From 52d88d3f365c86679640da8b2d4dd3e6db8c92b0 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 27 Nov 2016 17:36:11 -0500 Subject: [PATCH 001/166] Re-added the table code. Need to patch together everything else, but. --- common/table.c | 22 ++ darwin/table.m | 581 ++++++++++++++++++++++++++++++++++++++ test/images.c | 202 +++++++++++++ test/page16.c | 143 ++++++++++ uitable.h | 65 +++++ unix/table.c | 642 ++++++++++++++++++++++++++++++++++++++++++ windows/tablepart.cpp | 95 +++++++ windows/tableutil.cpp | 32 +++ 8 files changed, 1782 insertions(+) create mode 100644 common/table.c create mode 100644 darwin/table.m create mode 100644 test/images.c create mode 100644 test/page16.c create mode 100644 uitable.h create mode 100644 unix/table.c create mode 100644 windows/tablepart.cpp create mode 100644 windows/tableutil.cpp diff --git a/common/table.c b/common/table.c new file mode 100644 index 00000000..3726883a --- /dev/null +++ b/common/table.c @@ -0,0 +1,22 @@ +// 21 june 2016 +#include "../ui.h" +#include "uipriv.h" + +void *uiTableModelGiveInt(int i) +{ + return (void *) ((intptr_t) i); +} + +int uiTableModelTakeInt(void *v) +{ + return (int) ((intptr_t) v); +} + +uiTableColumn *uiTableAppendTextColumn(uiTable *t, const char *name, int modelColumn) +{ + uiTableColumn *tc; + + tc = uiTableAppendColumn(t, name); + uiTableColumnAppendTextPart(tc, modelColumn, 1); + return tc; +} diff --git a/darwin/table.m b/darwin/table.m new file mode 100644 index 00000000..9e578c85 --- /dev/null +++ b/darwin/table.m @@ -0,0 +1,581 @@ +// 21 june 2016 +#import "uipriv_darwin.h" + +// TODOs +// - initial state of table view is off +// - header cell seems off +// - background color shows up for a line or two below selection +// - editable NSTextFields have no intrinsic width +// - changing a part property does not refresh views +// - is the Y position of checkbox cells correct? +// - progressbars appear ABOVE the table header + +// LONGTERM +// - reuse row views instead of creating a new one each time + +@interface tableModel : NSObject { + uiTableModel *libui_m; +} +- (id)initWithModel:(uiTableModel *)m; +- (IBAction)onAction:(id)sender; +@end + +enum { + partText, + partImage, + partButton, + partCheckbox, + partProgressBar, +}; + +@interface tablePart : NSObject +@property int type; +@property int textColumn; +@property int textColorColumn; +@property int imageColumn; +@property int valueColumn; +@property int expand; +@property int editable; +- (NSView *)mkView:(uiTableModel *)m row:(int)row; +@end + +@interface tableColumn : NSTableColumn +@property uiTableColumn *libui_col; +@end + +@interface tableView : NSTableView +@property uiTable *libui_t; +@end + +struct uiTableModel { + uiTableModelHandler *mh; + tableModel *m; + NSMutableArray *tables; +}; + +struct uiTableColumn { + tableColumn *c; + NSMutableArray *parts; +}; + +struct uiTable { + uiDarwinControl c; + NSScrollView *sv; + tableView *tv; + struct scrollViewData *d; + int backgroundColumn; +}; + +@implementation tableModel + +- (id)initWithModel:(uiTableModel *)m +{ + self = [super init]; + if (self) + self->libui_m = m; + return self; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tv +{ + uiTableModelHandler *mh = self->libui_m->mh; + + return (*(mh->NumRows))(mh, self->libui_m); +} + +// these are according to Interface Builder +#define xleft 2 +#define xmiddle 7 /* between images and text, anyway; let's just use it for everything to be simpler */ +#define xright 3 + + - (NSView *)tableView:(NSTableView *)tv viewForTableColumn:(NSTableColumn *)cc row:(NSInteger)row +{ + NSTableCellView *v; + tableColumn *c = (tableColumn *) cc; + tablePart *part; + NSMutableArray *views; + NSView *view, *prev; + + v = [[NSTableCellView alloc] initWithFrame:NSZeroRect]; + + views = [NSMutableArray new]; + for (part in c.libui_col->parts) + [views addObject:[part mkView:self->libui_m row:row]]; + if ([views count] == 0) // empty (TODO allow?) + goto done; + + // add to v and arrange horizontally + prev = nil; + for (view in views) { + [v addSubview:view]; + // TODO set [v imageView] and [v textField] as appropriate? + if (prev == nil) { // first view + [v addConstraint:mkConstraint(v, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + view, NSLayoutAttributeLeading, + 1, -xleft, + @"uiTableColumn first part horizontal constraint")]; + prev = view; + continue; + } + [v addConstraint:mkConstraint(prev, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + view, NSLayoutAttributeLeading, + 1, -xmiddle, + @"uiTableColumn middle horizontal constraint")]; + prev = view; + } + [v addConstraint:mkConstraint(prev, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + v, NSLayoutAttributeTrailing, + 1, -xright, + @"uiTableColumn last part horizontal constraint")]; + + // and vertically + for (view in views) { + [v addConstraint:mkConstraint(view, NSLayoutAttributeCenterY, + NSLayoutRelationEqual, + v, NSLayoutAttributeCenterY, + 1, 0, + @"uiTableColumn part vertical constraint")]; + // TODO avoid the need for this hack + if ([view isKindOfClass:[NSImageView class]]) + [v addConstraint:mkConstraint(view, NSLayoutAttributeTop, + NSLayoutRelationEqual, + v, NSLayoutAttributeTop, + 1, 0, + @"uiTableColumn part vertical top constraint")]; + } + +done: + [views release]; + [v setTranslatesAutoresizingMaskIntoConstraints:NO]; + // TODO autorelease? + return v; +} + +- (void)tableView:(NSTableView *)nstv didAddRowView:(NSTableRowView *)rv forRow:(NSInteger)row +{ + uiTableModel *m = self->libui_m; + tableView *tv = (tableView *) nstv; + uiTable *t = tv.libui_t; + NSColor *color; + + if (t->backgroundColumn == -1) + return; + color = (NSColor *) ((*(m->mh->CellValue))(m->mh, m, row, t->backgroundColumn)); + if (color == nil) + return; + [rv setBackgroundColor:color]; + // TODO autorelease color? or release it? +} + +- (IBAction)onAction:(id)sender +{ + uiTableModel *m = self->libui_m; + NSView *view = (NSView *) sender; + NSTableView *tv; + NSInteger row; + const void *data; + + row = -1; + for (tv in m->tables) { + row = [tv rowForView:view]; + if (row != -1) + break; + } + if (row == -1) + implbug("table model action triggered on view with no associated table"); + + if ([view isKindOfClass:[NSTextField class]]) + data = [[((NSTextField *) view) stringValue] UTF8String]; + else if ([view isKindOfClass:[NSButton class]]) { + NSButton *b; + + b = (NSButton *) view; +// if ([b buttonType] == NSSwitchButton) + data = uiTableModelGiveInt([b state] == NSOnState); +// TODO there is no buttonType getter +if(1); else + data = NULL; + } else + implbug("table model editing action triggered on non-editable view"); + + // note the use of [view tag] — we need the model column, which we store in the view tag for relevant views below + (*(m->mh->SetCellValue))(m->mh, m, + row, [view tag], + data); + // always refresh the value in case the model rejected it + // TODO only affect tv? + uiTableModelRowChanged(m, row); +} + +@end + +@implementation tablePart + +- (id)init +{ + self = [super init]; + if (self) { + self.textColumn = -1; + self.textColorColumn = -1; + } + return self; +} + +- (NSView *)mkView:(uiTableModel *)m row:(int)row +{ + void *data; + NSString *str; + NSView *view; + NSTextField *tf; + NSImageView *iv; + NSButton *b; + NSProgressIndicator *p; + int value; + + switch (self.type) { + case partText: + data = (*(m->mh->CellValue))(m->mh, m, row, self.textColumn); + str = toNSString((char *) data); + uiFree(data); + tf = newLabel(str); + // TODO set wrap and ellipsize modes? + if (self.textColorColumn != -1) { + NSColor *color; + + color = (NSColor *) ((*(m->mh->CellValue))(m->mh, m, row, self.textColorColumn)); + if (color != nil) + [tf setTextColor:color]; + // TODO release color + } + if (self.editable) { + [tf setEditable:YES]; + [tf setTarget:m->m]; + [tf setAction:@selector(onAction:)]; + } + [tf setTag:self.textColumn]; + view = tf; + break; + case partImage: + data = (*(m->mh->CellValue))(m->mh, m, row, self.imageColumn); + iv = [[NSImageView alloc] initWithFrame:NSZeroRect]; + [iv setImage:imageImage((uiImage *) data)]; + [iv setImageFrameStyle:NSImageFrameNone]; + [iv setImageAlignment:NSImageAlignCenter]; + [iv setImageScaling:NSImageScaleProportionallyDown]; + [iv setAnimates:NO]; + [iv setEditable:NO]; + [iv addConstraint:mkConstraint(iv, NSLayoutAttributeWidth, + NSLayoutRelationEqual, + iv, NSLayoutAttributeHeight, + 1, 0, + @"uiTable image squareness constraint")]; + [iv setTag:self.imageColumn]; + view = iv; + break; + case partButton: + // TODO buttons get clipped + data = (*(m->mh->CellValue))(m->mh, m, row, self.textColumn); + str = toNSString((char *) data); + b = [[NSButton alloc] initWithFrame:NSZeroRect]; + [b setTitle:str]; + [b setButtonType:NSMomentaryPushInButton]; + [b setBordered:YES]; + [b setBezelStyle:NSRoundRectBezelStyle]; + uiDarwinSetControlFont(b, NSRegularControlSize); + if (self.editable) { + [b setTarget:m->m]; + [b setAction:@selector(onAction:)]; + } else + [b setEnabled:NO]; + [b setTag:self.textColumn]; + view = b; + break; + case partCheckbox: + data = (*(m->mh->CellValue))(m->mh, m, row, self.valueColumn); + b = [[NSButton alloc] initWithFrame:NSZeroRect]; + [b setTitle:@""]; + [b setButtonType:NSSwitchButton]; + // doesn't seem to have an associated bezel style + [b setBordered:NO]; + [b setTransparent:NO]; + uiDarwinSetControlFont(b, NSRegularControlSize); + if (uiTableModelTakeInt(data) != 0) + [b setState:NSOnState]; + else + [b setState:NSOffState]; + if (self.editable) { + [b setTarget:m->m]; + [b setAction:@selector(onAction:)]; + } else + [b setEnabled:NO]; + [b setTag:self.valueColumn]; + view = b; + break; + case partProgressBar: + data = (*(m->mh->CellValue))(m->mh, m, row, self.valueColumn); + value = uiTableModelTakeInt(data); + // TODO no intrinsic width + p = [[NSProgressIndicator alloc] initWithFrame:NSZeroRect]; + [p setControlSize:NSRegularControlSize]; + [p setBezeled:YES]; + [p setStyle:NSProgressIndicatorBarStyle]; + if (value == -1) { + [p setIndeterminate:YES]; + [p startAnimation:p]; + } else if (value == 100) { + [p setIndeterminate:NO]; + [p setMaxValue:101]; + [p setDoubleValue:101]; + [p setDoubleValue:100]; + [p setMaxValue:100]; + } else { + [p setIndeterminate:NO]; + [p setDoubleValue:(value + 1)]; + [p setDoubleValue:value]; + } + view = p; + break; + } + + // if stretchy, don't hug, otherwise hug forcibly + if (self.expand) + [view setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal]; + else + [view setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; + [view setTranslatesAutoresizingMaskIntoConstraints:NO]; + // TODO autorelease? + return view; +} + +@end + +@implementation tableColumn +@end + +@implementation tableView +@end + +void *uiTableModelStrdup(const char *str) +{ + // TODO don't we have this already? + char *dup; + + dup = (char *) uiAlloc((strlen(str) + 1) * sizeof (char), "char[]"); + strcpy(dup, str); + return dup; +} + +uiTableModel *uiNewTableModel(uiTableModelHandler *mh) +{ + uiTableModel *m; + + m = uiNew(uiTableModel); + m->mh = mh; + m->m = [[tableModel alloc] initWithModel:m]; + m->tables = [NSMutableArray new]; + return m; +} + +void *uiTableModelGiveColor(double r, double g, double b, double a) +{ + return [[NSColor colorWithSRGBRed:r green:g blue:b alpha:a] retain]; +} + +void uiFreeTableModel(uiTableModel *m) +{ + if ([m->tables count] != 0) + userbug("You cannot free a uiTableModel while uiTables are using it."); + [m->tables release]; + [m->m release]; + uiFree(m); +} + +void uiTableModelRowInserted(uiTableModel *m, int newIndex) +{ + NSTableView *tv; + NSIndexSet *set; + + set = [NSIndexSet indexSetWithIndex:newIndex]; + for (tv in m->tables) + [tv insertRowsAtIndexes:set withAnimation:NSTableViewAnimationEffectNone]; + // set is autoreleased +} + +void uiTableModelRowChanged(uiTableModel *m, int index) +{ + NSTableView *tv; + NSIndexSet *set, *cols; + + set = [NSIndexSet indexSetWithIndex:index]; + for (tv in m->tables) { + cols = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, [[tv tableColumns] count])]; + [tv reloadDataForRowIndexes:set columnIndexes:cols]; + // TODO this isn't enough + [cols release]; + } + // set is autoreleased +} + +void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) +{ + NSTableView *tv; + NSIndexSet *set; + + set = [NSIndexSet indexSetWithIndex:oldIndex]; + for (tv in m->tables) + [tv removeRowsAtIndexes:set withAnimation:NSTableViewAnimationEffectNone]; + // set is autoreleased +} + +void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) +{ + tablePart *part; + + part = [tablePart new]; + part.type = partText; + part.textColumn = modelColumn; + part.expand = expand; + [c->parts addObject:part]; +} + +void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) +{ + tablePart *part; + + part = [tablePart new]; + part.type = partImage; + part.imageColumn = modelColumn; + part.expand = expand; + [c->parts addObject:part]; +} + +void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand) +{ + tablePart *part; + + part = [tablePart new]; + part.type = partButton; + part.textColumn = modelColumn; + part.expand = expand; + part.editable = 1; // editable by default + [c->parts addObject:part]; +} + +void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) +{ + tablePart *part; + + part = [tablePart new]; + part.type = partCheckbox; + part.valueColumn = modelColumn; + part.expand = expand; + part.editable = 1; // editable by default + [c->parts addObject:part]; +} + +void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand) +{ + tablePart *part; + + part = [tablePart new]; + part.type = partProgressBar; + part.valueColumn = modelColumn; + part.expand = expand; + [c->parts addObject:part]; +} + +void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) +{ + tablePart *p; + + p = (tablePart *) [c->parts objectAtIndex:part]; + p.editable = editable; +} + +void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) +{ + tablePart *p; + + p = (tablePart *) [c->parts objectAtIndex:part]; + p.textColorColumn = modelColumn; +} + +uiDarwinControlAllDefaultsExceptDestroy(uiTable, sv) + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + + // TODO + [t->sv release]; + uiFreeControl(uiControl(t)); +} + +uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) +{ + uiTableColumn *c; + + c = uiNew(uiTableColumn); + c->c = [[tableColumn alloc] initWithIdentifier:@""]; + c->c.libui_col = c; + // via Interface Builder + [c->c setResizingMask:(NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask)]; + // 10.10 adds -[NSTableColumn setTitle:]; before then we have to do this + [[c->c headerCell] setStringValue:toNSString(name)]; + // TODO is this sufficient? + [[c->c headerCell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]]; + c->parts = [NSMutableArray new]; + [t->tv addTableColumn:c->c]; + return c; +} + +void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) +{ + t->backgroundColumn = modelColumn; +} + +uiTable *uiNewTable(uiTableModel *model) +{ + uiTable *t; + struct scrollViewCreateParams p; + + uiDarwinNewControl(uiTable, t); + + t->tv = [[tableView alloc] initWithFrame:NSZeroRect]; + t->tv.libui_t = t; + + [t->tv setDataSource:model->m]; + [t->tv setDelegate:model->m]; + [t->tv reloadData]; + [model->tables addObject:t->tv]; + + // TODO is this sufficient? + [t->tv setAllowsColumnReordering:NO]; + [t->tv setAllowsColumnResizing:YES]; + [t->tv setAllowsMultipleSelection:NO]; + [t->tv setAllowsEmptySelection:YES]; + [t->tv setAllowsColumnSelection:NO]; + [t->tv setUsesAlternatingRowBackgroundColors:YES]; + [t->tv setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleRegular]; + [t->tv setGridStyleMask:NSTableViewGridNone]; + [t->tv setAllowsTypeSelect:YES]; + // TODO floatsGroupRows — do we even allow group rows? + + memset(&p, 0, sizeof (struct scrollViewCreateParams)); + p.DocumentView = t->tv; + // this is what Interface Builder sets it to + // TODO verify + p.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0]; + p.DrawsBackground = YES; + p.Bordered = YES; + p.HScroll = YES; + p.VScroll = YES; + t->sv = mkScrollView(&p, &(t->d)); + + t->backgroundColumn = -1; + + return t; +} diff --git a/test/images.c b/test/images.c new file mode 100644 index 00000000..df21b6eb --- /dev/null +++ b/test/images.c @@ -0,0 +1,202 @@ +// auto-generated by images/gen.go +#include "test.h" + +static const uint32_t dat0[] = { + 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFF8AC3FF, 0xFF8AC3FF, 0xFF8AC3FF, 0xFF8AC3FF, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFF89C57C, + 0xFF89C57C, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFF89C57C, + 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, +}; + +static const uint32_t dat1[] = { + 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, + 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFE560FC, + 0xFFE560FC, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, + 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, + 0xFFFFFB43, 0xFFE560FC, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFE560FC, + 0xFFE560FC, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFF4079, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFF8AC3FF, + 0xFF8AC3FF, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFFFFFB43, 0xFF89C57C, + 0xFF89C57C, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFF89C57C, + 0xFF89C57C, 0xFF8AC3FF, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, + 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFFFFFB43, 0xFF8AC3FF, 0xFF89C57C, + 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, + 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, 0xFF89C57C, +}; + +static const uint32_t dat2[] = { + 0xAC676767, 0xFF818181, 0xFF818181, 0xFF818181, 0xFF818181, 0xFF818181, 0xFF818181, 0xFF818181, 0xFF818181, 0xFF818181, 0xFF818181, 0xFF818181, 0xFF818181, 0x562B2B2B, 0x00000000, 0x00000000, + 0xFF818181, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF818181, 0x00000000, 0x00000000, + 0xFF818181, 0xFFFFFFFF, 0xFFB7B7B7, 0xFFB7B7B7, 0xFFB7B7B8, 0xFF9E9E9F, 0xFFB8B8B8, 0xFFB8B8B8, 0xFF9F9F9F, 0xFFB8B8B9, 0xFFB8B8B9, 0xFF9F9F9F, 0xFFFFFFFF, 0xFF818181, 0x00000000, 0x00000000, + 0xFF818181, 0xFFFFFFFF, 0xFF9E9E9E, 0xFF9F9F9E, 0xFF9F9F9F, 0xFF9F9F9F, 0xFF9F9F9F, 0xFF9F9F9F, 0xFF9F9F9F, 0xFF9F9F9F, 0xFF9F9F9F, 0xFF9F9F9F, 0xFFFFFFFF, 0xFF818181, 0x00000000, 0x00000000, + 0xFF818181, 0xFFFFFFFF, 0xFFB7B8B7, 0xFFC3C3C3, 0xFFC3C3C3, 0xFF9F9F9F, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFC2C2C2, 0xFFEEEFEE, 0xFFEFEEEF, 0xFFC2C2C2, 0xFFFFFFFF, 0xFF818181, 0x00000000, 0x00000000, + 0xFF818181, 0xFFFFFFFF, 0xFFB1B1B1, 0xFFB8B8B8, 0xFFB9B8B9, 0xFF9F9F9F, 0xFFE0E0E0, 0xFFE0E1E0, 0xFFC2C2C2, 0xFFE1E0E0, 0xFFE1E1E1, 0xFFC2C2C2, 0xFFFFFFFF, 0xFF818181, 0x00000000, 0x00000000, + 0xFF818181, 0xFFFFFFFF, 0xFFB9B8B8, 0xFFC4C4C4, 0xFFC3C4C4, 0xFF9F9F9F, 0xFFEEEEEF, 0xFFEFEEEF, 0xFFC2C2C2, 0xFFEFEFEF, 0xFFF0EFEF, 0xFFC2C2C2, 0xFFFFFFFF, 0xFF818181, 0x00000000, 0x00000000, + 0xFF818181, 0xFFFFFFFF, 0xFFB1B1B1, 0xFFB9B9B8, 0xFFB9B9B9, 0xFF9F9F9F, 0xFFC9A6A5, 0xFFAE3A36, 0xFFA50F0C, 0xFFA40201, 0xFFA40201, 0xFFA40D0A, 0xFFB03B37, 0xFF8D6969, 0x00000000, 0x00000000, + 0xFF818181, 0xFFFFFFFF, 0xFFB9B9B9, 0xFFC4C4C4, 0xFFC4C4C4, 0xFF9E403D, 0xFFA70A07, 0xFFC66453, 0xFFCB715C, 0xFFC9735D, 0xFFC5715B, 0xFFBF6C59, 0xFFC06E61, 0xFFAC201D, 0xC17E2927, 0x19191919, + 0xFF818181, 0xFFFFFFFF, 0xFFB2B2B2, 0xFFB9B9B9, 0xFFA65C5B, 0xFFAF2017, 0xFFCB725D, 0xFFC7654D, 0xFFBB5338, 0xFFB65136, 0xFFB04E35, 0xFFB35D47, 0xFFAE5B45, 0xFFA95743, 0xFFAA2F28, 0xA1641716, + 0xFF818181, 0xFFFFFFFF, 0xFFB9B9B9, 0xFFC4C4C4, 0xFFA51412, 0xFFCA6F5A, 0xFFBF5439, 0xFFBA5237, 0xFFB44F36, 0xFFAF4D34, 0xFF886556, 0xFF765F5A, 0xFF715B56, 0xFF6B5853, 0xFF77605B, 0xF7980C0B, + 0xFF818181, 0xFFFFFFFF, 0xFFB2B2B2, 0xFFB9B9B9, 0xFFA40201, 0xFFC76951, 0xFFB85137, 0xFFB24E36, 0xFFAD4C34, 0xFFAE653A, 0xFF95995D, 0xFF1D84A0, 0xFF177F99, 0xFF3D91A5, 0xFF3B8EA0, 0xFFA20101, + 0xFF818181, 0xFFFFFFFF, 0xFFB9B9B9, 0xFFC4C4C4, 0xFF9F1212, 0xFFCA7E6D, 0xFFB85F48, 0xFFAB4C33, 0xFFA64932, 0xFFB18B44, 0xFFB8BC50, 0xFF358086, 0xFF509CAD, 0xFF278397, 0xFF496E77, 0xF7920808, + 0xFF818181, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBC7878, 0xFF9C1D1A, 0xFFBD7E6D, 0xFFBE7F70, 0xFFBD8374, 0xFFCDC787, 0xFFCBD089, 0xFFB4B48A, 0xFF5C93A0, 0xFF44656D, 0xFF7B1618, 0xA1661D1D, + 0xB4696969, 0xFF818181, 0xFF818181, 0xFF818181, 0xFF818181, 0xFF934040, 0xFF9E0605, 0xFF99463D, 0xFF9E624D, 0xFFA6A14C, 0xFF9F9B4A, 0xFF948845, 0xFF4B3C43, 0xFF9A0505, 0xBA771F1F, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x55351616, 0xD37F1717, 0xF99C0B0B, 0xFFA30201, 0xFFA20101, 0xF99C0B0B, 0xD3881E1E, 0x55412222, 0x00000000, 0x00000000, +}; + +static const uint32_t dat3[] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x52303030, 0xF2919191, 0xFF999999, 0xFF9C9C9C, 0xFF9E9E9E, 0xFF9C9C9C, 0xFF999999, 0xFF969696, 0xFF939393, 0xFF8F8F8F, 0xFF8C8C8C, 0xFF888888, + 0xFF848484, 0xFF818181, 0xFF7D7D7D, 0xFF7A7A7A, 0xFF767676, 0xFF727272, 0xFF6F6F6F, 0xFF6B6B6B, 0xFF686868, 0xFF646464, 0xF25F5F5F, 0x521E1E1E, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xEF8D8D8D, 0xFFFDFDFD, 0xFFFDFDFD, 0xFFFEFEFE, 0xFFFEFEFE, 0xFFFEFEFE, 0xFFFDFDFD, 0xFFFDFDFD, 0xFFFDFDFD, 0xFFFDFDFD, 0xFFFCFCFC, 0xFFFCFCFC, + 0xFFFCFCFC, 0xFFFBFBFB, 0xFFFBFBFB, 0xFFFBFBFB, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFF9F9F9, 0xFFF9F9F9, 0xFFF9F9F9, 0xFFF8F8F8, 0xEF5D5D5D, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF939393, 0xFFFDFDFD, 0xFFDCDCDC, 0xFFDDDDDD, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFE0E0E0, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, + 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE3E3E3, 0xFFE3E3E3, 0xFFE3E3E3, 0xFFE3E3E3, 0xFFE3E3E3, 0xFFE2E2E2, 0xFFF8F8F8, 0xFF5D5D5D, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF929292, 0xFFFEFEFE, 0xFFDDDDDD, 0xFF787878, 0xFF8D8D8D, 0xFF8E8E8E, 0xFF8F8F8F, 0xFF8F8F8F, 0xFF787878, 0xFF8F8F8F, 0xFF909090, 0xFF909090, + 0xFF7A7A7A, 0xFF919191, 0xFF919191, 0xFF919191, 0xFF7B7B7B, 0xFF919191, 0xFF919191, 0xFF919191, 0xFF7D7D7D, 0xFFE3E3E3, 0xFFF8F8F8, 0xFF5D5D5D, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF909090, 0xFFFEFEFE, 0xFFDEDEDE, 0xFF949494, 0xFFB0B0B0, 0xFFB1B1B1, 0xFFB1B1B1, 0xFFB1B1B1, 0xFF969696, 0xFFB2B2B2, 0xFFB3B3B3, 0xFFB3B3B3, + 0xFF989898, 0xFFB4B4B4, 0xFFB4B4B4, 0xFFB5B5B5, 0xFF999999, 0xFFB5B5B5, 0xFFB5B5B5, 0xFFB5B5B5, 0xFF999999, 0xFFE4E4E4, 0xFFF8F8F8, 0xFF5C5C5C, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF8E8E8E, 0xFFFDFDFD, 0xFFDFDFDF, 0xFF949494, 0xFFB1B1B1, 0xFFB1B1B1, 0xFFB2B2B2, 0xFFB2B2B2, 0xFF979797, 0xFFB3B3B3, 0xFFB4B4B4, 0xFFB4B4B4, + 0xFF999999, 0xFFB5B5B5, 0xFFB5B5B5, 0xFFB5B5B5, 0xFF999999, 0xFFB5B5B5, 0xFFB5B5B5, 0xFFB5B5B5, 0xFF999999, 0xFFE5E5E5, 0xFFF8F8F8, 0xFF5C5C5C, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF8B8B8B, 0xFFFDFDFD, 0xFFDFDFDF, 0xFF7A7A7A, 0xFF919191, 0xFF929292, 0xFF929292, 0xFF939393, 0xFF7D7D7D, 0xFF949494, 0xFF949494, 0xFF949494, + 0xFF7D7D7D, 0xFF949494, 0xFF949494, 0xFF959595, 0xFF7E7E7E, 0xFF959595, 0xFF959595, 0xFF959595, 0xFF7E7E7E, 0xFFE6E6E6, 0xFFF8F8F8, 0xFF5B5B5B, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF898989, 0xFFFDFDFD, 0xFFE0E0E0, 0xFF949494, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB1B1B1, 0xFFB2B2B2, 0xFF979797, 0xFFE2E2E2, 0xFFE3E3E3, 0xFFE3E3E3, + 0xFFC0C0C0, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE5E5E5, 0xFFC1C1C1, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFC1C1C1, 0xFFE8E8E8, 0xFFF8F8F8, 0xFF5A5A5A, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF868686, 0xFFFDFDFD, 0xFFE1E1E1, 0xFF7B7B7B, 0xFF929292, 0xFF939393, 0xFF949494, 0xFF949494, 0xFF7D7D7D, 0xFFBDBDBD, 0xFFBDBDBD, 0xFFBDBDBD, + 0xFFA0A0A0, 0xFFBEBEBE, 0xFFBEBEBE, 0xFFBFBFBF, 0xFFA1A1A1, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFA1A1A1, 0xFFE9E9E9, 0xFFF8F8F8, 0xFF595959, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF838383, 0xFFFDFDFD, 0xFFE1E1E1, 0xFF949494, 0xFFB1B1B1, 0xFFB2B2B2, 0xFFB3B3B3, 0xFFB3B3B3, 0xFF979797, 0xFFE4E4E4, 0xFFE5E5E5, 0xFFE5E5E5, + 0xFFC2C2C2, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE7E7E7, 0xFFC3C3C3, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFC3C3C3, 0xFFEAEAEA, 0xFFF8F8F8, 0xFF585858, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF808080, 0xFFFCFCFC, 0xFFE2E2E2, 0xFF7C7C7C, 0xFF949494, 0xFF949494, 0xFF949494, 0xFF949494, 0xFF7E7E7E, 0xFFBEBEBE, 0xFFBEBEBE, 0xFFBFBFBF, + 0xFFA2A2A2, 0xFFC0C0C0, 0xFFC0C0C0, 0xFFC1C1C1, 0xFFA3A3A3, 0xFFC1C1C1, 0xFFC1C1C1, 0xFFC1C1C1, 0xFFA3A3A3, 0xFFEBEBEB, 0xFFF8F8F8, 0xFF575757, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF7D7D7D, 0xFFFCFCFC, 0xFFE3E3E3, 0xFF969696, 0xFFB3B3B3, 0xFFB3B3B3, 0xFFB3B3B3, 0xFFB4B4B4, 0xFF999999, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE7E7E7, + 0xFFC4C4C4, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE9E9E9, 0xFFC4C4C4, 0xFFE9E9E9, 0xFFE9E9E9, 0xFFE9E9E9, 0xFFC4C4C4, 0xFFECECEC, 0xFFF8F8F8, 0xFF555555, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF7A7A7A, 0xFFFCFCFC, 0xFFE3E3E3, 0xFF7D7D7D, 0xFF949494, 0xFF949494, 0xFF959595, 0xFF969696, 0xFF7F7F7F, 0xFFBFBFBF, 0xFFC0C0C0, 0xFFC1C1C1, + 0xFFA3A3A3, 0xFFC1C1C1, 0xFFC1C1C1, 0xFFC2C2C2, 0xFFA4A4A4, 0xFFC2C2C2, 0xFFC2C2C2, 0xFFC2C2C2, 0xFFA4A4A4, 0xFFEDEDED, 0xFFF8F8F8, 0xFF545454, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF777777, 0xFFFBFBFB, 0xFFE4E4E4, 0xFF979797, 0xFFB3B3B3, 0xFFB4B4B4, 0xFFB5B5B5, 0xFFB6B6B6, 0xFF999999, 0xFFE7E7E7, 0xFFE8E8E8, 0xFFE9E9E9, + 0xFFC4C4C4, 0xFFEAEAEA, 0xFFEAEAEA, 0xFFEBEBEB, 0xFFC6C6C6, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFC6C6C6, 0xFFEEEEEE, 0xFFF8F8F8, 0xFF525252, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF737373, 0xFFFBFBFB, 0xFFE4E4E4, 0xFF7D7D7D, 0xFF949494, 0xFF959595, 0xFF969696, 0xFF979797, 0xFF7F7F7F, 0xFFC1C1C1, 0xFFC1C1C1, 0xFFC2C2C2, + 0xFFA3A2A2, 0xFFAB9191, 0xFF946060, 0xFF843D3D, 0xFF782525, 0xFF802727, 0xFF7E2B2B, 0xFF843D3D, 0xFF865252, 0xFFCCB2B2, 0xFFF6F5F5, 0xFF505050, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF707070, 0xFFFBFBFB, 0xFFE5E5E5, 0xFF979797, 0xFFB4B4B4, 0xFFB5B5B5, 0xFFB6B6B6, 0xFFB6B6B6, 0xFF9A9A9A, 0xFFE9E9E9, 0xFFE6E4E4, 0xFFB18585, + 0xFF7A1B1B, 0xFFA62F2F, 0xFFD35050, 0xFFF26767, 0xFFFF7070, 0xFFFE6E6E, 0xFFFC6969, 0xFFED5B5B, 0xFFCD4343, 0xFFA22525, 0xFF7E1D1D, 0xFF582C2C, 0x06020000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF6D6D6D, 0xFFFBFBFB, 0xFFE5E5E5, 0xFF7D7D7D, 0xFF959595, 0xFF969696, 0xFF979797, 0xFF979797, 0xFF808080, 0xFFB7ABAB, 0xFF7B2828, 0xFFAC3434, + 0xFFF56A6A, 0xFFFF7070, 0xFFFF7070, 0xFFFE6F6F, 0xFFFC6A6A, 0xFFFA6565, 0xFFF86060, 0xFFF55C5C, 0xFFF35757, 0xFFF15252, 0xFFE64848, 0xFFA11F1F, 0xCB530000, 0x1D0C0000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF6A6A6A, 0xFFFAFAFA, 0xFFE6E6E6, 0xFF989898, 0xFFB5B5B5, 0xFFB6B6B6, 0xFFB6B6B6, 0xFFB7B7B7, 0xFF989292, 0xFF7E2626, 0xFFCB5050, 0xFFFF7474, + 0xFFFF7070, 0xFFFF7070, 0xFFFD6B6B, 0xFFFA6767, 0xFFF86262, 0xFFF65D5D, 0xFFF45858, 0xFFF15353, 0xFFEF4E4E, 0xFFED4949, 0xFFEB4444, 0xFFE93F3F, 0xFFB62424, 0xD7570000, 0x0F060000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF666666, 0xFFFAFAFA, 0xFFE6E6E6, 0xFF7E7E7E, 0xFF969696, 0xFF979797, 0xFF979797, 0xFF989898, 0xFF733D3D, 0xFFA63737, 0xFFFF7979, 0xFFFF7272, + 0xFFFD6D6D, 0xFFFB6868, 0xFFF96363, 0xFFF65E5E, 0xFFF45959, 0xFFF25454, 0xFFF04F4F, 0xFFEE4A4A, 0xFFEB4545, 0xFFE94141, 0xFFE73C3C, 0xFFE53737, 0xFFE23535, 0xFF911313, 0x86360000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF636363, 0xFFFAFAFA, 0xFFE6E6E6, 0xFF989898, 0xFFB5B5B5, 0xFFB6B6B6, 0xFFB7B7B7, 0xFFB8B8B8, 0xFF6E1515, 0xFFDF6D6D, 0xFFFE7575, 0xFFFB6969, + 0xFFF96464, 0xFFF75F5F, 0xFFF55A5A, 0xFFF35555, 0xFFF05050, 0xFFEE4C4C, 0xFFEC4747, 0xFFEA4242, 0xFFE73D3D, 0xFFE53838, 0xFFE33333, 0xFFE12E2E, 0xFFDF2D2D, 0xFFC22828, 0xDE590000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF606060, 0xFFF9F9F9, 0xFFE6E6E6, 0xFF7E7E7E, 0xFF979797, 0xFF979797, 0xFF989898, 0xFF989898, 0xFF670101, 0xFFED7B7B, 0xFFFA6A6A, 0xFFF86060, + 0xFFF55B5B, 0xFFF35656, 0xFFF15252, 0xFFEF4D4D, 0xFFEC4848, 0xFFCD8478, 0xFF777CAE, 0xFF777BAD, 0xFF7275A7, 0xFF6B6FA1, 0xFF67699B, 0xFF606396, 0xFF5E6091, 0xFF7E5E82, 0xFC660000, 0x02000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF5D5D5D, 0xFFF9F9F9, 0xFFE6E6E6, 0xFF999999, 0xFFB6B6B6, 0xFFB6B6B6, 0xFFB7B7B7, 0xFFB8B8B8, 0xFF670000, 0xFFD86262, 0xFFF76D6D, 0xFFF45858, + 0xFFF15353, 0xFFEF4E4E, 0xFFED4949, 0xFFEB4444, 0xFFE84341, 0xFFC5E18C, 0xFF8A939F, 0xFF5889C7, 0xFF5182C1, 0xFF4B7CBA, 0xFF4475B4, 0xFF3D6EAE, 0xFF4572AD, 0xFF6A6087, 0xFF670000, 0x0E000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF595959, 0xFFF9F9F9, 0xFFE7E7E7, 0xFF7E7E7E, 0xFF979797, 0xFF979797, 0xFF989898, 0xFF989898, 0xFF690D0D, 0xFFA32424, 0xFFEF7B7B, 0xFFF15A5A, + 0xFFEE4A4A, 0xFFEB4545, 0xFFE94040, 0xFFE73C3C, 0xFFD86B4D, 0xFFADE773, 0xFFABC35F, 0xFF5585C0, 0xFF4D7EBD, 0xFF4778B7, 0xFF4071B0, 0xFF4372AE, 0xFF6B7AA6, 0xFF544267, 0xE85C0000, 0x16000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF565656, 0xFFF8F8F8, 0xFFE7E7E7, 0xFF999999, 0xFFB6B6B6, 0xFFB6B6B6, 0xFFB7B7B7, 0xFFB8B8B8, 0xFF773939, 0xFF7A0909, 0xFFBB3232, 0xFFE87171, + 0xFFED6161, 0xFFE84242, 0xFFE53838, 0xFFE33333, 0xFFBA9F4E, 0xFF99E053, 0xFF8FDC43, 0xFF848A66, 0xFF497AB9, 0xFF4777B4, 0xFF5882B9, 0xFF797FA6, 0xFF455E90, 0xFF651723, 0xAC3E0000, 0x13000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF535353, 0xFFF8F8F8, 0xFFE6E6E6, 0xFF7E7E7E, 0xFF979797, 0xFF979797, 0xFF989898, 0xFF989898, 0xFF766B6B, 0xFF6D0E0E, 0xFF881111, 0xFFB02323, + 0xFFCA4545, 0xFFE26666, 0xFFE85D5D, 0xFFE24F4B, 0xFFA1D656, 0xFF91DC47, 0xFF8BD93C, 0xFF90C93A, 0xFF7494BB, 0xFF848EB4, 0xFF70688E, 0xFF3B5E92, 0xFF602942, 0xEF5F0000, 0x400B0000, 0x08000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF4F4F4F, 0xFFF8F8F8, 0xFFE6E6E6, 0xFFE5E5E5, 0xFFE6E6E6, 0xFFE7E7E7, 0xFFE8E8E8, 0xFFE9E9E9, 0xFFE6E6E6, 0xFFBDA9A9, 0xFF711515, 0xFF7B0C0C, + 0xFFA21D1D, 0xFFAD1B1B, 0xFFB62727, 0xFFB24F33, 0xFF93A640, 0xFF96A840, 0xFF91A73C, 0xFF869F33, 0xFF6E675B, 0xFF375C93, 0xFF544D75, 0xFF6B1823, 0xEB5C0000, 0x59130000, 0x16000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x0D000000, 0xF14C4C4C, 0xFFF7F7F7, 0xFFF6F6F6, 0xFFF6F6F6, 0xFFF6F6F6, 0xFFF7F7F7, 0xFFF7F7F7, 0xFFF7F7F7, 0xFFF7F7F7, 0xFFF0F0F0, 0xFFD1C9C9, 0xFF905353, + 0xFF6A0505, 0xFF770A0A, 0xFF8E1414, 0xFF91331C, 0xFF728723, 0xFF709024, 0xFF6E8F23, 0xFF737C1F, 0xFF703C31, 0xFF6C141B, 0xFF6A0505, 0xFB531616, 0x52080000, 0x1D000000, 0x01000000, 0x00000000, + 0x00000000, 0x00000000, 0x0D000000, 0x22000000, 0x78191919, 0xF6535353, 0xFF565656, 0xFF565656, 0xFF565656, 0xFF565656, 0xFF565656, 0xFF565656, 0xFF565656, 0xFF565656, 0xFF555555, 0xFF4F4F4F, + 0xFF4B4343, 0xFF522828, 0xFF5A1616, 0xFF610A0A, 0xFF650404, 0xFF670000, 0xFF650404, 0xFF600909, 0xFF591515, 0xFF502626, 0xF9453C3C, 0x93151515, 0x3A000000, 0x1B000000, 0x02000000, 0x00000000, + 0x00000000, 0x00000000, 0x06000000, 0x1D000000, 0x30000000, 0x40000000, 0x47000000, 0x4A000000, 0x4A000000, 0x4A000000, 0x4A000000, 0x4A000000, 0x4A000000, 0x4A000000, 0x4A000000, 0x4A000000, + 0x4C000000, 0x54000000, 0x5E000000, 0x65000000, 0x69000000, 0x6B000000, 0x6B000000, 0x69000000, 0x63000000, 0x52000000, 0x4B000000, 0x3B000000, 0x29000000, 0x0C000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x07000000, 0x0F000000, 0x15000000, 0x16000000, 0x16000000, 0x16000000, 0x16000000, 0x16000000, 0x16000000, 0x16000000, 0x16000000, 0x16000000, + 0x16000000, 0x16000000, 0x16000000, 0x16000000, 0x17000000, 0x18000000, 0x17000000, 0x17000000, 0x16000000, 0x14000000, 0x0F000000, 0x08000000, 0x01000000, 0x00000000, 0x00000000, 0x00000000, +}; + +static const struct { + const char *name; + void *data; + int width; + int height; + int stride; +} files[] = { + { "andlabs_16x16test_24june2016.png", dat0, 16, 16, 64 }, + { "andlabs_32x32test_24june2016.png", dat1, 32, 32, 128 }, + { "tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png", dat2, 16, 16, 64 }, + { "tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png", dat3, 32, 32, 128 }, +}; + +void appendImageNamed(uiImage *img, const char *name) +{ + int i; + + i = 0; + for (;;) { + if (strcmp(name, files[i].name) == 0) { + uiImageAppend(img, files[i].data, files[i].width, files[i].height, files[i].stride); + return; + } + i++; + } +} + diff --git a/test/page16.c b/test/page16.c new file mode 100644 index 00000000..80ac0139 --- /dev/null +++ b/test/page16.c @@ -0,0 +1,143 @@ +// 21 june 2016 +#include "test.h" + +static uiTableModelHandler mh; + +static int modelNumColumns(uiTableModelHandler *mh, uiTableModel *m) +{ + return 9; +} + +static uiTableModelColumnType modelColumnType(uiTableModelHandler *mh, uiTableModel *m, int column) +{ + if (column == 3 || column == 4) + return uiTableModelColumnColor; + if (column == 5) + return uiTableModelColumnImage; + if (column == 7 || column == 8) + return uiTableModelColumnInt; + return uiTableModelColumnString; +} + +static int modelNumRows(uiTableModelHandler *mh, uiTableModel *m) +{ + return 15; +} + +static uiImage *img[2]; +static char row9text[1024]; +static int yellowRow = -1; +static int checkStates[15]; + +static void *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col) +{ + char buf[256]; + + if (col == 3) { + if (row == yellowRow) + return uiTableModelGiveColor(1, 1, 0, 1); + if (row == 3) + return uiTableModelGiveColor(1, 0, 0, 1); + if (row == 11) + return uiTableModelGiveColor(0, 0.5, 1, 0.5); + return NULL; + } + if (col == 4) { + if ((row % 2) == 1) + return uiTableModelGiveColor(0.5, 0, 0.75, 1); + return NULL; + } + if (col == 5) { + if (row < 8) + return img[0]; + return img[1]; + } + if (col == 7) + return uiTableModelGiveInt(checkStates[row]); + if (col == 8) { + if (row == 0) + return uiTableModelGiveInt(0); + if (row == 13) + return uiTableModelGiveInt(100); + if (row == 14) + return uiTableModelGiveInt(-1); + return uiTableModelGiveInt(50); + } + switch (col) { + case 0: + sprintf(buf, "Row %d", row); + break; + case 2: + if (row == 9) + return uiTableModelStrdup(row9text); + // fall through + case 1: + strcpy(buf, "Part"); + break; + case 6: + strcpy(buf, "Make Yellow"); + break; + } + return uiTableModelStrdup(buf); +} + +static void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col, const void *val) +{ + if (row == 9 && col == 2) + strcpy(row9text, (const char *) val); + if (col == 6) + yellowRow = row; + if (col == 7) + checkStates[row] = uiTableModelTakeInt(val); +} + +uiBox *makePage16(void) +{ + uiBox *page16; + uiTableModel *m; + uiTable *t; + uiTableColumn *tc; + + img[0] = uiNewImage(16, 16); + appendImageNamed(img[0], "andlabs_16x16test_24june2016.png"); + appendImageNamed(img[0], "andlabs_32x32test_24june2016.png"); + img[1] = uiNewImage(16, 16); + appendImageNamed(img[1], "tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png"); + appendImageNamed(img[1], "tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png"); + + strcpy(row9text, "Part"); + + memset(checkStates, 0, 15 * sizeof (int)); + + page16 = newVerticalBox(); + + mh.NumColumns = modelNumColumns; + mh.ColumnType = modelColumnType; + mh.NumRows = modelNumRows; + mh.CellValue = modelCellValue; + mh.SetCellValue = modelSetCellValue; + m = uiNewTableModel(&mh); + + t = uiNewTable(m); + uiBoxAppend(page16, uiControl(t), 1); + + uiTableAppendTextColumn(t, "Column 1", 0); + + tc = uiTableAppendColumn(t, "Column 2"); + uiTableColumnAppendImagePart(tc, 5, 0); + uiTableColumnAppendTextPart(tc, 1, 0); + uiTableColumnAppendTextPart(tc, 2, 1); + uiTableColumnPartSetTextColor(tc, 1, 4); + uiTableColumnPartSetEditable(tc, 2, 1); + + uiTableSetRowBackgroundColorModelColumn(t, 3); + + tc = uiTableAppendColumn(t, "Buttons"); + uiTableColumnAppendCheckboxPart(tc, 7, 0); + uiTableColumnAppendButtonPart(tc, 6, 1); + + tc = uiTableAppendColumn(t, "Progress Bar"); + uiTableColumnAppendProgressBarPart(tc, 8, 0); + + return page16; +} diff --git a/uitable.h b/uitable.h new file mode 100644 index 00000000..a54398c5 --- /dev/null +++ b/uitable.h @@ -0,0 +1,65 @@ +// 20 june 2016 +// kept in a separate file for now + +typedef struct uiImage uiImage; + +// TODO use const void * for const correctness +_UI_EXTERN uiImage *uiNewImage(double width, double height); +_UI_EXTERN void uiFreeImage(uiImage *i); +_UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride); + +typedef struct uiTableModel uiTableModel; +typedef struct uiTableModelHandler uiTableModelHandler; + +// TODO actually validate these +_UI_ENUM(uiTableModelColumnType) { + uiTableModelColumnString, + uiTableModelColumnImage, + uiTableModelColumnInt, + uiTableModelColumnColor, +}; + +// TODO validate ranges; validate types on each getter/setter call (? table columns only?) +struct uiTableModelHandler { + int (*NumColumns)(uiTableModelHandler *, uiTableModel *); + uiTableModelColumnType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int); + int (*NumRows)(uiTableModelHandler *, uiTableModel *); + void *(*CellValue)(uiTableModelHandler *, uiTableModel *, int, int); + void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const void *); +}; + +_UI_EXTERN void *uiTableModelStrdup(const char *str); +// TODO rename the strdup one to this too +_UI_EXTERN void *uiTableModelGiveColor(double r, double g, double b, double a); +_UI_EXTERN void *uiTableModelGiveInt(int i); +// TODO TakeString +// TODO add const +_UI_EXTERN int uiTableModelTakeInt(void *v); + +_UI_EXTERN uiTableModel *uiNewTableModel(uiTableModelHandler *mh); +_UI_EXTERN void uiFreeTableModel(uiTableModel *m); +_UI_EXTERN void uiTableModelRowInserted(uiTableModel *m, int newIndex); +_UI_EXTERN void uiTableModelRowChanged(uiTableModel *m, int index); +_UI_EXTERN void uiTableModelRowDeleted(uiTableModel *m, int oldIndex); +// TODO reordering/moving + +typedef struct uiTableColumn uiTableColumn; + +_UI_EXTERN void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand); +// TODO images shouldn't expand... +_UI_EXTERN void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand); +_UI_EXTERN void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand); +// TODO should these have labels? +_UI_EXTERN void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand); +_UI_EXTERN void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand); +// TODO Editable? +_UI_EXTERN void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable); +_UI_EXTERN void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn); + +typedef struct uiTable uiTable; +#define uiTable(this) ((uiTable *) (this)) +_UI_EXTERN uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name); +_UI_EXTERN uiTableColumn *uiTableAppendTextColumn(uiTable *t, const char *name, int modelColumn); +// TODO getter? +_UI_EXTERN void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn); +_UI_EXTERN uiTable *uiNewTable(uiTableModel *model); diff --git a/unix/table.c b/unix/table.c new file mode 100644 index 00000000..a11844c2 --- /dev/null +++ b/unix/table.c @@ -0,0 +1,642 @@ +// 26 june 2016 +#include "uipriv_unix.h" + +#define uiTableModelType (uiTableModel_get_type()) +#define uiTableModel(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uiTableModelType, uiTableModel)) +#define isuiTableModel(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uiTableModelType)) +#define uiTableModelClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uiTableModelType, uiTableModelClass)) +#define isuiTableModelClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uiTableModel)) +#define getuiTableModelClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uiTableModelType, uiTableModelClass)) + +typedef struct uiTableModelClass uiTableModelClass; + +struct uiTableModel { + GObject parent_instance; + uiTableModelHandler *mh; +}; + +struct uiTableModelClass { + GObjectClass parent_class; +}; + +static void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface); + +G_DEFINE_TYPE_WITH_CODE(uiTableModel, uiTableModel, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, uiTableModel_gtk_tree_model_interface_init)) + +static void uiTableModel_init(uiTableModel *m) +{ + // nothing to do +} + +static void uiTableModel_dispose(GObject *obj) +{ + G_OBJECT_CLASS(uiTableModel_parent_class)->dispose(obj); +} + +static void uiTableModel_finalize(GObject *obj) +{ + G_OBJECT_CLASS(uiTableModel_parent_class)->finalize(obj); +} + +static GtkTreeModelFlags uiTableModel_get_flags(GtkTreeModel *mm) +{ + return GTK_TREE_MODEL_LIST_ONLY; +} + +static gint uiTableModel_get_n_columns(GtkTreeModel *mm) +{ + uiTableModel *m = uiTableModel(mm); + + return (*(m->mh->NumColumns))(m->mh, m); +} + +static GType uiTableModel_get_column_type(GtkTreeModel *mm, gint index) +{ + uiTableModel *m = uiTableModel(mm); + + switch ((*(m->mh->ColumnType))(m->mh, m, index)) { + case uiTableModelColumnString: + return G_TYPE_STRING; + case uiTableModelColumnImage: + return G_TYPE_POINTER; + case uiTableModelColumnInt: + return G_TYPE_INT; + case uiTableModelColumnColor: + return GDK_TYPE_RGBA; + } + // TODO + return G_TYPE_INVALID; +} + +#define STAMP_GOOD 0x1234 +#define STAMP_BAD 0x5678 + +static gboolean uiTableModel_get_iter(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreePath *path) +{ + uiTableModel *m = uiTableModel(mm); + gint row; + + if (gtk_tree_path_get_depth(path) != 1) + goto bad; + row = gtk_tree_path_get_indices(path)[0]; + if (row < 0) + goto bad; + if (row >= (*(m->mh->NumRows))(m->mh, m)) + goto bad; + iter->stamp = STAMP_GOOD; + iter->user_data = GINT_TO_POINTER(row); + return TRUE; +bad: + iter->stamp = STAMP_BAD; + return FALSE; +} + +// GtkListStore returns NULL on error; let's do that too +static GtkTreePath *uiTableModel_get_path(GtkTreeModel *mm, GtkTreeIter *iter) +{ + gint row; + + if (iter->stamp != STAMP_GOOD) + return NULL; + row = GPOINTER_TO_INT(iter->user_data); + return gtk_tree_path_new_from_indices(row, -1); +} + +// GtkListStore leaves value empty on failure; let's do the same +static void uiTableModel_get_value(GtkTreeModel *mm, GtkTreeIter *iter, gint column, GValue *value) +{ + uiTableModel *m = uiTableModel(mm); + gint row; + void *data; + + if (iter->stamp != STAMP_GOOD) + return; + row = GPOINTER_TO_INT(iter->user_data); + data = (*(m->mh->CellValue))(m->mh, m, row, column); + switch ((*(m->mh->ColumnType))(m->mh, m, column)) { + case uiTableModelColumnString: + g_value_init(value, G_TYPE_STRING); + g_value_take_string(value, (char *) data); + return; + case uiTableModelColumnImage: + g_value_init(value, G_TYPE_POINTER); + g_value_set_pointer(value, data); + return; + case uiTableModelColumnInt: + g_value_init(value, G_TYPE_INT); + g_value_set_int(value, uiTableModelTakeInt(data)); + return; + case uiTableModelColumnColor: + g_value_init(value, GDK_TYPE_RGBA); + g_value_take_boxed(value, data); + return; + } + // TODO +} + +static gboolean uiTableModel_iter_next(GtkTreeModel *mm, GtkTreeIter *iter) +{ + uiTableModel *m = uiTableModel(mm); + gint row; + + if (iter->stamp != STAMP_GOOD) + return FALSE; + row = GPOINTER_TO_INT(iter->user_data); + row++; + if (row >= (*(m->mh->NumRows))(m->mh, m)) { + iter->stamp = STAMP_BAD; + return FALSE; + } + iter->user_data = GINT_TO_POINTER(row); + return TRUE; +} + +static gboolean uiTableModel_iter_previous(GtkTreeModel *mm, GtkTreeIter *iter) +{ + gint row; + + if (iter->stamp != STAMP_GOOD) + return FALSE; + row = GPOINTER_TO_INT(iter->user_data); + row--; + if (row < 0) { + iter->stamp = STAMP_BAD; + return FALSE; + } + iter->user_data = GINT_TO_POINTER(row); + return TRUE; +} + +static gboolean uiTableModel_iter_children(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent) +{ + return gtk_tree_model_iter_nth_child(mm, iter, parent, 0); +} + +static gboolean uiTableModel_iter_has_child(GtkTreeModel *mm, GtkTreeIter *iter) +{ + return FALSE; +} + +static gint uiTableModel_iter_n_children(GtkTreeModel *mm, GtkTreeIter *iter) +{ + uiTableModel *m = uiTableModel(mm); + + if (iter != NULL) + return 0; + return (*(m->mh->NumRows))(m->mh, m); +} + +static gboolean uiTableModel_iter_nth_child(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent, gint n) +{ + uiTableModel *m = uiTableModel(mm); + + if (iter->stamp != STAMP_GOOD) + return FALSE; + if (parent != NULL) + goto bad; + if (n < 0) + goto bad; + if (n >= (*(m->mh->NumRows))(m->mh, m)) + goto bad; + iter->stamp = STAMP_GOOD; + iter->user_data = GINT_TO_POINTER(n); + return TRUE; +bad: + iter->stamp = STAMP_BAD; + return FALSE; +} + +gboolean uiTableModel_iter_parent(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *child) +{ + iter->stamp = STAMP_BAD; + return FALSE; +} + +static void uiTableModel_class_init(uiTableModelClass *class) +{ + G_OBJECT_CLASS(class)->dispose = uiTableModel_dispose; + G_OBJECT_CLASS(class)->finalize = uiTableModel_finalize; +} + +static void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface) +{ + iface->get_flags = uiTableModel_get_flags; + iface->get_n_columns = uiTableModel_get_n_columns; + iface->get_column_type = uiTableModel_get_column_type; + iface->get_iter = uiTableModel_get_iter; + iface->get_path = uiTableModel_get_path; + iface->get_value = uiTableModel_get_value; + iface->iter_next = uiTableModel_iter_next; + iface->iter_previous = uiTableModel_iter_previous; + iface->iter_children = uiTableModel_iter_children; + iface->iter_has_child = uiTableModel_iter_has_child; + iface->iter_n_children = uiTableModel_iter_n_children; + iface->iter_nth_child = uiTableModel_iter_nth_child; + iface->iter_parent = uiTableModel_iter_parent; + // don't specify ref_node() or unref_node() +} + +void *uiTableModelStrdup(const char *str) +{ + return g_strdup(str); +} + +void *uiTableModelGiveColor(double r, double g, double b, double a) +{ + GdkRGBA rgba; + + rgba.red = r; + rgba.green = g; + rgba.blue = b; + rgba.alpha = a; + return gdk_rgba_copy(&rgba); +} + +uiTableModel *uiNewTableModel(uiTableModelHandler *mh) +{ + uiTableModel *m; + + m = uiTableModel(g_object_new(uiTableModelType, NULL)); + m->mh = mh; + return m; +} + +void uiFreeTableModel(uiTableModel *m) +{ + g_object_unref(m); +} + +void uiTableModelRowInserted(uiTableModel *m, int newIndex) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_indices(newIndex, -1); + iter.stamp = STAMP_GOOD; + iter.user_data = GINT_TO_POINTER(newIndex); + gtk_tree_model_row_inserted(GTK_TREE_MODEL(m), path, &iter); + gtk_tree_path_free(path); +} + +void uiTableModelRowChanged(uiTableModel *m, int index) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_indices(index, -1); + iter.stamp = STAMP_GOOD; + iter.user_data = GINT_TO_POINTER(index); + gtk_tree_model_row_changed(GTK_TREE_MODEL(m), path, &iter); + gtk_tree_path_free(path); +} + +void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) +{ + GtkTreePath *path; + + path = gtk_tree_path_new_from_indices(oldIndex, -1); + gtk_tree_model_row_deleted(GTK_TREE_MODEL(m), path); + gtk_tree_path_free(path); +} + +enum { + partText, + partImage, + partButton, + partCheckbox, + partProgressBar, +}; + +struct tablePart { + int type; + int textColumn; + int imageColumn; + int valueColumn; + int colorColumn; + GtkCellRenderer *r; + uiTable *tv; // for pixbufs and background color +}; + +struct uiTableColumn { + GtkTreeViewColumn *c; + uiTable *tv; // for pixbufs and background color + GPtrArray *parts; +}; + +struct uiTable { + uiUnixControl c; + GtkWidget *widget; + GtkContainer *scontainer; + GtkScrolledWindow *sw; + GtkWidget *treeWidget; + GtkTreeView *tv; + GPtrArray *columns; + uiTableModel *model; + int backgroundColumn; +}; + +// use the same size as GtkFileChooserWidget's treeview +// TODO refresh when icon theme changes +// TODO doesn't work when scaled +// TODO is this even necessary? +static void setImageSize(GtkCellRenderer *r) +{ + gint size; + gint width, height; + gint xpad, ypad; + + size = 16; // fallback used by GtkFileChooserWidget + if (gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height) != FALSE) + size = MAX(width, height); + gtk_cell_renderer_get_padding(r, &xpad, &ypad); + gtk_cell_renderer_set_fixed_size(r, + 2 * xpad + size, + 2 * ypad + size); +} + +static void applyColor(GtkTreeModel *mm, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop, const char *propSet) +{ + GValue value = G_VALUE_INIT; + GdkRGBA *rgba; + + gtk_tree_model_get_value(mm, iter, modelColumn, &value); + rgba = (GdkRGBA *) g_value_get_boxed(&value); + if (rgba != NULL) + g_object_set(r, prop, rgba, NULL); + else + g_object_set(r, propSet, FALSE, NULL); + g_value_unset(&value); +} + +static void dataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *mm, GtkTreeIter *iter, gpointer data) +{ + struct tablePart *part = (struct tablePart *) data; + GValue value = G_VALUE_INIT; + const gchar *str; + uiImage *img; + int pval; + + switch (part->type) { + case partText: + gtk_tree_model_get_value(mm, iter, part->textColumn, &value); + str = g_value_get_string(&value); + g_object_set(r, "text", str, NULL); + if (part->colorColumn != -1) + applyColor(mm, iter, + part->colorColumn, + r, "foreground-rgba", "foreground-set"); + break; + case partImage: +//TODO setImageSize(r); + gtk_tree_model_get_value(mm, iter, part->imageColumn, &value); + img = (uiImage *) g_value_get_pointer(&value); + g_object_set(r, "surface", + imageAppropriateSurface(img, part->tv->treeWidget), + NULL); + break; + case partButton: + gtk_tree_model_get_value(mm, iter, part->textColumn, &value); + str = g_value_get_string(&value); + g_object_set(r, "text", str, NULL); + break; + case partCheckbox: + gtk_tree_model_get_value(mm, iter, part->valueColumn, &value); + g_object_set(r, "active", g_value_get_int(&value) != 0, NULL); + break; + case partProgressBar: + gtk_tree_model_get_value(mm, iter, part->valueColumn, &value); + pval = g_value_get_int(&value); + if (pval == -1) { + // TODO + } else + g_object_set(r, + "pulse", -1, + "value", pval, + NULL); + break; + } + g_value_unset(&value); + + if (part->tv->backgroundColumn != -1) + applyColor(mm, iter, + part->tv->backgroundColumn, + r, "cell-background-rgba", "cell-background-set"); +} + +static void onEdited(struct tablePart *part, int column, const char *pathstr, const void *data) +{ + GtkTreePath *path; + int row; + uiTableModel *m; + + path = gtk_tree_path_new_from_string(pathstr); + row = gtk_tree_path_get_indices(path)[0]; + gtk_tree_path_free(path); + m = part->tv->model; + (*(m->mh->SetCellValue))(m->mh, m, row, column, data); + // and update + uiTableModelRowChanged(m, row); +} + +static void appendPart(uiTableColumn *c, struct tablePart *part, GtkCellRenderer *r, int expand) +{ + part->r = r; + gtk_tree_view_column_pack_start(c->c, part->r, expand != 0); + gtk_tree_view_column_set_cell_data_func(c->c, part->r, dataFunc, part, NULL); + g_ptr_array_add(c->parts, part); +} + +static void textEdited(GtkCellRendererText *renderer, gchar *path, gchar *newText, gpointer data) +{ + struct tablePart *part = (struct tablePart *) data; + + onEdited(part, part->textColumn, path, newText); +} + +void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + GtkCellRenderer *r; + + part = uiNew(struct tablePart); + part->type = partText; + part->textColumn = modelColumn; + part->tv = c->tv; + part->colorColumn = -1; + + r = gtk_cell_renderer_text_new(); + g_object_set(r, "editable", FALSE, NULL); + g_signal_connect(r, "edited", G_CALLBACK(textEdited), part); + + appendPart(c, part, r, expand); +} + +void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + + part = uiNew(struct tablePart); + part->type = partImage; + part->imageColumn = modelColumn; + part->tv = c->tv; + appendPart(c, part, + gtk_cell_renderer_pixbuf_new(), + expand); +} + +// TODO wrong type here +static void buttonClicked(GtkCellRenderer *r, gchar *pathstr, gpointer data) +{ + struct tablePart *part = (struct tablePart *) data; + + onEdited(part, part->textColumn, pathstr, NULL); +} + +void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + GtkCellRenderer *r; + + part = uiNew(struct tablePart); + part->type = partButton; + part->textColumn = modelColumn; + part->tv = c->tv; + + r = newCellRendererButton(); + g_object_set(r, "sensitive", TRUE, NULL); // editable by default + g_signal_connect(r, "clicked", G_CALLBACK(buttonClicked), part); + + appendPart(c, part, r, expand); +} + +// yes, we need to do all this twice :| +static void checkboxToggled(GtkCellRendererToggle *r, gchar *pathstr, gpointer data) +{ + struct tablePart *part = (struct tablePart *) data; + GtkTreePath *path; + int row; + uiTableModel *m; + void *value; + int intval; + + path = gtk_tree_path_new_from_string(pathstr); + row = gtk_tree_path_get_indices(path)[0]; + gtk_tree_path_free(path); + m = part->tv->model; + value = (*(m->mh->CellValue))(m->mh, m, row, part->valueColumn); + intval = !uiTableModelTakeInt(value); + onEdited(part, part->valueColumn, pathstr, uiTableModelGiveInt(intval)); +} + +void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + GtkCellRenderer *r; + + part = uiNew(struct tablePart); + part->type = partCheckbox; + part->valueColumn = modelColumn; + part->tv = c->tv; + + r = gtk_cell_renderer_toggle_new(); + g_object_set(r, "sensitive", TRUE, NULL); // editable by default + g_signal_connect(r, "toggled", G_CALLBACK(checkboxToggled), part); + + appendPart(c, part, r, expand); +} + +void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + + part = uiNew(struct tablePart); + part->type = partProgressBar; + part->valueColumn = modelColumn; + part->tv = c->tv; + appendPart(c, part, + gtk_cell_renderer_progress_new(), + expand); +} + +void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) +{ + struct tablePart *p; + + p = (struct tablePart *) g_ptr_array_index(c->parts, part); + switch (p->type) { + case partImage: + case partProgressBar: + return; + case partButton: + case partCheckbox: + g_object_set(p->r, "sensitive", editable != 0, NULL); + return; + } + g_object_set(p->r, "editable", editable != 0, NULL); +} + +void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) +{ + struct tablePart *p; + + p = (struct tablePart *) g_ptr_array_index(c->parts, part); + p->colorColumn = modelColumn; + // TODO refresh table +} + +uiUnixControlAllDefaultsExceptDestroy(uiTable) + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + + // TODO + g_object_unref(t->widget); + uiFreeControl(uiControl(t)); +} + +uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) +{ + uiTableColumn *c; + + c = uiNew(uiTableColumn); + c->c = gtk_tree_view_column_new(); + gtk_tree_view_column_set_resizable(c->c, TRUE); + gtk_tree_view_column_set_title(c->c, name); + gtk_tree_view_append_column(t->tv, c->c); + c->tv = t; // TODO rename field to t, cascade + c->parts = g_ptr_array_new(); + return c; +} + +void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) +{ + t->backgroundColumn = modelColumn; + // TODO refresh table +} + +uiTable *uiNewTable(uiTableModel *model) +{ + uiTable *t; + + uiUnixNewControl(uiTable, t); + + t->model = model; + t->backgroundColumn = -1; + + t->widget = gtk_scrolled_window_new(NULL, NULL); + t->scontainer = GTK_CONTAINER(t->widget); + t->sw = GTK_SCROLLED_WINDOW(t->widget); + gtk_scrolled_window_set_shadow_type(t->sw, GTK_SHADOW_IN); + + t->treeWidget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(t->model)); + t->tv = GTK_TREE_VIEW(t->treeWidget); + // TODO set up t->tv + + gtk_container_add(t->scontainer, t->treeWidget); + // and make the tree view visible; only the scrolled window's visibility is controlled by libui + gtk_widget_show(t->treeWidget); + + return t; +} diff --git a/windows/tablepart.cpp b/windows/tablepart.cpp new file mode 100644 index 00000000..c31932d7 --- /dev/null +++ b/windows/tablepart.cpp @@ -0,0 +1,95 @@ +// 30 june 2016 +// TODO includes + +typedef struct tablePartDrawParams tablePartDrawParams; +typedef struct tablePartMinimumSizeParams tableDrawMinimumSizeParams; +typedef struct tablePartEditingParams tablePartEditingParams; + +struct tablePartDrawParams { + HWND hwnd; + HDC hdc; + RECT *r; + bool selected; + bool focused; + bool hovering; + uiTableModel *model; + int row; +}; + +struct tablePartMinimumSizeParams { + HWND hwnd; + HDC hdc; + uiTableModel *model; + int row; +}; + +enum { + partEventDoNothing, + partEventRedraw, + partEventEdit, +}; + +struct tablePartEditingParams { + HWND newHWND; +}; + +enum { + partEditContinue, + partEditDone, +}; + +class tablePart { +public: + // needed so we can delete a tablePart + virtual ~tablePart() {} + + virtual HRESULT Draw(tablePartDrawParams *p) = 0; + virtual HRESULT MinimumSize(tablePartMinimumSizeParams *p, int *width, int *height) = 0; + + // returns a partEvent constant + virtual int MouseMove(int x, int y, RECT *cell) = 0; + virtual int MouseLeave(void) = 0; + virtual int LButtonDown(int x, int y, int count, RECT *cell) = 0; + virtual int LButtonUp(int x, int y, RECT *cell) = 0; + virtual int CaptureBroken(void) = 0; + virtual int KeyDown(void) = 0; + virtual int KeyUp(void) = 0; + + // editing; all optional + virtual int StartEditing(tablePartEditingParams *p) { return editDone; } + virtual int EditChildWM_COMMAND(WORD code, LRESULT *lr) { return editDone; } + virtual void FinishEditing(uiTableModel *model, int row) {} + virtual void CancelEditing(void) {} + + // TODO tooltips + // TODO timers and animations + + // optional methods + virtual void SetTextColorColumn(int col) {} + virtual void SetEditable(bool editable) {} +}; + +class tablePartText : public tablePart { + int textColumn; + int colorColumn; +public: + tablePartText(int tc) + { + this->textColumn = tc; + this->colorColumn = -1; + } + + // TODO figure out vertical alignment + virtual HRESULT Draw(tablePartDrawParams *p) + { + } + + virtual HRESULT MinimumSize(tablePartMinimumSizeParams *p, int *width, int *height) + { + } +}; + +tablePart *newTablePartText(int tc) +{ + return new tablePartText(tc); +} diff --git a/windows/tableutil.cpp b/windows/tableutil.cpp new file mode 100644 index 00000000..e6e5bb14 --- /dev/null +++ b/windows/tableutil.cpp @@ -0,0 +1,32 @@ +// 1 july 2016 +// TODO includes + +void tableGetClientRect(HWND hwnd, RECT *r) +{ + if (GetClientRect(hwnd, r) == 0) { + r->left = 0; + r->top = 0; + r->right = 0; + r->bottom = 0; + } +} + +void tableGetWindowRect(HWND hwnd, RECT *r) +{ + if (GetWindowRect(hwnd, r) == 0) { + r->left = 0; + r->top = 0; + r->right = 0; + r->bottom = 0; + } +} + +void tableGetTextExtentPoint32W(HDC dc, const WSTR *str, int len, SIZE *s) +{ + if (len == -1) + len = wcslen(str); + if (GetTextExtentPoint32W(dc, str, len, s) == 0) { + s->cx = 0; + s->cy = 0; + } +} From 18b103c988bb057d04c6b0bd930dad315a87d039 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 27 Nov 2016 17:44:52 -0500 Subject: [PATCH 002/166] Fully reintegrated the table code. --- common/CMakeLists.txt | 1 + darwin/CMakeLists.txt | 1 + test/CMakeLists.txt | 2 ++ test/main.c | 4 ++-- test/test.h | 6 ++++++ ui.h | 3 +++ unix/CMakeLists.txt | 1 + 7 files changed, 16 insertions(+), 2 deletions(-) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 91d79493..7f704032 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -6,6 +6,7 @@ list(APPEND _LIBUI_SOURCES common/debug.c common/matrix.c common/shouldquit.c + common/table.c common/userbugs.c ) set(_LIBUI_SOURCES ${_LIBUI_SOURCES} PARENT_SCOPE) diff --git a/darwin/CMakeLists.txt b/darwin/CMakeLists.txt index dbef5d43..f0c936b5 100644 --- a/darwin/CMakeLists.txt +++ b/darwin/CMakeLists.txt @@ -35,6 +35,7 @@ list(APPEND _LIBUI_SOURCES darwin/spinbox.m darwin/stddialogs.m darwin/tab.m + darwin/table.m darwin/text.m darwin/util.m darwin/window.m diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e4924bb3..b753a7d4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,6 +6,7 @@ endif() _add_exec(tester drawtests.c + images.c main.c menus.c page1.c @@ -26,6 +27,7 @@ _add_exec(tester page13.c page14.c page15.c + page16.c spaced.c ${_TEST_RESOURCES_RC} ) diff --git a/test/main.c b/test/main.c index 18774dcd..f33f30ab 100644 --- a/test/main.c +++ b/test/main.c @@ -159,8 +159,8 @@ int main(int argc, char *argv[]) innerTab = newTab(); uiTabAppend(outerTab, "Pages 16-?", uiControl(innerTab)); -// page16 = makePage16(); -// uiTabAppend(innerTab, "Page 16", uiControl(page16)); + page16 = makePage16(); + uiTabAppend(innerTab, "Page 16", uiControl(page16)); if (startspaced) setSpaced(1); diff --git a/test/test.h b/test/test.h index 66b1baa7..224ef667 100644 --- a/test/test.h +++ b/test/test.h @@ -89,3 +89,9 @@ extern uiTab *makePage14(void); // page15.c extern uiBox *makePage15(uiWindow *); + +// page16.c +extern uiBox *makePage16(void); + +// images.c +extern void appendImageNamed(uiImage *img, const char *name); diff --git a/ui.h b/ui.h index 5a2069e8..5852bf6d 100644 --- a/ui.h +++ b/ui.h @@ -684,6 +684,9 @@ _UI_EXTERN int uiGridPadded(uiGrid *g); _UI_EXTERN void uiGridSetPadded(uiGrid *g, int padded); _UI_EXTERN uiGrid *uiNewGrid(void); +// TODO merge +#include "uitable.h" + #ifdef __cplusplus } #endif diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 9300bcb7..e967f29a 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -40,6 +40,7 @@ list(APPEND _LIBUI_SOURCES unix/spinbox.c unix/stddialogs.c unix/tab.c + unix/table.c unix/text.c unix/util.c unix/window.c From ba2e9154f79c39d1a7e19e32e82be0c57196d8d8 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 3 Jun 2017 17:59:50 -0400 Subject: [PATCH 003/166] Some notes and failed bugfixes on NSProgressIndicators in tables. --- darwin/table.m | 1 + 1 file changed, 1 insertion(+) diff --git a/darwin/table.m b/darwin/table.m index 9e578c85..5e29de26 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -9,6 +9,7 @@ // - changing a part property does not refresh views // - is the Y position of checkbox cells correct? // - progressbars appear ABOVE the table header +// - threaded animation (which was known to have some issues: https://stackoverflow.com/questions/18142801/nstableview-nsprogressindicator-flickering-issues) is NOT the cause; happens regardless of setting (or does the setting not stick? TODO) // LONGTERM // - reuse row views instead of creating a new one each time From d4414551123f6ef1430ac9431bb4f9f9bccd1973 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 18 Apr 2018 10:06:43 -0400 Subject: [PATCH 004/166] Updated common uipriv names on OS X. --- darwin/table.m | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/darwin/table.m b/darwin/table.m index 5e29de26..43822dfc 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -186,7 +186,7 @@ done: break; } if (row == -1) - implbug("table model action triggered on view with no associated table"); + uiprivImplBug("table model action triggered on view with no associated table"); if ([view isKindOfClass:[NSTextField class]]) data = [[((NSTextField *) view) stringValue] UTF8String]; @@ -200,7 +200,7 @@ done: if(1); else data = NULL; } else - implbug("table model editing action triggered on non-editable view"); + uiprivImplBug("table model editing action triggered on non-editable view"); // note the use of [view tag] — we need the model column, which we store in the view tag for relevant views below (*(m->mh->SetCellValue))(m->mh, m, @@ -240,7 +240,7 @@ if(1); else case partText: data = (*(m->mh->CellValue))(m->mh, m, row, self.textColumn); str = toNSString((char *) data); - uiFree(data); + uiprivFree(data); tf = newLabel(str); // TODO set wrap and ellipsize modes? if (self.textColorColumn != -1) { @@ -364,7 +364,7 @@ void *uiTableModelStrdup(const char *str) // TODO don't we have this already? char *dup; - dup = (char *) uiAlloc((strlen(str) + 1) * sizeof (char), "char[]"); + dup = (char *) uiprivAlloc((strlen(str) + 1) * sizeof (char), "char[]"); strcpy(dup, str); return dup; } @@ -373,7 +373,7 @@ uiTableModel *uiNewTableModel(uiTableModelHandler *mh) { uiTableModel *m; - m = uiNew(uiTableModel); + m = uiprivNew(uiTableModel); m->mh = mh; m->m = [[tableModel alloc] initWithModel:m]; m->tables = [NSMutableArray new]; @@ -388,10 +388,10 @@ void *uiTableModelGiveColor(double r, double g, double b, double a) void uiFreeTableModel(uiTableModel *m) { if ([m->tables count] != 0) - userbug("You cannot free a uiTableModel while uiTables are using it."); + uiprivUserBug("You cannot free a uiTableModel while uiTables are using it."); [m->tables release]; [m->m release]; - uiFree(m); + uiprivFree(m); } void uiTableModelRowInserted(uiTableModel *m, int newIndex) @@ -519,7 +519,7 @@ uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) { uiTableColumn *c; - c = uiNew(uiTableColumn); + c = uiprivNew(uiTableColumn); c->c = [[tableColumn alloc] initWithIdentifier:@""]; c->c.libui_col = c; // via Interface Builder From 6507a0d3a1baa38925abb941cb0d630fff4e5ddf Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 18 Apr 2018 10:17:41 -0400 Subject: [PATCH 005/166] Updated common uipriv names on Unix. --- unix/table.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/unix/table.c b/unix/table.c index a11844c2..ac8b03d3 100644 --- a/unix/table.c +++ b/unix/table.c @@ -459,7 +459,7 @@ void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) struct tablePart *part; GtkCellRenderer *r; - part = uiNew(struct tablePart); + part = uiprivNew(struct tablePart); part->type = partText; part->textColumn = modelColumn; part->tv = c->tv; @@ -476,7 +476,7 @@ void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) { struct tablePart *part; - part = uiNew(struct tablePart); + part = uiprivNew(struct tablePart); part->type = partImage; part->imageColumn = modelColumn; part->tv = c->tv; @@ -498,7 +498,7 @@ void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand struct tablePart *part; GtkCellRenderer *r; - part = uiNew(struct tablePart); + part = uiprivNew(struct tablePart); part->type = partButton; part->textColumn = modelColumn; part->tv = c->tv; @@ -534,7 +534,7 @@ void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expa struct tablePart *part; GtkCellRenderer *r; - part = uiNew(struct tablePart); + part = uiprivNew(struct tablePart); part->type = partCheckbox; part->valueColumn = modelColumn; part->tv = c->tv; @@ -550,7 +550,7 @@ void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int e { struct tablePart *part; - part = uiNew(struct tablePart); + part = uiprivNew(struct tablePart); part->type = partProgressBar; part->valueColumn = modelColumn; part->tv = c->tv; @@ -600,7 +600,7 @@ uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) { uiTableColumn *c; - c = uiNew(uiTableColumn); + c = uiprivNew(uiTableColumn); c->c = gtk_tree_view_column_new(); gtk_tree_view_column_set_resizable(c->c, TRUE); gtk_tree_view_column_set_title(c->c, name); From bd2f436d91670c2e505174034f2b2cade8c95c51 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 18 Apr 2018 10:20:55 -0400 Subject: [PATCH 006/166] Fixed uiImage redeclaration warnings on GTK+. --- unix/uipriv_unix.h | 1 - 1 file changed, 1 deletion(-) diff --git a/unix/uipriv_unix.h b/unix/uipriv_unix.h index 43ed144b..ba592a97 100644 --- a/unix/uipriv_unix.h +++ b/unix/uipriv_unix.h @@ -47,7 +47,6 @@ extern uiDrawContext *newContext(cairo_t *cr, GtkStyleContext *style); extern void freeContext(uiDrawContext *); // image.c -/*TODO remove this*/typedef struct uiImage uiImage; extern cairo_surface_t *imageAppropriateSurface(uiImage *i, GtkWidget *w); // cellrendererbutton.c From ab336e0e8b4e261d04981e76de265c2c3af0a015 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 18 Apr 2018 10:29:16 -0400 Subject: [PATCH 007/166] Fixed uiImage redeclaration warnings on OS X. --- darwin/uipriv_darwin.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h index 8b95e315..c221464f 100644 --- a/darwin/uipriv_darwin.h +++ b/darwin/uipriv_darwin.h @@ -19,8 +19,6 @@ #define NSAppKitVersionNumber10_9 1265 #endif -/*TODO remove this*/typedef struct uiImage uiImage; - // menu.m @interface menuManager : NSObject { struct mapTable *items; From dc98bc2c255efdf99d4c7c4e12f17457d4958445 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 6 May 2018 20:01:54 -0400 Subject: [PATCH 008/166] Avoid merge conflict in uipriv_darwin.h. --- darwin/uipriv_darwin.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h index c221464f..afd97691 100644 --- a/darwin/uipriv_darwin.h +++ b/darwin/uipriv_darwin.h @@ -19,6 +19,15 @@ #define NSAppKitVersionNumber10_9 1265 #endif +// map.m +extern struct mapTable *newMap(void); +extern void mapDestroy(struct mapTable *m); +extern void *mapGet(struct mapTable *m, void *key); +extern void mapSet(struct mapTable *m, void *key, void *value); +extern void mapDelete(struct mapTable *m, void *key); +extern void mapWalk(struct mapTable *m, void (*f)(void *key, void *value)); +extern void mapReset(struct mapTable *m); + // menu.m @interface menuManager : NSObject { struct mapTable *items; @@ -88,15 +97,6 @@ extern void singleChildConstraintsEstablish(struct singleChildConstraints *c, NS extern void singleChildConstraintsRemove(struct singleChildConstraints *c, NSView *cv); extern void singleChildConstraintsSetMargined(struct singleChildConstraints *c, int margined); -// map.m -extern struct mapTable *newMap(void); -extern void mapDestroy(struct mapTable *m); -extern void *mapGet(struct mapTable *m, void *key); -extern void mapSet(struct mapTable *m, void *key, void *value); -extern void mapDelete(struct mapTable *m, void *key); -extern void mapWalk(struct mapTable *m, void (*f)(void *key, void *value)); -extern void mapReset(struct mapTable *m); - // area.m extern int sendAreaEvents(NSEvent *); From 221e8731c0fa370ed7c986817a3f78953afb5f47 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 6 May 2018 21:26:51 -0400 Subject: [PATCH 009/166] Revert "Avoid merge conflict in uipriv_darwin.h." Nope, this confused git further This reverts commit dc98bc2c255efdf99d4c7c4e12f17457d4958445. --- darwin/uipriv_darwin.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h index afd97691..c221464f 100644 --- a/darwin/uipriv_darwin.h +++ b/darwin/uipriv_darwin.h @@ -19,15 +19,6 @@ #define NSAppKitVersionNumber10_9 1265 #endif -// map.m -extern struct mapTable *newMap(void); -extern void mapDestroy(struct mapTable *m); -extern void *mapGet(struct mapTable *m, void *key); -extern void mapSet(struct mapTable *m, void *key, void *value); -extern void mapDelete(struct mapTable *m, void *key); -extern void mapWalk(struct mapTable *m, void (*f)(void *key, void *value)); -extern void mapReset(struct mapTable *m); - // menu.m @interface menuManager : NSObject { struct mapTable *items; @@ -97,6 +88,15 @@ extern void singleChildConstraintsEstablish(struct singleChildConstraints *c, NS extern void singleChildConstraintsRemove(struct singleChildConstraints *c, NSView *cv); extern void singleChildConstraintsSetMargined(struct singleChildConstraints *c, int margined); +// map.m +extern struct mapTable *newMap(void); +extern void mapDestroy(struct mapTable *m); +extern void *mapGet(struct mapTable *m, void *key); +extern void mapSet(struct mapTable *m, void *key, void *value); +extern void mapDelete(struct mapTable *m, void *key); +extern void mapWalk(struct mapTable *m, void (*f)(void *key, void *value)); +extern void mapReset(struct mapTable *m); + // area.m extern int sendAreaEvents(NSEvent *); From 5ac579df35dda533bda110b3f4a3190f08aa310b Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 6 May 2018 22:13:03 -0400 Subject: [PATCH 010/166] Fixed table.m to line up with the changes on master that have since been merged in. --- darwin/table.m | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/darwin/table.m b/darwin/table.m index 43822dfc..66666863 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -63,7 +63,7 @@ struct uiTable { uiDarwinControl c; NSScrollView *sv; tableView *tv; - struct scrollViewData *d; + uiprivScrollViewData *d; int backgroundColumn; }; @@ -111,7 +111,7 @@ struct uiTable { [v addSubview:view]; // TODO set [v imageView] and [v textField] as appropriate? if (prev == nil) { // first view - [v addConstraint:mkConstraint(v, NSLayoutAttributeLeading, + [v addConstraint:uiprivMkConstraint(v, NSLayoutAttributeLeading, NSLayoutRelationEqual, view, NSLayoutAttributeLeading, 1, -xleft, @@ -119,14 +119,14 @@ struct uiTable { prev = view; continue; } - [v addConstraint:mkConstraint(prev, NSLayoutAttributeTrailing, + [v addConstraint:uiprivMkConstraint(prev, NSLayoutAttributeTrailing, NSLayoutRelationEqual, view, NSLayoutAttributeLeading, 1, -xmiddle, @"uiTableColumn middle horizontal constraint")]; prev = view; } - [v addConstraint:mkConstraint(prev, NSLayoutAttributeTrailing, + [v addConstraint:uiprivMkConstraint(prev, NSLayoutAttributeTrailing, NSLayoutRelationEqual, v, NSLayoutAttributeTrailing, 1, -xright, @@ -134,14 +134,14 @@ struct uiTable { // and vertically for (view in views) { - [v addConstraint:mkConstraint(view, NSLayoutAttributeCenterY, + [v addConstraint:uiprivMkConstraint(view, NSLayoutAttributeCenterY, NSLayoutRelationEqual, v, NSLayoutAttributeCenterY, 1, 0, @"uiTableColumn part vertical constraint")]; // TODO avoid the need for this hack if ([view isKindOfClass:[NSImageView class]]) - [v addConstraint:mkConstraint(view, NSLayoutAttributeTop, + [v addConstraint:uiprivMkConstraint(view, NSLayoutAttributeTop, NSLayoutRelationEqual, v, NSLayoutAttributeTop, 1, 0, @@ -239,9 +239,9 @@ if(1); else switch (self.type) { case partText: data = (*(m->mh->CellValue))(m->mh, m, row, self.textColumn); - str = toNSString((char *) data); + str = uiprivToNSString((char *) data); uiprivFree(data); - tf = newLabel(str); + tf = uiprivNewLabel(str); // TODO set wrap and ellipsize modes? if (self.textColorColumn != -1) { NSColor *color; @@ -262,13 +262,13 @@ if(1); else case partImage: data = (*(m->mh->CellValue))(m->mh, m, row, self.imageColumn); iv = [[NSImageView alloc] initWithFrame:NSZeroRect]; - [iv setImage:imageImage((uiImage *) data)]; + [iv setImage:uiprivImageNSImage((uiImage *) data)]; [iv setImageFrameStyle:NSImageFrameNone]; [iv setImageAlignment:NSImageAlignCenter]; [iv setImageScaling:NSImageScaleProportionallyDown]; [iv setAnimates:NO]; [iv setEditable:NO]; - [iv addConstraint:mkConstraint(iv, NSLayoutAttributeWidth, + [iv addConstraint:uiprivMkConstraint(iv, NSLayoutAttributeWidth, NSLayoutRelationEqual, iv, NSLayoutAttributeHeight, 1, 0, @@ -279,7 +279,7 @@ if(1); else case partButton: // TODO buttons get clipped data = (*(m->mh->CellValue))(m->mh, m, row, self.textColumn); - str = toNSString((char *) data); + str = uiprivToNSString((char *) data); b = [[NSButton alloc] initWithFrame:NSZeroRect]; [b setTitle:str]; [b setButtonType:NSMomentaryPushInButton]; @@ -525,7 +525,7 @@ uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) // via Interface Builder [c->c setResizingMask:(NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask)]; // 10.10 adds -[NSTableColumn setTitle:]; before then we have to do this - [[c->c headerCell] setStringValue:toNSString(name)]; + [[c->c headerCell] setStringValue:uiprivToNSString(name)]; // TODO is this sufficient? [[c->c headerCell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]]; c->parts = [NSMutableArray new]; @@ -541,7 +541,7 @@ void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) uiTable *uiNewTable(uiTableModel *model) { uiTable *t; - struct scrollViewCreateParams p; + uiprivScrollViewCreateParams p; uiDarwinNewControl(uiTable, t); @@ -565,7 +565,7 @@ uiTable *uiNewTable(uiTableModel *model) [t->tv setAllowsTypeSelect:YES]; // TODO floatsGroupRows — do we even allow group rows? - memset(&p, 0, sizeof (struct scrollViewCreateParams)); + memset(&p, 0, sizeof (uiprivScrollViewCreateParams)); p.DocumentView = t->tv; // this is what Interface Builder sets it to // TODO verify @@ -574,7 +574,7 @@ uiTable *uiNewTable(uiTableModel *model) p.Bordered = YES; p.HScroll = YES; p.VScroll = YES; - t->sv = mkScrollView(&p, &(t->d)); + t->sv = uiprivMkScrollView(&p, &(t->d)); t->backgroundColumn = -1; From 0b8e86e4f8fa0f4d6319ca6d5d2ff984ab1a471b Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 12 May 2018 13:59:22 -0400 Subject: [PATCH 011/166] Started applying new uipriv names to table.c. Let's let the compiler tell us what we missed. --- unix/table.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unix/table.c b/unix/table.c index ac8b03d3..d8cbceaf 100644 --- a/unix/table.c +++ b/unix/table.c @@ -392,7 +392,7 @@ static void dataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *mm, gtk_tree_model_get_value(mm, iter, part->imageColumn, &value); img = (uiImage *) g_value_get_pointer(&value); g_object_set(r, "surface", - imageAppropriateSurface(img, part->tv->treeWidget), + uiprivImageAppropriateSurface(img, part->tv->treeWidget), NULL); break; case partButton: @@ -503,7 +503,7 @@ void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand part->textColumn = modelColumn; part->tv = c->tv; - r = newCellRendererButton(); + r = uiprivNewCellRendererButton(); g_object_set(r, "sensitive", TRUE, NULL); // editable by default g_signal_connect(r, "clicked", G_CALLBACK(buttonClicked), part); From fc2ea17bb8ccc5f3d1a9375d5584d273aea3b449 Mon Sep 17 00:00:00 2001 From: Ben Campbell Date: Sat, 5 May 2018 22:45:01 +1200 Subject: [PATCH 012/166] Add minimal uiTable implementation for windows This uses the win32 common controls listview to implement uiTable. There are limitations: - It supports only a single TextPart per column. - ImagePart, CheckboxPart and ProgessBarPart are not implemented. - There is no support for cell coloring. - Cell editing is not implemented. Some of these will be very hard to support using the standard common control listview, and probably require an entire custom listview. --- test/page16.c | 2 + windows/CMakeLists.txt | 2 + windows/image.cpp | 31 +++++ windows/table.cpp | 277 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 312 insertions(+) create mode 100644 windows/image.cpp create mode 100644 windows/table.cpp diff --git a/test/page16.c b/test/page16.c index 80ac0139..3a5ac639 100644 --- a/test/page16.c +++ b/test/page16.c @@ -132,6 +132,8 @@ uiBox *makePage16(void) uiTableSetRowBackgroundColorModelColumn(t, 3); + uiTableAppendTextColumn(t, "Numbers", 8); + tc = uiTableAppendColumn(t, "Buttons"); uiTableColumnAppendCheckboxPart(tc, 7, 0); uiTableColumnAppendButtonPart(tc, 6, 1); diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 16beefa7..b871b529 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -34,6 +34,7 @@ list(APPEND _LIBUI_SOURCES windows/graphemes.cpp windows/grid.cpp windows/group.cpp + windows/image.cpp windows/init.cpp windows/label.cpp windows/main.cpp @@ -49,6 +50,7 @@ list(APPEND _LIBUI_SOURCES windows/spinbox.cpp windows/stddialogs.cpp windows/tab.cpp + windows/table.cpp windows/tabpage.cpp windows/text.cpp windows/utf16.cpp diff --git a/windows/image.cpp b/windows/image.cpp new file mode 100644 index 00000000..bba013b1 --- /dev/null +++ b/windows/image.cpp @@ -0,0 +1,31 @@ +#include "uipriv_windows.hpp" +// stubbed out windows image list implementation. +// Required for uiTable control, but windows implemenation +// doesn't currently have image support. + +struct uiImage { + double width; + double height; + // HIMAGELIST images; +}; + +uiImage *uiNewImage(double width, double height) +{ + uiImage *i; + + i = uiprivNew(uiImage); + i->width = width; + i->height = height; + return i; +} + +void uiFreeImage(uiImage *i) +{ + uiprivFree(i); +} + +void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride) +{ + // not implemented +} + diff --git a/windows/table.cpp b/windows/table.cpp new file mode 100644 index 00000000..5d2273af --- /dev/null +++ b/windows/table.cpp @@ -0,0 +1,277 @@ +#include "uipriv_windows.hpp" + +#include + +static void uiTableDestroy(uiControl *c); +static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height); +static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nm, LRESULT *lResult); + +struct uiTable; + +struct uiTableModel { + uiTableModelHandler *mh; + std::vector tables; +}; + +struct uiTableColumn { + uiTable *t; + WCHAR *name; + // don't really support parts (but this would part=>column mappings if we did) + int modelColumn; // -1 = none +}; + +struct uiTable { + uiWindowsControl c; + uiTableModel *model; + HWND hwnd; + std::vector columns; +}; + +void *uiTableModelStrdup(const char *str) +{ + return strdup(str); +} + +void *uiTableModelGiveColor(double r, double g, double b, double a) +{ + return 0; // not implemented +} + +uiTableModel *uiNewTableModel(uiTableModelHandler *mh) +{ + uiTableModel *m; + + m = new uiTableModel(); + m->mh = mh; + return m; +} + + +void uiFreeTableModel(uiTableModel *m) +{ + delete m; +} + +void uiTableModelRowInserted(uiTableModel *m, int newIndex) +{ + LVITEM item; + + item.mask = 0; + item.iItem = newIndex; + item.iSubItem = 0; //? + for (auto t : m->tables) { + ListView_InsertItem( t->hwnd, &item ); + } +} + +void uiTableModelRowChanged(uiTableModel *m, int index) +{ + for (auto t : m->tables) { + ListView_Update( t->hwnd, index ); + } +} + +void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) +{ + for (auto t : m->tables) { + ListView_DeleteItem( t->hwnd, oldIndex ); + } +} + +void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) +{ + uiTable *t = c->t; + int lvIndex = 0; + + if (c->modelColumn >=0) { + return; // multiple parts not implemented + } + c->modelColumn = modelColumn; + + // work out appropriate listview index for the column + for (auto candidate : t->columns) { + if (candidate == c) { + break; + } + if (candidate->modelColumn >= 0) { + ++lvIndex; + } + } + + LV_COLUMN lvc; + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; /* | LVCF_SUBITEM; */ + lvc.fmt = LVCFMT_LEFT; + lvc.cx = 120; // TODO + lvc.pszText = c->name; + ListView_InsertColumn(c->t->hwnd, lvIndex, &lvc); +} + +void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) +{ + // not implemented +} + +void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand) +{ + // not implemented +} + +void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) +{ + // not implemented +} + +void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand) +{ + // not implemented +} + +void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) +{ + // TODO +} + +void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) +{ + // not implemented +} + +// uiTable implementation + +uiWindowsControlAllDefaultsExceptDestroy(uiTable) + +uiTable *uiNewTable(uiTableModel *model) +{ + uiTable *t; + int winStyle = WS_CHILD | LVS_AUTOARRANGE | LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL; + + uiWindowsNewControl(uiTable, t); + new(&t->columns) std::vector(); // (initialising in place) + t->model = model; + t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, + WC_LISTVIEW, + L"", + winStyle, + hInstance, + NULL, + TRUE); + model->tables.push_back(t); + uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t)); + ListView_SetExtendedListViewStyle(t->hwnd, LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP); + // TODO: try LVS_EX_AUTOSIZECOLUMNS + int n = (*(model->mh->NumRows))(model->mh, model); + ListView_SetItemCountEx(t->hwnd, n, 0); + return t; +} + +uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) +{ + uiTableColumn *c = uiprivNew(uiTableColumn); + c->name = toUTF16(name); + c->t = t; + c->modelColumn = -1; // -1 = unassigned + // we defer the actual ListView_InsertColumn call until a part is added... + t->columns.push_back(c); + return c; +} + +void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) +{ + // not implemented +} + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + uiTableModel *model = t->model; + std::vector::iterator it; + + uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); + uiWindowsEnsureDestroyWindow(t->hwnd); + // detach table from model + for (it = model->tables.begin(); it != model->tables.end(); ++it) { + if (*it == t) { + model->tables.erase(it); + break; + } + } + // free the columns + for (auto col: t->columns) { + uiprivFree(col->name); + uiprivFree(col); + } + t->columns.~vector(); // (created with placement new, so just call dtor directly) + uiFreeControl(uiControl(t)); +} + +static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height) +{ + uiTable *t = uiTable(c); + uiWindowsSizing sizing; + int x, y; + + // suggested listview sizing from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing: + // "columns widths that avoid truncated data x an integral number of items" + // Don't think that'll cut it when some cells have overlong data (eg + // stupidly long URLs). So for now, just hardcode a minimum: + + x = 107; // in line with other controls + y = 14*3; // header + 2 lines (roughly) + uiWindowsGetSizing(t->hwnd, &sizing); + uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); + *width = x; + *height = y; +} + +static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nm, LRESULT *lResult) +{ + uiTable *t = uiTable(c); + uiTableModelHandler *mh = t->model->mh; + BOOL ret = FALSE; + + switch (nm->code) { + case LVN_GETDISPINFO: + { + NMLVDISPINFO* di = (NMLVDISPINFO*)nm; + LVITEM* item = &di->item; + if (!(item->mask & LVIF_TEXT)) { + break; + } + int row = item->iItem; + int col = item->iSubItem; + if (col<0 || col>=(int)t->columns.size()) { + break; + } + + uiTableColumn *tc = (uiTableColumn*)t->columns[col]; + + int mcol = tc->modelColumn; + uiTableModelColumnType typ = (*mh->ColumnType)(mh,t->model,mcol); + if (typ == uiTableModelColumnString) { + void* data = (*(mh->CellValue))(mh, t->model, row, mcol); + int n = MultiByteToWideChar(CP_UTF8, 0, (const char*)data, -1, item->pszText, item->cchTextMax); + // make sure clipped strings are nul-terminated + if (n>=item->cchTextMax) { + item->pszText[item->cchTextMax-1] = L'\0'; + } + } else if (typ == uiTableModelColumnInt) { + char buf[32]; + intptr_t data = (intptr_t)(*(mh->CellValue))(mh, t->model, row, mcol); + sprintf(buf, "%d", (int)data); + int n = MultiByteToWideChar(CP_UTF8, 0, buf, -1, item->pszText, item->cchTextMax); + // make sure clipped strings are nul-terminated + if (n>=item->cchTextMax) { + item->pszText[item->cchTextMax-1] = L'\0'; + } + } else { + item->pszText[0] = L'\0'; + } + break; + } + default: + break; + } + *lResult = 0; + return ret; +} + From 7402dec26610b6317d40758986a54feaa34140e2 Mon Sep 17 00:00:00 2001 From: Ben Campbell Date: Sun, 6 May 2018 01:29:59 +1200 Subject: [PATCH 013/166] remove superfluous #include --- windows/table.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 5d2273af..4d2ebd12 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -1,7 +1,5 @@ #include "uipriv_windows.hpp" -#include - static void uiTableDestroy(uiControl *c); static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height); static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nm, LRESULT *lResult); From 4246ae5549c43cbd48c0bea726ec16b8d1f38282 Mon Sep 17 00:00:00 2001 From: Ben Campbell Date: Tue, 22 May 2018 22:53:25 +1200 Subject: [PATCH 014/166] assorted consistancy cleanup --- windows/table.cpp | 160 ++++++++++++++++++++++++++-------------------- 1 file changed, 90 insertions(+), 70 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 4d2ebd12..2f7737ee 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -1,14 +1,12 @@ #include "uipriv_windows.hpp" -static void uiTableDestroy(uiControl *c); -static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height); -static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nm, LRESULT *lResult); +//static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height); struct uiTable; struct uiTableModel { uiTableModelHandler *mh; - std::vector tables; + std::vector tables; }; struct uiTableColumn { @@ -22,7 +20,7 @@ struct uiTable { uiWindowsControl c; uiTableModel *model; HWND hwnd; - std::vector columns; + std::vector columns; }; void *uiTableModelStrdup(const char *str) @@ -52,27 +50,31 @@ void uiFreeTableModel(uiTableModel *m) void uiTableModelRowInserted(uiTableModel *m, int newIndex) { - LVITEM item; + LVITEMW item; + ZeroMemory(&item, sizeof (LVITEMW)); item.mask = 0; item.iItem = newIndex; item.iSubItem = 0; //? for (auto t : m->tables) { - ListView_InsertItem( t->hwnd, &item ); + if (SendMessageW(t->hwnd, LVM_INSERTITEM, 0, (LPARAM) (&item)) == (LRESULT) (-1)) + logLastError(L"error calling LVM_INSERTITEM in uiTableModelRowInserted()"); } } void uiTableModelRowChanged(uiTableModel *m, int index) { for (auto t : m->tables) { - ListView_Update( t->hwnd, index ); + if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) index, 0) == (LRESULT) (-1)) + logLastError(L"error calling LVM_UPDATE in uiTableModelRowChanged()"); } } void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) { for (auto t : m->tables) { - ListView_DeleteItem( t->hwnd, oldIndex ); + if (SendMessageW(t->hwnd, LVM_DELETEITEM, (WPARAM) oldIndex, 0) == (LRESULT) (-1)) + logLastError(L"error calling LVM_DELETEITEM in uiTableModelRowDeleted()"); } } @@ -80,6 +82,7 @@ void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) { uiTable *t = c->t; int lvIndex = 0; + LVCOLUMNW lvc; if (c->modelColumn >=0) { return; // multiple parts not implemented @@ -92,16 +95,17 @@ void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) break; } if (candidate->modelColumn >= 0) { - ++lvIndex; + lvIndex++; } } - - LV_COLUMN lvc; + + ZeroMemory(&lvc, sizeof (LVCOLUMNW)); lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; /* | LVCF_SUBITEM; */ lvc.fmt = LVCFMT_LEFT; lvc.cx = 120; // TODO lvc.pszText = c->name; - ListView_InsertColumn(c->t->hwnd, lvIndex, &lvc); + if (SendMessageW(c->t->hwnd, LVM_INSERTCOLUMN, (WPARAM) lvIndex, (LPARAM) (&lvc)) == (LRESULT) (-1)) + logLastError(L"error calling LVM_INSERTCOLUMN in uiTableColumnPartSetTextPart()"); } void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) @@ -138,33 +142,11 @@ void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) uiWindowsControlAllDefaultsExceptDestroy(uiTable) -uiTable *uiNewTable(uiTableModel *model) -{ - uiTable *t; - int winStyle = WS_CHILD | LVS_AUTOARRANGE | LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL; - - uiWindowsNewControl(uiTable, t); - new(&t->columns) std::vector(); // (initialising in place) - t->model = model; - t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, - WC_LISTVIEW, - L"", - winStyle, - hInstance, - NULL, - TRUE); - model->tables.push_back(t); - uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t)); - ListView_SetExtendedListViewStyle(t->hwnd, LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP); - // TODO: try LVS_EX_AUTOSIZECOLUMNS - int n = (*(model->mh->NumRows))(model->mh, model); - ListView_SetItemCountEx(t->hwnd, n, 0); - return t; -} - uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) { - uiTableColumn *c = uiprivNew(uiTableColumn); + uiTableColumn *c; + + c = uiprivNew(uiTableColumn); c->name = toUTF16(name); c->t = t; c->modelColumn = -1; // -1 = unassigned @@ -182,81 +164,93 @@ static void uiTableDestroy(uiControl *c) { uiTable *t = uiTable(c); uiTableModel *model = t->model; - std::vector::iterator it; + std::vector::iterator it; uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); uiWindowsEnsureDestroyWindow(t->hwnd); // detach table from model - for (it = model->tables.begin(); it != model->tables.end(); ++it) { + for (it = model->tables.begin(); it != model->tables.end(); it++) { if (*it == t) { model->tables.erase(it); break; } } // free the columns - for (auto col: t->columns) { + for (auto col : t->columns) { uiprivFree(col->name); uiprivFree(col); } - t->columns.~vector(); // (created with placement new, so just call dtor directly) + t->columns.~vector(); // (created with placement new, so just call dtor directly) uiFreeControl(uiControl(t)); } +// suggested listview sizing from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing: +// "columns widths that avoid truncated data x an integral number of items" +// Don't think that'll cut it when some cells have overlong data (eg +// stupidly long URLs). So for now, just hardcode a minimum: +#define tableMinWidth 107 /* in line with other controls */ +#define tableMinHeight (14*3) /* header + 2 lines (roughly) */ + static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height) { uiTable *t = uiTable(c); uiWindowsSizing sizing; int x, y; - // suggested listview sizing from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing: - // "columns widths that avoid truncated data x an integral number of items" - // Don't think that'll cut it when some cells have overlong data (eg - // stupidly long URLs). So for now, just hardcode a minimum: - - x = 107; // in line with other controls - y = 14*3; // header + 2 lines (roughly) + x = tableMinWidth; + y = tableMinHeight; uiWindowsGetSizing(t->hwnd, &sizing); uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); *width = x; *height = y; } -static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nm, LRESULT *lResult) + +static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) { uiTable *t = uiTable(c); uiTableModelHandler *mh = t->model->mh; BOOL ret = FALSE; - switch (nm->code) { + switch (nmhdr->code) { case LVN_GETDISPINFO: { - NMLVDISPINFO* di = (NMLVDISPINFO*)nm; - LVITEM* item = &di->item; - if (!(item->mask & LVIF_TEXT)) { - break; - } - int row = item->iItem; - int col = item->iSubItem; - if (col<0 || col>=(int)t->columns.size()) { - break; - } + NMLVDISPINFOW *di; + LVITEMW *item; + int row, col; + uiTableColumn *tc; + int mcol; + uiTableModelColumnType typ; - uiTableColumn *tc = (uiTableColumn*)t->columns[col]; + di = (NMLVDISPINFOW *)nmhdr; + item = &(di->item); + if (!(item->mask & LVIF_TEXT)) + break; + row = item->iItem; + col = item->iSubItem; + if (col<0 || col>=(int)t->columns.size()) + break; + tc = (uiTableColumn *)t->columns[col]; + mcol = tc->modelColumn; + typ = (*mh->ColumnType)(mh, t->model, mcol); - int mcol = tc->modelColumn; - uiTableModelColumnType typ = (*mh->ColumnType)(mh,t->model,mcol); if (typ == uiTableModelColumnString) { - void* data = (*(mh->CellValue))(mh, t->model, row, mcol); - int n = MultiByteToWideChar(CP_UTF8, 0, (const char*)data, -1, item->pszText, item->cchTextMax); + void* data; + int n; + + data = (*(mh->CellValue))(mh, t->model, row, mcol); + n = MultiByteToWideChar(CP_UTF8, 0, (const char *)data, -1, item->pszText, item->cchTextMax); // make sure clipped strings are nul-terminated - if (n>=item->cchTextMax) { + if (n>=item->cchTextMax) item->pszText[item->cchTextMax-1] = L'\0'; - } } else if (typ == uiTableModelColumnInt) { char buf[32]; - intptr_t data = (intptr_t)(*(mh->CellValue))(mh, t->model, row, mcol); + intptr_t data; + int n; + + data = (intptr_t)(*(mh->CellValue))(mh, t->model, row, mcol); sprintf(buf, "%d", (int)data); - int n = MultiByteToWideChar(CP_UTF8, 0, buf, -1, item->pszText, item->cchTextMax); + n = MultiByteToWideChar(CP_UTF8, 0, buf, -1, item->pszText, item->cchTextMax); // make sure clipped strings are nul-terminated if (n>=item->cchTextMax) { item->pszText[item->cchTextMax-1] = L'\0'; @@ -273,3 +267,29 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nm, LRESULT *lResult) return ret; } +uiTable *uiNewTable(uiTableModel *model) +{ + uiTable *t; + int n; + + uiWindowsNewControl(uiTable, t); + new(&t->columns) std::vector(); // (initialising in place) + t->model = model; + t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, + WC_LISTVIEW, L"", + LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL | WS_TABSTOP | WS_HSCROLL | WS_VSCROLL, + hInstance, NULL, + TRUE); + model->tables.push_back(t); + uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t)); + + SendMessageW(t->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, + (WPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP), + (LPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP)); + // TODO: try LVS_EX_AUTOSIZECOLUMNS + n = (*(model->mh->NumRows))(model->mh, model); + if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0) + logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()"); + return t; +} + From ba13227bed454ac8f76455feb4a6d409fd497b95 Mon Sep 17 00:00:00 2001 From: Ben Campbell Date: Mon, 28 May 2018 20:26:07 +1200 Subject: [PATCH 015/166] further style consistency tweaks --- windows/table.cpp | 43 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 2f7737ee..ad05c62c 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -1,9 +1,5 @@ #include "uipriv_windows.hpp" -//static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height); - -struct uiTable; - struct uiTableModel { uiTableModelHandler *mh; std::vector tables; @@ -42,7 +38,6 @@ uiTableModel *uiNewTableModel(uiTableModelHandler *mh) return m; } - void uiFreeTableModel(uiTableModel *m) { delete m; @@ -55,27 +50,24 @@ void uiTableModelRowInserted(uiTableModel *m, int newIndex) ZeroMemory(&item, sizeof (LVITEMW)); item.mask = 0; item.iItem = newIndex; - item.iSubItem = 0; //? - for (auto t : m->tables) { + item.iSubItem = 0; + for (auto t : m->tables) if (SendMessageW(t->hwnd, LVM_INSERTITEM, 0, (LPARAM) (&item)) == (LRESULT) (-1)) logLastError(L"error calling LVM_INSERTITEM in uiTableModelRowInserted()"); - } } void uiTableModelRowChanged(uiTableModel *m, int index) { - for (auto t : m->tables) { + for (auto t : m->tables) if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) index, 0) == (LRESULT) (-1)) logLastError(L"error calling LVM_UPDATE in uiTableModelRowChanged()"); - } } void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) { - for (auto t : m->tables) { + for (auto t : m->tables) if (SendMessageW(t->hwnd, LVM_DELETEITEM, (WPARAM) oldIndex, 0) == (LRESULT) (-1)) logLastError(L"error calling LVM_DELETEITEM in uiTableModelRowDeleted()"); - } } void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) @@ -84,19 +76,16 @@ void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) int lvIndex = 0; LVCOLUMNW lvc; - if (c->modelColumn >=0) { + if (c->modelColumn >= 0) return; // multiple parts not implemented - } c->modelColumn = modelColumn; // work out appropriate listview index for the column for (auto candidate : t->columns) { - if (candidate == c) { + if (candidate == c) break; - } - if (candidate->modelColumn >= 0) { + if (candidate->modelColumn >= 0) lvIndex++; - } } ZeroMemory(&lvc, sizeof (LVCOLUMNW)); @@ -130,7 +119,7 @@ void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int e void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) { - // TODO + // not implemented } void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) @@ -187,7 +176,8 @@ static void uiTableDestroy(uiControl *c) // suggested listview sizing from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing: // "columns widths that avoid truncated data x an integral number of items" // Don't think that'll cut it when some cells have overlong data (eg -// stupidly long URLs). So for now, just hardcode a minimum: +// stupidly long URLs). So for now, just hardcode a minimum. +// TODO: Investigate using LVM_GETHEADER/HDM_LAYOUT here... #define tableMinWidth 107 /* in line with other controls */ #define tableMinHeight (14*3) /* header + 2 lines (roughly) */ @@ -205,7 +195,6 @@ static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height) *height = y; } - static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) { uiTable *t = uiTable(c); @@ -228,7 +217,7 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) break; row = item->iItem; col = item->iSubItem; - if (col<0 || col>=(int)t->columns.size()) + if (col < 0 || col >= (int)t->columns.size()) break; tc = (uiTableColumn *)t->columns[col]; mcol = tc->modelColumn; @@ -241,7 +230,7 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) data = (*(mh->CellValue))(mh, t->model, row, mcol); n = MultiByteToWideChar(CP_UTF8, 0, (const char *)data, -1, item->pszText, item->cchTextMax); // make sure clipped strings are nul-terminated - if (n>=item->cchTextMax) + if (n >= item->cchTextMax) item->pszText[item->cchTextMax-1] = L'\0'; } else if (typ == uiTableModelColumnInt) { char buf[32]; @@ -252,12 +241,10 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) sprintf(buf, "%d", (int)data); n = MultiByteToWideChar(CP_UTF8, 0, buf, -1, item->pszText, item->cchTextMax); // make sure clipped strings are nul-terminated - if (n>=item->cchTextMax) { + if (n >= item->cchTextMax) item->pszText[item->cchTextMax-1] = L'\0'; - } - } else { + } else item->pszText[0] = L'\0'; - } break; } default: @@ -283,10 +270,10 @@ uiTable *uiNewTable(uiTableModel *model) model->tables.push_back(t); uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t)); + // TODO: try LVS_EX_AUTOSIZECOLUMNS SendMessageW(t->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, (WPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP), (LPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP)); - // TODO: try LVS_EX_AUTOSIZECOLUMNS n = (*(model->mh->NumRows))(model->mh, model); if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0) logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()"); From b3b21196a157f91dd2b1cd569f8586fbeadfa64b Mon Sep 17 00:00:00 2001 From: Ben Campbell Date: Tue, 29 May 2018 18:17:10 +1200 Subject: [PATCH 016/166] minor windows table tweaks --- windows/table.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index ad05c62c..120bc830 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -119,7 +119,7 @@ void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int e void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) { - // not implemented + // TODO } void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) @@ -247,8 +247,6 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) item->pszText[0] = L'\0'; break; } - default: - break; } *lResult = 0; return ret; From a7fe45b8a5753332cefe5e3708a0aa1dc93552ce Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 29 May 2018 20:26:48 -0400 Subject: [PATCH 017/166] Removed carriage returns. --- windows/image.cpp | 62 ++--- windows/table.cpp | 560 +++++++++++++++++++++++----------------------- 2 files changed, 311 insertions(+), 311 deletions(-) diff --git a/windows/image.cpp b/windows/image.cpp index bba013b1..e3b78475 100644 --- a/windows/image.cpp +++ b/windows/image.cpp @@ -1,31 +1,31 @@ -#include "uipriv_windows.hpp" -// stubbed out windows image list implementation. -// Required for uiTable control, but windows implemenation -// doesn't currently have image support. - -struct uiImage { - double width; - double height; - // HIMAGELIST images; -}; - -uiImage *uiNewImage(double width, double height) -{ - uiImage *i; - - i = uiprivNew(uiImage); - i->width = width; - i->height = height; - return i; -} - -void uiFreeImage(uiImage *i) -{ - uiprivFree(i); -} - -void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride) -{ - // not implemented -} - +#include "uipriv_windows.hpp" +// stubbed out windows image list implementation. +// Required for uiTable control, but windows implemenation +// doesn't currently have image support. + +struct uiImage { + double width; + double height; + // HIMAGELIST images; +}; + +uiImage *uiNewImage(double width, double height) +{ + uiImage *i; + + i = uiprivNew(uiImage); + i->width = width; + i->height = height; + return i; +} + +void uiFreeImage(uiImage *i) +{ + uiprivFree(i); +} + +void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride) +{ + // not implemented +} + diff --git a/windows/table.cpp b/windows/table.cpp index 120bc830..934acd46 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -1,280 +1,280 @@ -#include "uipriv_windows.hpp" - -struct uiTableModel { - uiTableModelHandler *mh; - std::vector tables; -}; - -struct uiTableColumn { - uiTable *t; - WCHAR *name; - // don't really support parts (but this would part=>column mappings if we did) - int modelColumn; // -1 = none -}; - -struct uiTable { - uiWindowsControl c; - uiTableModel *model; - HWND hwnd; - std::vector columns; -}; - -void *uiTableModelStrdup(const char *str) -{ - return strdup(str); -} - -void *uiTableModelGiveColor(double r, double g, double b, double a) -{ - return 0; // not implemented -} - -uiTableModel *uiNewTableModel(uiTableModelHandler *mh) -{ - uiTableModel *m; - - m = new uiTableModel(); - m->mh = mh; - return m; -} - -void uiFreeTableModel(uiTableModel *m) -{ - delete m; -} - -void uiTableModelRowInserted(uiTableModel *m, int newIndex) -{ - LVITEMW item; - - ZeroMemory(&item, sizeof (LVITEMW)); - item.mask = 0; - item.iItem = newIndex; - item.iSubItem = 0; - for (auto t : m->tables) - if (SendMessageW(t->hwnd, LVM_INSERTITEM, 0, (LPARAM) (&item)) == (LRESULT) (-1)) - logLastError(L"error calling LVM_INSERTITEM in uiTableModelRowInserted()"); -} - -void uiTableModelRowChanged(uiTableModel *m, int index) -{ - for (auto t : m->tables) - if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) index, 0) == (LRESULT) (-1)) - logLastError(L"error calling LVM_UPDATE in uiTableModelRowChanged()"); -} - -void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) -{ - for (auto t : m->tables) - if (SendMessageW(t->hwnd, LVM_DELETEITEM, (WPARAM) oldIndex, 0) == (LRESULT) (-1)) - logLastError(L"error calling LVM_DELETEITEM in uiTableModelRowDeleted()"); -} - -void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) -{ - uiTable *t = c->t; - int lvIndex = 0; - LVCOLUMNW lvc; - - if (c->modelColumn >= 0) - return; // multiple parts not implemented - c->modelColumn = modelColumn; - - // work out appropriate listview index for the column - for (auto candidate : t->columns) { - if (candidate == c) - break; - if (candidate->modelColumn >= 0) - lvIndex++; - } - - ZeroMemory(&lvc, sizeof (LVCOLUMNW)); - lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; /* | LVCF_SUBITEM; */ - lvc.fmt = LVCFMT_LEFT; - lvc.cx = 120; // TODO - lvc.pszText = c->name; - if (SendMessageW(c->t->hwnd, LVM_INSERTCOLUMN, (WPARAM) lvIndex, (LPARAM) (&lvc)) == (LRESULT) (-1)) - logLastError(L"error calling LVM_INSERTCOLUMN in uiTableColumnPartSetTextPart()"); -} - -void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) -{ - // not implemented -} - -void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand) -{ - // not implemented -} - -void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) -{ - // not implemented -} - -void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand) -{ - // not implemented -} - -void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) -{ - // TODO -} - -void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) -{ - // not implemented -} - -// uiTable implementation - -uiWindowsControlAllDefaultsExceptDestroy(uiTable) - -uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) -{ - uiTableColumn *c; - - c = uiprivNew(uiTableColumn); - c->name = toUTF16(name); - c->t = t; - c->modelColumn = -1; // -1 = unassigned - // we defer the actual ListView_InsertColumn call until a part is added... - t->columns.push_back(c); - return c; -} - -void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) -{ - // not implemented -} - -static void uiTableDestroy(uiControl *c) -{ - uiTable *t = uiTable(c); - uiTableModel *model = t->model; - std::vector::iterator it; - - uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); - uiWindowsEnsureDestroyWindow(t->hwnd); - // detach table from model - for (it = model->tables.begin(); it != model->tables.end(); it++) { - if (*it == t) { - model->tables.erase(it); - break; - } - } - // free the columns - for (auto col : t->columns) { - uiprivFree(col->name); - uiprivFree(col); - } - t->columns.~vector(); // (created with placement new, so just call dtor directly) - uiFreeControl(uiControl(t)); -} - -// suggested listview sizing from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing: -// "columns widths that avoid truncated data x an integral number of items" -// Don't think that'll cut it when some cells have overlong data (eg -// stupidly long URLs). So for now, just hardcode a minimum. -// TODO: Investigate using LVM_GETHEADER/HDM_LAYOUT here... -#define tableMinWidth 107 /* in line with other controls */ -#define tableMinHeight (14*3) /* header + 2 lines (roughly) */ - -static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height) -{ - uiTable *t = uiTable(c); - uiWindowsSizing sizing; - int x, y; - - x = tableMinWidth; - y = tableMinHeight; - uiWindowsGetSizing(t->hwnd, &sizing); - uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); - *width = x; - *height = y; -} - -static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) -{ - uiTable *t = uiTable(c); - uiTableModelHandler *mh = t->model->mh; - BOOL ret = FALSE; - - switch (nmhdr->code) { - case LVN_GETDISPINFO: - { - NMLVDISPINFOW *di; - LVITEMW *item; - int row, col; - uiTableColumn *tc; - int mcol; - uiTableModelColumnType typ; - - di = (NMLVDISPINFOW *)nmhdr; - item = &(di->item); - if (!(item->mask & LVIF_TEXT)) - break; - row = item->iItem; - col = item->iSubItem; - if (col < 0 || col >= (int)t->columns.size()) - break; - tc = (uiTableColumn *)t->columns[col]; - mcol = tc->modelColumn; - typ = (*mh->ColumnType)(mh, t->model, mcol); - - if (typ == uiTableModelColumnString) { - void* data; - int n; - - data = (*(mh->CellValue))(mh, t->model, row, mcol); - n = MultiByteToWideChar(CP_UTF8, 0, (const char *)data, -1, item->pszText, item->cchTextMax); - // make sure clipped strings are nul-terminated - if (n >= item->cchTextMax) - item->pszText[item->cchTextMax-1] = L'\0'; - } else if (typ == uiTableModelColumnInt) { - char buf[32]; - intptr_t data; - int n; - - data = (intptr_t)(*(mh->CellValue))(mh, t->model, row, mcol); - sprintf(buf, "%d", (int)data); - n = MultiByteToWideChar(CP_UTF8, 0, buf, -1, item->pszText, item->cchTextMax); - // make sure clipped strings are nul-terminated - if (n >= item->cchTextMax) - item->pszText[item->cchTextMax-1] = L'\0'; - } else - item->pszText[0] = L'\0'; - break; - } - } - *lResult = 0; - return ret; -} - -uiTable *uiNewTable(uiTableModel *model) -{ - uiTable *t; - int n; - - uiWindowsNewControl(uiTable, t); - new(&t->columns) std::vector(); // (initialising in place) - t->model = model; - t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, - WC_LISTVIEW, L"", - LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL | WS_TABSTOP | WS_HSCROLL | WS_VSCROLL, - hInstance, NULL, - TRUE); - model->tables.push_back(t); - uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t)); - - // TODO: try LVS_EX_AUTOSIZECOLUMNS - SendMessageW(t->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, - (WPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP), - (LPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP)); - n = (*(model->mh->NumRows))(model->mh, model); - if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0) - logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()"); - return t; -} - +#include "uipriv_windows.hpp" + +struct uiTableModel { + uiTableModelHandler *mh; + std::vector tables; +}; + +struct uiTableColumn { + uiTable *t; + WCHAR *name; + // don't really support parts (but this would part=>column mappings if we did) + int modelColumn; // -1 = none +}; + +struct uiTable { + uiWindowsControl c; + uiTableModel *model; + HWND hwnd; + std::vector columns; +}; + +void *uiTableModelStrdup(const char *str) +{ + return strdup(str); +} + +void *uiTableModelGiveColor(double r, double g, double b, double a) +{ + return 0; // not implemented +} + +uiTableModel *uiNewTableModel(uiTableModelHandler *mh) +{ + uiTableModel *m; + + m = new uiTableModel(); + m->mh = mh; + return m; +} + +void uiFreeTableModel(uiTableModel *m) +{ + delete m; +} + +void uiTableModelRowInserted(uiTableModel *m, int newIndex) +{ + LVITEMW item; + + ZeroMemory(&item, sizeof (LVITEMW)); + item.mask = 0; + item.iItem = newIndex; + item.iSubItem = 0; + for (auto t : m->tables) + if (SendMessageW(t->hwnd, LVM_INSERTITEM, 0, (LPARAM) (&item)) == (LRESULT) (-1)) + logLastError(L"error calling LVM_INSERTITEM in uiTableModelRowInserted()"); +} + +void uiTableModelRowChanged(uiTableModel *m, int index) +{ + for (auto t : m->tables) + if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) index, 0) == (LRESULT) (-1)) + logLastError(L"error calling LVM_UPDATE in uiTableModelRowChanged()"); +} + +void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) +{ + for (auto t : m->tables) + if (SendMessageW(t->hwnd, LVM_DELETEITEM, (WPARAM) oldIndex, 0) == (LRESULT) (-1)) + logLastError(L"error calling LVM_DELETEITEM in uiTableModelRowDeleted()"); +} + +void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) +{ + uiTable *t = c->t; + int lvIndex = 0; + LVCOLUMNW lvc; + + if (c->modelColumn >= 0) + return; // multiple parts not implemented + c->modelColumn = modelColumn; + + // work out appropriate listview index for the column + for (auto candidate : t->columns) { + if (candidate == c) + break; + if (candidate->modelColumn >= 0) + lvIndex++; + } + + ZeroMemory(&lvc, sizeof (LVCOLUMNW)); + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; /* | LVCF_SUBITEM; */ + lvc.fmt = LVCFMT_LEFT; + lvc.cx = 120; // TODO + lvc.pszText = c->name; + if (SendMessageW(c->t->hwnd, LVM_INSERTCOLUMN, (WPARAM) lvIndex, (LPARAM) (&lvc)) == (LRESULT) (-1)) + logLastError(L"error calling LVM_INSERTCOLUMN in uiTableColumnPartSetTextPart()"); +} + +void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) +{ + // not implemented +} + +void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand) +{ + // not implemented +} + +void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) +{ + // not implemented +} + +void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand) +{ + // not implemented +} + +void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) +{ + // TODO +} + +void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) +{ + // not implemented +} + +// uiTable implementation + +uiWindowsControlAllDefaultsExceptDestroy(uiTable) + +uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) +{ + uiTableColumn *c; + + c = uiprivNew(uiTableColumn); + c->name = toUTF16(name); + c->t = t; + c->modelColumn = -1; // -1 = unassigned + // we defer the actual ListView_InsertColumn call until a part is added... + t->columns.push_back(c); + return c; +} + +void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) +{ + // not implemented +} + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + uiTableModel *model = t->model; + std::vector::iterator it; + + uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); + uiWindowsEnsureDestroyWindow(t->hwnd); + // detach table from model + for (it = model->tables.begin(); it != model->tables.end(); it++) { + if (*it == t) { + model->tables.erase(it); + break; + } + } + // free the columns + for (auto col : t->columns) { + uiprivFree(col->name); + uiprivFree(col); + } + t->columns.~vector(); // (created with placement new, so just call dtor directly) + uiFreeControl(uiControl(t)); +} + +// suggested listview sizing from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing: +// "columns widths that avoid truncated data x an integral number of items" +// Don't think that'll cut it when some cells have overlong data (eg +// stupidly long URLs). So for now, just hardcode a minimum. +// TODO: Investigate using LVM_GETHEADER/HDM_LAYOUT here... +#define tableMinWidth 107 /* in line with other controls */ +#define tableMinHeight (14*3) /* header + 2 lines (roughly) */ + +static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height) +{ + uiTable *t = uiTable(c); + uiWindowsSizing sizing; + int x, y; + + x = tableMinWidth; + y = tableMinHeight; + uiWindowsGetSizing(t->hwnd, &sizing); + uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); + *width = x; + *height = y; +} + +static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) +{ + uiTable *t = uiTable(c); + uiTableModelHandler *mh = t->model->mh; + BOOL ret = FALSE; + + switch (nmhdr->code) { + case LVN_GETDISPINFO: + { + NMLVDISPINFOW *di; + LVITEMW *item; + int row, col; + uiTableColumn *tc; + int mcol; + uiTableModelColumnType typ; + + di = (NMLVDISPINFOW *)nmhdr; + item = &(di->item); + if (!(item->mask & LVIF_TEXT)) + break; + row = item->iItem; + col = item->iSubItem; + if (col < 0 || col >= (int)t->columns.size()) + break; + tc = (uiTableColumn *)t->columns[col]; + mcol = tc->modelColumn; + typ = (*mh->ColumnType)(mh, t->model, mcol); + + if (typ == uiTableModelColumnString) { + void* data; + int n; + + data = (*(mh->CellValue))(mh, t->model, row, mcol); + n = MultiByteToWideChar(CP_UTF8, 0, (const char *)data, -1, item->pszText, item->cchTextMax); + // make sure clipped strings are nul-terminated + if (n >= item->cchTextMax) + item->pszText[item->cchTextMax-1] = L'\0'; + } else if (typ == uiTableModelColumnInt) { + char buf[32]; + intptr_t data; + int n; + + data = (intptr_t)(*(mh->CellValue))(mh, t->model, row, mcol); + sprintf(buf, "%d", (int)data); + n = MultiByteToWideChar(CP_UTF8, 0, buf, -1, item->pszText, item->cchTextMax); + // make sure clipped strings are nul-terminated + if (n >= item->cchTextMax) + item->pszText[item->cchTextMax-1] = L'\0'; + } else + item->pszText[0] = L'\0'; + break; + } + } + *lResult = 0; + return ret; +} + +uiTable *uiNewTable(uiTableModel *model) +{ + uiTable *t; + int n; + + uiWindowsNewControl(uiTable, t); + new(&t->columns) std::vector(); // (initialising in place) + t->model = model; + t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, + WC_LISTVIEW, L"", + LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL | WS_TABSTOP | WS_HSCROLL | WS_VSCROLL, + hInstance, NULL, + TRUE); + model->tables.push_back(t); + uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t)); + + // TODO: try LVS_EX_AUTOSIZECOLUMNS + SendMessageW(t->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, + (WPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP), + (LPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP)); + n = (*(model->mh->NumRows))(model->mh, model); + if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0) + logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()"); + return t; +} + From 374eed74329a706a53661c8636632e5c9cb88111 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 29 May 2018 20:27:31 -0400 Subject: [PATCH 018/166] Removed the facilities for printing an int as text; this was causing the tester to crash in a weird way on OS X (through NSApplication _crashOnException: without telling me what that exception was) and I didn't intend on this part type to be used in this way anyway... --- test/page16.c | 2 -- windows/table.cpp | 11 ----------- 2 files changed, 13 deletions(-) diff --git a/test/page16.c b/test/page16.c index 3a5ac639..80ac0139 100644 --- a/test/page16.c +++ b/test/page16.c @@ -132,8 +132,6 @@ uiBox *makePage16(void) uiTableSetRowBackgroundColorModelColumn(t, 3); - uiTableAppendTextColumn(t, "Numbers", 8); - tc = uiTableAppendColumn(t, "Buttons"); uiTableColumnAppendCheckboxPart(tc, 7, 0); uiTableColumnAppendButtonPart(tc, 6, 1); diff --git a/windows/table.cpp b/windows/table.cpp index 934acd46..c5dc9ef3 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -232,17 +232,6 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) // make sure clipped strings are nul-terminated if (n >= item->cchTextMax) item->pszText[item->cchTextMax-1] = L'\0'; - } else if (typ == uiTableModelColumnInt) { - char buf[32]; - intptr_t data; - int n; - - data = (intptr_t)(*(mh->CellValue))(mh, t->model, row, mcol); - sprintf(buf, "%d", (int)data); - n = MultiByteToWideChar(CP_UTF8, 0, buf, -1, item->pszText, item->cchTextMax); - // make sure clipped strings are nul-terminated - if (n >= item->cchTextMax) - item->pszText[item->cchTextMax-1] = L'\0'; } else item->pszText[0] = L'\0'; break; From 1cb0e9046f561ea42c67308284a63263d759f1f7 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 30 May 2018 22:48:46 -0400 Subject: [PATCH 019/166] Made the button cell renderer draw and size much more nicely than it did before, fixing a few bugs along the way. --- unix/cellrendererbutton.c | 151 +++++++++++++++++++++++--------------- 1 file changed, 92 insertions(+), 59 deletions(-) diff --git a/unix/cellrendererbutton.c b/unix/cellrendererbutton.c index c3ff4f82..608cd61d 100644 --- a/unix/cellrendererbutton.c +++ b/unix/cellrendererbutton.c @@ -3,10 +3,9 @@ // TODOs // - it's a rather tight fit -// - selected row text color is white -// - resizing a column with a button in it crashes the program +// - selected row text color is white (TODO not on 3.22) // - accessibility -// - right side too big? +// - right side too big? (TODO reverify) #define cellRendererButtonType (cellRendererButton_get_type()) #define cellRendererButton(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), cellRendererButtonType, cellRendererButton)) @@ -58,6 +57,7 @@ static GtkSizeRequestMode cellRendererButton_get_request_mode(GtkCellRenderer *r } // this is basically what GtkCellRendererToggle did in 3.10 and does in 3.20, as well as what the Foreign Drawing gtk3-demo demo does +// TODO how does this seem to work with highlight on 3.22, and does that work with 3.10 too static GtkStyleContext *setButtonStyle(GtkWidget *widget) { GtkStyleContext *base, *context; @@ -90,7 +90,56 @@ void unsetButtonStyle(GtkStyleContext *context) g_object_unref(context); } -// this is based on what GtkCellRendererText does +// this is based on what GtkCellRendererText in GTK+ 3.22.30 does +// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c) +static PangoLayout *cellRendererButtonPangoLayout(cellRendererButton *c, GtkWidget *widget) +{ + PangoLayout *layout; + + layout = gtk_widget_create_pango_layout(widget, c->text); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE); + pango_layout_set_width(layout, -1); + pango_layout_set_wrap(layout, PANGO_WRAP_CHAR); + pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); + return layout; +} + +// this is based on what GtkCellRendererText in GTK+ 3.22.30 does +// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c) +static void cellRendererButtonSize(cellRendererButton *c, GtkWidget *widget, PangoLayout *layout, const GdkRectangle *cell_area, gint *xoff, gint *yoff, gint *width, gint *height) +{ + PangoRectangle rect; + gint xpad, ypad; + gfloat xalign, yalign; + + gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad); + pango_layout_get_pixel_extents(layout, NULL, &rect); + if (rect.width > cell_area->width - (2 * xpad)) + rect.width = cell_area->width - (2 * xpad); + if (rect.height > cell_area->height - (2 * ypad)) + rect.height = cell_area->height - (2 * ypad); + + gtk_cell_renderer_get_alignment(GTK_CELL_RENDERER(c), &xalign, &yalign); + if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL) + xalign = 1.0 - xalign; + if (xoff != NULL) { + *xoff = cell_area->width - (rect.width + (2 * xpad)); + *xoff = (gint) ((gfloat) (*xoff) * xalign); + } + if (yoff != NULL) { + *yoff = cell_area->height - (rect.height + (2 * ypad)); + *yoff = (gint) ((gfloat) (*yoff) * yalign); + if (*yoff < 0) + *yoff = 0; + } + if (width != NULL) + *width = rect.width - (2 * xpad); + if (height != NULL) + *height = rect.height - (2 * ypad); +} + +// this is based on what GtkCellRendererText in GTK+ 3.22.30 does +// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c) static void cellRendererButton_get_preferred_width(GtkCellRenderer *r, GtkWidget *widget, gint *minimum, gint *natural) { cellRendererButton *c = cellRendererButton(r); @@ -101,19 +150,21 @@ static void cellRendererButton_get_preferred_width(GtkCellRenderer *r, GtkWidget gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, NULL); - layout = gtk_widget_create_pango_layout(widget, c->text); - pango_layout_set_width(layout, -1); + layout = cellRendererButtonPangoLayout(c, widget); pango_layout_get_extents(layout, NULL, &rect); g_object_unref(layout); - out = 2 * xpad + PANGO_PIXELS_CEIL(rect.width); + out = PANGO_PIXELS_CEIL(rect.width) + (2 * xpad); + if (rect.x > 0) + out += rect.x; if (minimum != NULL) *minimum = out; if (natural != NULL) *natural = out; } -// this is based on what GtkCellRendererText does +// this is based on what GtkCellRendererText in GTK+ 3.22.30 does +// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c) static void cellRendererButton_get_preferred_height_for_width(GtkCellRenderer *r, GtkWidget *widget, gint width, gint *minimum, gint *natural) { cellRendererButton *c = cellRendererButton(r); @@ -124,19 +175,20 @@ static void cellRendererButton_get_preferred_height_for_width(GtkCellRenderer *r gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad); - layout = gtk_widget_create_pango_layout(widget, c->text); - pango_layout_set_width(layout, ((2 * xpad + width) * PANGO_SCALE)); + layout = cellRendererButtonPangoLayout(c, widget); + pango_layout_set_width(layout, (width + (xpad * 2)) * PANGO_SCALE); pango_layout_get_pixel_size(layout, NULL, &height); g_object_unref(layout); - out = 2 * ypad + height; + out = height + (ypad * 2); if (minimum != NULL) *minimum = out; if (natural != NULL) *natural = out; } -// this is basically what GtkCellRendererText does +// this is based on what GtkCellRendererText in GTK+ 3.22.30 does +// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c) static void cellRendererButton_get_preferred_height(GtkCellRenderer *r, GtkWidget *widget, gint *minimum, gint *natural) { gint width; @@ -145,84 +197,65 @@ static void cellRendererButton_get_preferred_height(GtkCellRenderer *r, GtkWidge gtk_cell_renderer_get_preferred_height_for_width(r, widget, width, minimum, natural); } -// this is based on what GtkCellRendererText does +// this is based on what GtkCellRendererText in GTK+ 3.22.30 does +// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c) static void cellRendererButton_get_aligned_area(GtkCellRenderer *r, GtkWidget *widget, GtkCellRendererState flags, const GdkRectangle *cell_area, GdkRectangle *aligned_area) { cellRendererButton *c = cellRendererButton(r); - gint xpad, ypad; PangoLayout *layout; - PangoRectangle rect; - gfloat xalign, yalign; - gint xoffset, yoffset; + gint xoff, yoff; + gint width, height; - gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad); + layout = cellRendererButtonPangoLayout(c, widget); + cellRendererButtonSize(c, widget, layout, cell_area, + &xoff, &yoff, &width, &height); - layout = gtk_widget_create_pango_layout(widget, c->text); - pango_layout_set_width(layout, -1); - pango_layout_get_pixel_extents(layout, NULL, &rect); - - xoffset = 0; - yoffset = 0; - if (cell_area != NULL) { - gtk_cell_renderer_get_alignment(GTK_CELL_RENDERER(c), &xalign, &yalign); - xoffset = cell_area->width - (2 * xpad + rect.width); - // use explicit casts just to be safe - if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL) - xoffset = ((gdouble) xoffset) * (1.0 - xalign); - else - xoffset *= ((gdouble) xoffset) * xalign; - yoffset = yalign * (cell_area->height - (2 * ypad + rect.height)); - yoffset = MAX(yoffset, 0); - } - - aligned_area->x = cell_area->x + xoffset; - aligned_area->y = cell_area->y + yoffset; - aligned_area->width = 2 * xpad + rect.width; - aligned_area->height = 2 * ypad + rect.height; + aligned_area->x = cell_area->x + xoff; + aligned_area->y = cell_area->y + yoff; + aligned_area->width = width; + aligned_area->height = height; g_object_unref(layout); } -// this is based on both what GtkCellRendererText does and what GtkCellRendererToggle does +// this is based on both what GtkCellRendererText on 3.22.30 does and what GtkCellRendererToggle does (TODO verify the latter; both on 3.10.9) static void cellRendererButton_render(GtkCellRenderer *r, cairo_t *cr, GtkWidget *widget, const GdkRectangle *background_area, const GdkRectangle *cell_area, GtkCellRendererState flags) { cellRendererButton *c = cellRendererButton(r); gint xpad, ypad; GdkRectangle alignedArea; - gint xoffset, yoffset; + gint xoff, yoff; GtkStyleContext *context; PangoLayout *layout; PangoRectangle rect; gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad); - gtk_cell_renderer_get_aligned_area(GTK_CELL_RENDERER(c), widget, flags, cell_area, &alignedArea); - xoffset = alignedArea.x - cell_area->x; - yoffset = alignedArea.y - cell_area->y; + layout = cellRendererButtonPangoLayout(c, widget); + cellRendererButtonSize(c, widget, layout, cell_area, + &xoff, &yoff, NULL, NULL); context = setButtonStyle(widget); - layout = gtk_widget_create_pango_layout(widget, c->text); gtk_render_background(context, cr, - background_area->x + xoffset + xpad, - background_area->y + yoffset + ypad, - background_area->width - 2 * xpad, - background_area->height - 2 * ypad); + background_area->x + xpad, + background_area->y + ypad, + background_area->width - (xpad * 2), + background_area->height - (ypad * 2)); gtk_render_frame(context, cr, - background_area->x + xoffset + xpad, - background_area->y + yoffset + ypad, - background_area->width - 2 * xpad, - background_area->height - 2 * ypad); + background_area->x + xpad, + background_area->y + ypad, + background_area->width - (xpad * 2), + background_area->height - (ypad * 2)); - pango_layout_set_width(layout, -1); pango_layout_get_pixel_extents(layout, NULL, &rect); - xoffset -= rect.x; + xoff -= rect.x; gtk_render_layout(context, cr, - cell_area->x + xoffset + xpad, - cell_area->y + yoffset + ypad, + cell_area->x + xoff + xpad, + cell_area->y + yoff + ypad, layout); - g_object_unref(layout); unsetButtonStyle(context); + g_object_unref(layout); } static guint clickedSignal; From 2f83428ebee20b92ccf8fa1a21c9e4d3d3a42b0c Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 12:26:23 -0400 Subject: [PATCH 020/166] Trying out a new uiTable API. This will make implementations easier, and figures that very few people need dynamic control over table column layout or contents. --- OLD_uitable.h | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ uitable.h | 52 ++++++++++++++++++++++++++++++----------- 2 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 OLD_uitable.h diff --git a/OLD_uitable.h b/OLD_uitable.h new file mode 100644 index 00000000..a54398c5 --- /dev/null +++ b/OLD_uitable.h @@ -0,0 +1,65 @@ +// 20 june 2016 +// kept in a separate file for now + +typedef struct uiImage uiImage; + +// TODO use const void * for const correctness +_UI_EXTERN uiImage *uiNewImage(double width, double height); +_UI_EXTERN void uiFreeImage(uiImage *i); +_UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride); + +typedef struct uiTableModel uiTableModel; +typedef struct uiTableModelHandler uiTableModelHandler; + +// TODO actually validate these +_UI_ENUM(uiTableModelColumnType) { + uiTableModelColumnString, + uiTableModelColumnImage, + uiTableModelColumnInt, + uiTableModelColumnColor, +}; + +// TODO validate ranges; validate types on each getter/setter call (? table columns only?) +struct uiTableModelHandler { + int (*NumColumns)(uiTableModelHandler *, uiTableModel *); + uiTableModelColumnType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int); + int (*NumRows)(uiTableModelHandler *, uiTableModel *); + void *(*CellValue)(uiTableModelHandler *, uiTableModel *, int, int); + void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const void *); +}; + +_UI_EXTERN void *uiTableModelStrdup(const char *str); +// TODO rename the strdup one to this too +_UI_EXTERN void *uiTableModelGiveColor(double r, double g, double b, double a); +_UI_EXTERN void *uiTableModelGiveInt(int i); +// TODO TakeString +// TODO add const +_UI_EXTERN int uiTableModelTakeInt(void *v); + +_UI_EXTERN uiTableModel *uiNewTableModel(uiTableModelHandler *mh); +_UI_EXTERN void uiFreeTableModel(uiTableModel *m); +_UI_EXTERN void uiTableModelRowInserted(uiTableModel *m, int newIndex); +_UI_EXTERN void uiTableModelRowChanged(uiTableModel *m, int index); +_UI_EXTERN void uiTableModelRowDeleted(uiTableModel *m, int oldIndex); +// TODO reordering/moving + +typedef struct uiTableColumn uiTableColumn; + +_UI_EXTERN void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand); +// TODO images shouldn't expand... +_UI_EXTERN void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand); +_UI_EXTERN void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand); +// TODO should these have labels? +_UI_EXTERN void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand); +_UI_EXTERN void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand); +// TODO Editable? +_UI_EXTERN void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable); +_UI_EXTERN void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn); + +typedef struct uiTable uiTable; +#define uiTable(this) ((uiTable *) (this)) +_UI_EXTERN uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name); +_UI_EXTERN uiTableColumn *uiTableAppendTextColumn(uiTable *t, const char *name, int modelColumn); +// TODO getter? +_UI_EXTERN void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn); +_UI_EXTERN uiTable *uiNewTable(uiTableModel *model); diff --git a/uitable.h b/uitable.h index a54398c5..0f23c448 100644 --- a/uitable.h +++ b/uitable.h @@ -43,23 +43,49 @@ _UI_EXTERN void uiTableModelRowChanged(uiTableModel *m, int index); _UI_EXTERN void uiTableModelRowDeleted(uiTableModel *m, int oldIndex); // TODO reordering/moving -typedef struct uiTableColumn uiTableColumn; +#define uiTableModelColumnNeverEditable (-1) +#define uiTableModelColumnAlwaysEditable (-2) -_UI_EXTERN void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand); -// TODO images shouldn't expand... -_UI_EXTERN void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand); -_UI_EXTERN void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand); -// TODO should these have labels? -_UI_EXTERN void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand); -_UI_EXTERN void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand); -// TODO Editable? -_UI_EXTERN void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable); -_UI_EXTERN void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn); +typedef struct uiTableTextColumnOptionalParams uiTableTextColumnOptionalParams; + +struct uiTableTextColumnOptionalParams { + int ColorModelColumn; +}; typedef struct uiTable uiTable; #define uiTable(this) ((uiTable *) (this)) -_UI_EXTERN uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name); -_UI_EXTERN uiTableColumn *uiTableAppendTextColumn(uiTable *t, const char *name, int modelColumn); +_UI_EXTERN void uiTableAppendTextColumn(uiTable *t, + const char *name, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *params); +_UI_EXTERN void uiTableAppendImageColumn(uiTable *t, + const char *name, + int imageModelColumn); +_UI_EXTERN void uiTableAppendImageTextColumn(uiTable *t, + const char *name, + int imageModelColumn, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *textParams); +_UI_EXTERN void uiTableAppendCheckboxColumn(uiTable *t, + const char *name, + int checkboxModelColumn, + int checkboxEditableModelColumn); +_UI_EXTERN void uiTableAppendCheckboxTextColumn(uiTable *t, + const char *name, + int checkboxModelColumn, + int checkboxEditableModelColumn, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *textParams); +_UI_EXTERN void uiTableAppendProgressBarColumn(uiTable *t, + const char *name, + int progressModelColumn); +_UI_EXTERN void uiTableAppendButtonColumn(uiTable *t, + const char *name, + int buttonTextModelColumn, + int buttonClickableModelColumn); // TODO getter? _UI_EXTERN void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn); _UI_EXTERN uiTable *uiNewTable(uiTableModel *model); From 5d576667641921ee78f7ca63982e8b7960f0b17a Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 13:28:11 -0400 Subject: [PATCH 021/166] Started writing the new table column code on OS X. This new code will take full advantage of NSTableView features like the reuse cache. Right now we just have the boilerplate for text-only columns. --- darwin/{table.m => OLD_table.m} | 0 darwin/tablecolumn.m | 198 ++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) rename darwin/{table.m => OLD_table.m} (100%) create mode 100644 darwin/tablecolumn.m diff --git a/darwin/table.m b/darwin/OLD_table.m similarity index 100% rename from darwin/table.m rename to darwin/OLD_table.m diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m new file mode 100644 index 00000000..da80455a --- /dev/null +++ b/darwin/tablecolumn.m @@ -0,0 +1,198 @@ +// 3 june 2018 +#import "uipriv_darwin.h" + +// values from interface builder +#define textColumnLeading 2 +#define textColumnTrailing 2 + +static void layoutCellSubview(NSView *superview, NSView *subview, NSView *leading, CGFloat leadingConstant, NSView *trailing, CGFloat trailingConstant, BOOL stretchy) +{ + [subview setTranslatesAutoresizingMaskIntoConstraints:NO]; + if (stretchy) + [subview setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal]; + else + [subview setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; + if (leading != nil) + [superview addConstraint:uiprivMkConstraint(leading, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + subview, NSLayoutAttributeLeading, + 1, -leadingConstant, + @"uiTable cell subview leading constraint")]; + [superview addConstraint:uiprivMkConstraint(superview, NSLayoutAttributeTop, + NSLayoutRelationEqual, + subview, NSLayoutAttributeTop, + 1, 0, + @"uiTable cell subview top constraint")]; + if (trailing != nil) + [superview addConstraint:uiprivMkConstraint(trailing, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + subview, NSLayoutAttributeLeading, + 1, trailingConstant, + @"uiTable cell subview trailing constraint")]; + [superview addConstraint:uiprivMkConstraint(superview, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + subview, NSLayoutAttributeBottom, + 1, 0, + @"uiTable cell subview bottom constraint")]; +} + +@interface uiprivColumnCellView : NSTableCellView +- (void)uiprivUpdate:(NSInteger)row; +@end + +@implementation uiprivColumnCellView + +- (void)uiprivUpdate:(NSInteger)row +{ + [self doesNotRecognizeSelector:_cmd]; +} + +@end + +static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { + .ColorModelColumn = -1, +}; + +static void updateCellTextField(NSTextField *tf, NSInteger row, uiTableModel *m, int modelColumn, int editableColumn, uiTableTextColumnOptionalParams *params) +{ + void *data; + NSString *str; + BOOL editable; + + data = (*(m->mh->CellValue))(m->mh, m, row, modelColumn); + str = uiprivToNSString((char *) data); + uiprivFree(data); + [tf setStringValue:str]; + + switch (editableColumn) { + case uiTableModelColumnNeverEditable: + editable = NO; + break; + case uiTableModelColumnAlwaysEditable: + editable = YES; + break; + default: + data = (*(m->mh->CellValue))(m->mh, m, row, editableColumn); + editable = uiTableModelTakeInt(data) != 0; + // TODO free data + } + [tf setEditable:editable]; + + color = nil; + if (params->ColorModelColumn != -1) + color = (NSColor *) ((*(m->mh->CellValue))(m->mh, m, row, params->ColorModelColumn)); + if (color == nil) + color = [NSColor controlTextColor]; + [tf setColor:color]; + // TODO release color +} + +@interface uiprivTextColumnCellView : uiprivColumnCellView { + uiTable *t; + uiTableModel *m; + NSTextField *tf; + int modelColumn; + int editableColumn; + uiTableTextColumnOptionalParams params; +} +- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec params:(uiTableTextColumnOptionalParams *)p; +- (IBAction)uiprivOnAction:(id)sender; +@end + +@implementation uiprivTextColumnCellView + +- (id)initWithFrame:(NSRect)r ModelColumn:(int)mc editableColumn:(int)ec params:(uiTableTextColumnOptionalParams *)p +{ + self = [super initWithFrame:frame]; + if (self) { + self->t = table; + self->m = model; + self->modelColumn = mc; + self->editableColumn = ec; + if (p != NULL) + params = *p; + else + params = defaultTextColumnOptionalParams; + + self->tf = uiprivNewLabel(@""); + // TODO set wrap and ellipsize modes? + [self->tf setTarget:self]; + [self->tf setAction:@selector(uiprivOnAction:)]; + [self addSubview:self->tf]; + layoutCellSubview(self, self->tv, + self, textColumnLeading, + self, textColumnTrailing, + YES); + + // take advantage of NSTableCellView-provided accessibility features + [self setTextField:self->tf]; + } + return self; +} + +- (void)dealloc +{ + [self->tf release]; + self->tf = nil; + [super dealloc]; +} + +- (void)uiprivUpdate:(NSInteger)row +{ + updateCellTextField(self->tf, row, self->m, + self->modelColumn, self->editableColumn, &(self->params)); +} + +- (IBAction)onAction:(id)sender +{ + NSInteger row; + const void *data; + + row = [self->t->tv rowForView:self->tf]; + data = [[self->tf stringValue] UTF8String]; + (*(self->m->mh->SetCellValue))(self->m->mh, self->m, + row, self->modelColumn, data); + // always refresh the value in case the model rejected it + [self uiprivUpdate:row]; +} + +@end + +void uiTableAppendTextColumn(uiTable *t, + const char *name, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *params); + +void uiTableAppendImageColumn(uiTable *t, + const char *name, + int imageModelColumn); + +void uiTableAppendImageTextColumn(uiTable *t, + const char *name, + int imageModelColumn, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *textParams); + +void uiTableAppendCheckboxColumn(uiTable *t, + const char *name, + int checkboxModelColumn, + int checkboxEditableModelColumn); + +void uiTableAppendCheckboxTextColumn(uiTable *t, + const char *name, + int checkboxModelColumn, + int checkboxEditableModelColumn, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *textParams); + +void uiTableAppendProgressBarColumn(uiTable *t, + const char *name, + int progressModelColumn); + +void uiTableAppendButtonColumn(uiTable *t, + const char *name, + int buttonTextModelColumn, + int buttonClickableModelColumn); From d1b7d14a8277c71d9719fd072a495ac8690913eb Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 14:13:32 -0400 Subject: [PATCH 022/166] Added image columns. --- darwin/tablecolumn.m | 120 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index da80455a..834db985 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -4,6 +4,8 @@ // values from interface builder #define textColumnLeading 2 #define textColumnTrailing 2 +#define imageColumnLeading 3 +#define imageTextColumnLeading 7 static void layoutCellSubview(NSView *superview, NSView *subview, NSView *leading, CGFloat leadingConstant, NSView *trailing, CGFloat trailingConstant, BOOL stretchy) { @@ -119,7 +121,7 @@ static void updateCellTextField(NSTextField *tf, NSInteger row, uiTableModel *m, [self->tf setTarget:self]; [self->tf setAction:@selector(uiprivOnAction:)]; [self addSubview:self->tf]; - layoutCellSubview(self, self->tv, + layoutCellSubview(self, self->tf, self, textColumnLeading, self, textColumnTrailing, YES); @@ -158,6 +160,122 @@ static void updateCellTextField(NSTextField *tf, NSInteger row, uiTableModel *m, @end +xx TODO somehow merge this with the above +@interface uiprivImageTextColumnCellView : uiprivColumnCellView { + uiTable *t; + uiTableModel *m; + NSImageView *iv; + int modelColumn; + NSTextField *tf; + int textModelColumn; + int textEditableColumn; + uiTableTextColumnOptionalParams params; +} +- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc textModelColumn:(int)tmc editableColumn:(int)ec params:(uiTableTextColumnOptionalParams *)p; +- (IBAction)uiprivOnAction:(id)sender; +@end + +@implementation uiprivImageTextColumnCellView + +- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc textModelColumn:(int)tmc editableColumn:(int)ec params:(uiTableTextColumnOptionalParams *)p +{ + self = [super initWithFrame:frame]; + if (self) { + self->t = table; + self->m = model; + self->modelColumn = mc; + self->textModelColumn = tmc; + self->editableColumn = ec; + if (p != NULL) + params = *p; + else + params = defaultTextColumnOptionalParams; + + self->iv = [[NSImageView alloc] initWithFrame:NSZeroRect]; + [self->iv setImageFrameStyle:NSImageFrameNone]; + [self->iv setImageAlignment:NSImageAlignCenter]; + [self->iv setImageScaling:NSImageScaleProportionallyDown]; + [self->iv setAnimates:NO]; + [self->iv setEditable:NO]; + [self->iv addConstraint:uiprivMkConstraint(self->iv, NSLayoutAttributeWidth, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeHeight, + 1, 0, + @"uiTable image squareness constraint")]; + [self addSubview:self->iv]; + + self->tf = nil; + if (self->textModelColumn != -1) { + self->tf = uiprivNewLabel(@""); + // TODO set wrap and ellipsize modes? + [self->tf setTarget:self]; + [self->tf setAction:@selector(uiprivOnAction:)]; + [self addSubview:self->tf]; + layoutCellSubview(self, self->iv, + self, imageColumnLeading, + nil, 0, + NO); + layoutCellSubview(self, self->tf, + self, imageTextColumnLeading, + self, textColumnTrailing, + YES); + } else { + layoutCellSubview(self, self->iv, + nil, 0, + nil, 0, + NO); + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeCenterX, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeCenterX, + 1, 0, + @"uiTable image centering constraint")]; + } + + // take advantage of NSTableCellView-provided accessibility features + [self setImageView:self->iv]; + if (self->tf != nil) + [self setTextField:self->tf]; + } + return self; +} + +- (void)dealloc +{ + if (self->tf != nil) { + [self->tf release]; + self->tf = nil; + } + [self->iv release]; + self->iv = nil; + [super dealloc]; +} + +- (void)uiprivUpdate:(NSInteger)row +{ + void *data; + + data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->modelColumn); + [self->iv setImage:uiprivImageNSImage((uiImage *) data)]; + if (self->tf != nil) + updateCellTextField(self->tf, row, self->m, + self->textModelColumn, self->editableColumn, &(self->params)); +} + +- (IBAction)onAction:(id)sender +{ + NSInteger row; + const void *data; + + row = [self->t->tv rowForView:self->tf]; + data = [[self->tf stringValue] UTF8String]; + (*(self->m->mh->SetCellValue))(self->m->mh, self->m, + row, self->textModelColumn, data); + // always refresh the value in case the model rejected it + [self uiprivUpdate:row]; +} + +@end + void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, From 9c519f1bfa26455f1ba9c43b5d754a970c9805f9 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 16:50:00 -0400 Subject: [PATCH 023/166] Added checkboxes and consolidated all the columns with or without text. Progressbars and buttons will be separate object types. I do need to redo the data-passing method, and now that there's uiAttribute I can just model a better one off that. --- darwin/tablecolumn.m | 324 +++++++++++++++++++++++-------------------- 1 file changed, 171 insertions(+), 153 deletions(-) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 834db985..6c4f666d 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -6,6 +6,9 @@ #define textColumnTrailing 2 #define imageColumnLeading 3 #define imageTextColumnLeading 7 +#define checkboxTextColumnLeading 0 +// these aren't provided by IB; let's just choose one +#define checkboxColumnLeading imageColumnLeading static void layoutCellSubview(NSView *superview, NSView *subview, NSView *leading, CGFloat leadingConstant, NSView *trailing, CGFloat trailingConstant, BOOL stretchy) { @@ -51,217 +54,217 @@ static void layoutCellSubview(NSView *superview, NSView *subview, NSView *leadin @end +static BOOL isCellEditable(uiTableModel *m, NSInteger row, int modelColumn) +{ + void *data; + + switch (modelColumn) { + case uiTableModelColumnNeverEditable: + return NO; + case uiTableModelColumnAlwaysEditable: + return YES; + } + data = (*(m->mh->CellValue))(m->mh, m, row, modelColumn); + return uiTableModelTakeInt(data) != 0; + // TODO free data +} + static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { .ColorModelColumn = -1, }; -static void updateCellTextField(NSTextField *tf, NSInteger row, uiTableModel *m, int modelColumn, int editableColumn, uiTableTextColumnOptionalParams *params) -{ - void *data; - NSString *str; - BOOL editable; - - data = (*(m->mh->CellValue))(m->mh, m, row, modelColumn); - str = uiprivToNSString((char *) data); - uiprivFree(data); - [tf setStringValue:str]; - - switch (editableColumn) { - case uiTableModelColumnNeverEditable: - editable = NO; - break; - case uiTableModelColumnAlwaysEditable: - editable = YES; - break; - default: - data = (*(m->mh->CellValue))(m->mh, m, row, editableColumn); - editable = uiTableModelTakeInt(data) != 0; - // TODO free data - } - [tf setEditable:editable]; - - color = nil; - if (params->ColorModelColumn != -1) - color = (NSColor *) ((*(m->mh->CellValue))(m->mh, m, row, params->ColorModelColumn)); - if (color == nil) - color = [NSColor controlTextColor]; - [tf setColor:color]; - // TODO release color -} - -@interface uiprivTextColumnCellView : uiprivColumnCellView { +struct textColumnCreateParams { uiTable *t; uiTableModel *m; + + BOOL makeTextField; + int textModelColumn; + int textEditableColumn; + uiTableTextColumnOptionalParams textParams; + + BOOL makeImage; + int imageModelColumn; + + BOOL makeCheckbox; + int checkboxModelColumn; + int checkboxEditableColumn; +}; + +@interface uiprivTextImageCheckboxColumnCellView : uiprivColumnCellView { + uiTable *t; + uiTableModel *m; + NSTextField *tf; - int modelColumn; - int editableColumn; - uiTableTextColumnOptionalParams params; + int textModelColumn; + int textEditableColumn; + uiTableTextColumnOptionalParams textParams; + + NSImageView *iv; + int imageModelColumn; + + NSButton *cb; + int checkboxModelColumn; + int checkboxEditableColumn; } -- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec params:(uiTableTextColumnOptionalParams *)p; -- (IBAction)uiprivOnAction:(id)sender; +- (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p; +- (IBAction)uiprivOnTextFieldAction:(id)sender; +- (IBAction)uiprivOnCheckboxAction:(id)sender; @end @implementation uiprivTextColumnCellView -- (id)initWithFrame:(NSRect)r ModelColumn:(int)mc editableColumn:(int)ec params:(uiTableTextColumnOptionalParams *)p +- (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p { self = [super initWithFrame:frame]; if (self) { - self->t = table; - self->m = model; - self->modelColumn = mc; - self->editableColumn = ec; - if (p != NULL) - params = *p; - else - params = defaultTextColumnOptionalParams; + NSView *left; + CGFloat leftConstant; + CGFloat leftTextConstant; - self->tf = uiprivNewLabel(@""); - // TODO set wrap and ellipsize modes? - [self->tf setTarget:self]; - [self->tf setAction:@selector(uiprivOnAction:)]; - [self addSubview:self->tf]; - layoutCellSubview(self, self->tf, - self, textColumnLeading, - self, textColumnTrailing, - YES); - - // take advantage of NSTableCellView-provided accessibility features - [self setTextField:self->tf]; - } - return self; -} - -- (void)dealloc -{ - [self->tf release]; - self->tf = nil; - [super dealloc]; -} - -- (void)uiprivUpdate:(NSInteger)row -{ - updateCellTextField(self->tf, row, self->m, - self->modelColumn, self->editableColumn, &(self->params)); -} - -- (IBAction)onAction:(id)sender -{ - NSInteger row; - const void *data; - - row = [self->t->tv rowForView:self->tf]; - data = [[self->tf stringValue] UTF8String]; - (*(self->m->mh->SetCellValue))(self->m->mh, self->m, - row, self->modelColumn, data); - // always refresh the value in case the model rejected it - [self uiprivUpdate:row]; -} - -@end - -xx TODO somehow merge this with the above -@interface uiprivImageTextColumnCellView : uiprivColumnCellView { - uiTable *t; - uiTableModel *m; - NSImageView *iv; - int modelColumn; - NSTextField *tf; - int textModelColumn; - int textEditableColumn; - uiTableTextColumnOptionalParams params; -} -- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc textModelColumn:(int)tmc editableColumn:(int)ec params:(uiTableTextColumnOptionalParams *)p; -- (IBAction)uiprivOnAction:(id)sender; -@end - -@implementation uiprivImageTextColumnCellView - -- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc textModelColumn:(int)tmc editableColumn:(int)ec params:(uiTableTextColumnOptionalParams *)p -{ - self = [super initWithFrame:frame]; - if (self) { - self->t = table; - self->m = model; - self->modelColumn = mc; - self->textModelColumn = tmc; - self->editableColumn = ec; - if (p != NULL) - params = *p; - else - params = defaultTextColumnOptionalParams; - - self->iv = [[NSImageView alloc] initWithFrame:NSZeroRect]; - [self->iv setImageFrameStyle:NSImageFrameNone]; - [self->iv setImageAlignment:NSImageAlignCenter]; - [self->iv setImageScaling:NSImageScaleProportionallyDown]; - [self->iv setAnimates:NO]; - [self->iv setEditable:NO]; - [self->iv addConstraint:uiprivMkConstraint(self->iv, NSLayoutAttributeWidth, - NSLayoutRelationEqual, - self->iv, NSLayoutAttributeHeight, - 1, 0, - @"uiTable image squareness constraint")]; - [self addSubview:self->iv]; + self->t = p->t; + self->m = p->m; self->tf = nil; - if (self->textModelColumn != -1) { + if (p->makeTextField) { + self->textModelColumn = p->textModelColumn; + self->textEditableColumn = p->textEditableColumn; + self->textParams = p->textParams; + self->tf = uiprivNewLabel(@""); // TODO set wrap and ellipsize modes? [self->tf setTarget:self]; - [self->tf setAction:@selector(uiprivOnAction:)]; + [self->tf setAction:@selector(uiprivOnTextFieldAction:)]; [self addSubview:self->tf]; - layoutCellSubview(self, self->iv, - self, imageColumnLeading, + } + + left = nil; + + self->iv = nil; + if (p->makeImageView) { + self->iv = [[NSImageView alloc] initWithFrame:NSZeroRect]; + [self->iv setImageFrameStyle:NSImageFrameNone]; + [self->iv setImageAlignment:NSImageAlignCenter]; + [self->iv setImageScaling:NSImageScaleProportionallyDown]; + [self->iv setAnimates:NO]; + [self->iv setEditable:NO]; + [self->iv addConstraint:uiprivMkConstraint(self->iv, NSLayoutAttributeWidth, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeHeight, + 1, 0, + @"uiTable image squareness constraint")]; + [self addSubview:self->iv]; + left = self->iv; + leftConstant = imageColumnLeading; + leftTextConstant = imageTextColumnLeading; + } + + self->cb = nil; + if (p->makeCheckbox) { + self->cb = [[NSButton alloc] initWithFrame:NSZeroRect]; + [self->cb setTitle:@""]; + [self->cb setButtonType:NSSwitchButton]; + // doesn't seem to have an associated bezel style + [self->cb setBordered:NO]; + [self->cb setTransparent:NO]; + uiDarwinSetControlFont(self->cb, NSRegularControlSize); + [self addSubview:self->cb]; + left = self->cb; + leftConstant = checkboxColumnLeading; + leftTextConstant = checkboxTextColumnLeading; + } + + if (self->tf != nil && left == nil) + layoutCellSubview(self, self->tf, + self, textColumnLeading, + self, textColumnTrailing, + YES); + else if (self->tf != nil) { + layoutCellSubview(self, left, + self, leftConstant, nil, 0, NO); layoutCellSubview(self, self->tf, - self, imageTextColumnLeading, + left, leftTextConstant, self, textColumnTrailing, YES); } else { - layoutCellSubview(self, self->iv, + layoutCellSubview(self, left, nil, 0, nil, 0, NO); [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeCenterX, NSLayoutRelationEqual, - self->iv, NSLayoutAttributeCenterX, + left, NSLayoutAttributeCenterX, 1, 0, - @"uiTable image centering constraint")]; + @"uiTable image/checkbox centering constraint")]; } // take advantage of NSTableCellView-provided accessibility features - [self setImageView:self->iv]; if (self->tf != nil) [self setTextField:self->tf]; + if (self->iv != nil) + [self setImageView:self->iv]; } return self; } - (void)dealloc { + if (self->cb != nil) { + [self->cb release]; + self->cb = nil; + } + if (self->iv != nil) { + [self->iv release]; + self->iv = nil; + } if (self->tf != nil) { [self->tf release]; self->tf = nil; } - [self->iv release]; - self->iv = nil; [super dealloc]; } - (void)uiprivUpdate:(NSInteger)row { void *data; + BOOL editable; - data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->modelColumn); - [self->iv setImage:uiprivImageNSImage((uiImage *) data)]; - if (self->tf != nil) - updateCellTextField(self->tf, row, self->m, - self->textModelColumn, self->editableColumn, &(self->params)); + if (self->tv != nil) { + NSString *str; + BOOL editable; + + data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textModelColumn); + str = uiprivToNSString((char *) data); + uiprivFree(data); + [self->tf setStringValue:str]; + + [self->tf setEditable:isCellEditable(self->m, row, self->textEditableColumn)]; + + color = nil; + if (self->textParams.ColorModelColumn != -1) + color = (NSColor *) ((*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textParams.ColorModelColumn)); + if (color == nil) + color = [NSColor controlTextColor]; + [self->tf setColor:color]; + // TODO release color + } + if (self->iv != nil) { + data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->imageModelColumn); + [self->iv setImage:uiprivImageNSImage((uiImage *) data)]; + } + if (self->cb != nil) { + data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->imageModelColumn); + if (TODO(data)) + [self->cb setState:NSOnState]; + else + [self->cb setState:NSOffState]; + + [self->cb setEditable:isCellEditable(self->m, row, self->checkboxEditableColumn)]; + } } -- (IBAction)onAction:(id)sender +- (IBAction)uiprivOnTextFieldAction:(id)sender { NSInteger row; const void *data; @@ -274,6 +277,21 @@ xx TODO somehow merge this with the above [self uiprivUpdate:row]; } +- (IBAction)uiprivOnCheckboxAction:(id)sender +{ + NSInteger row; + int val; + void *data; + + row = [self->t->tv rowForView:self->cb]; + val = [self->cb state] != NSOffState; + data = TODO(val); + (*(self->m->mh->SetCellValue))(self->m->mh, self->m, + row, self->checkboxModelColumn, data); + // always refresh the value in case the model rejected it + [self uiprivUpdate:row]; +} + @end void uiTableAppendTextColumn(uiTable *t, From 705bf2d9bfcd46860e5f120afcf3189d1fd14d2e Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 17:18:01 -0400 Subject: [PATCH 024/166] Planned a new system for transferring data between tables and models, based on uiAttribute. --- uitable.h | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/uitable.h b/uitable.h index 0f23c448..bd95cb53 100644 --- a/uitable.h +++ b/uitable.h @@ -8,24 +8,43 @@ _UI_EXTERN uiImage *uiNewImage(double width, double height); _UI_EXTERN void uiFreeImage(uiImage *i); _UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride); -typedef struct uiTableModel uiTableModel; -typedef struct uiTableModelHandler uiTableModelHandler; +typedef struct uiTableData uiTableData; + +_UI_EXTERN void uiFreeTableData(uiTableData *d); // TODO actually validate these -_UI_ENUM(uiTableModelColumnType) { - uiTableModelColumnString, - uiTableModelColumnImage, - uiTableModelColumnInt, - uiTableModelColumnColor, +_UI_ENUM(uiTableDataType) { + uiTableDataTypeString, + uiTableDataTypeImage, + uiTableDataTypeInt, + uiTableDataTypeColor, }; +// TODO I don't like this name +_UI_EXTERN uiTableDataType uiTableDataGetType(const uiTableData *d); + +_UI_EXTERN uiTableData *uiNewTableDataString(const char *str); +_UI_EXTERN const char *uiTableDataString(const uiTableData *d); + +_UI_EXTERN uiTableData *uiNewTableDataImage(uiImage *img); +_UI_EXTERN uiImage *uiTableDataImage(const uiTableData *d); + +_UI_EXTERN uiTableData *uiNewTableDataInt(int i); +_UI_EXTERN int uiTableDataInt(const uiTableData *d); + +_UI_EXTERN uiTableData *uiNewTableDataColor(double r, double g, double b, double a); +_UI_EXTERN void uiTableDataColor(const uiTableData *d, double *r, double *g, double *b, double *a); + +typedef struct uiTableModel uiTableModel; +typedef struct uiTableModelHandler uiTableModelHandler; + // TODO validate ranges; validate types on each getter/setter call (? table columns only?) struct uiTableModelHandler { int (*NumColumns)(uiTableModelHandler *, uiTableModel *); - uiTableModelColumnType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int); + uiTableDataType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int); int (*NumRows)(uiTableModelHandler *, uiTableModel *); - void *(*CellValue)(uiTableModelHandler *, uiTableModel *, int, int); - void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const void *); + uiTableData *(*CellValue)(uiTableModelHandler *, uiTableModel *, int, int); + void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const uiTableData *); }; _UI_EXTERN void *uiTableModelStrdup(const char *str); From 43b1a466695a9aeec5f77eafb3c56b29cd40a0fd Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 17:28:54 -0400 Subject: [PATCH 025/166] Wrote the common table data functions. --- common/CMakeLists.txt | 1 + common/tabledata.c | 105 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 common/tabledata.c diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 4ef742d6..23cf663b 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -12,6 +12,7 @@ list(APPEND _LIBUI_SOURCES common/opentype.c common/shouldquit.c common/table.c + common/tabledata.c common/userbugs.c common/utf.c ) diff --git a/common/tabledata.c b/common/tabledata.c new file mode 100644 index 00000000..25710bcf --- /dev/null +++ b/common/tabledata.c @@ -0,0 +1,105 @@ +// 3 june 2018 +#include "../ui.h" +#include "uipriv.h" + +struct uiTableData { + uiTableDataType type; + union { + char *str; + uiImage *img; + int i; + struct { + double r; + double g; + double b; + double a; + } color; + } u; +}; + +static uiTableData *newTableData(uiTableData type) +{ + uiTableData *d; + + d = uiprivNew(uiTableData); + d->type = type; + return d; +} + +void uiFreeAttribute(uiTableData *a) +{ + switch (d->type) { + case uiTableDataTypeString: + uiprivFree(d->u.str); + break; + } + uiprivFree(d); +} + +uiTableDataType uiTableDataGetType(const uiTableData *d) +{ + return d->type; +} + +uiTableData *uiNewTableDataString(const char *str) +{ + uiTableData *d; + + d = newTableData(uiTableDataTypeString); + d->u.str = (char *) uiprivAlloc((strlen(str) + 1) * sizeof (char), "char[] (uiTableData)"); + strcpy(d->u.str, str); + return d; +} + +const char *uiTableDataString(const uiTableData *d) +{ + return d->u.str; +} + +uiTableData *uiNewTableDataImage(uiImage *img) +{ + uiTableData *d; + + d = newTableData(uiTableDataTypeImage); + d->u.img = img; + return d; +} + +uiImage *uiTableDataImage(const uiTableData *d) +{ + return d->u.img; +} + +uiTableData *uiNewTableDataInt(int i) +{ + uiTableData *d; + + d = newTableData(uiTableDataTypeInt); + d->u.i = i; + return d; +} + +int uiTableDataInt(const uiTableData *d) +{ + return d->u.i; +} + +uiTableData *uiNewTableDataColor(double r, double g, double b, double a) +{ + uiTableData *d; + + d = newTableData(uiTableDataTypeColor); + d->u.color.r = r; + d->u.color.g = g; + d->u.color.b = b; + d->u.color.a = a; + return d; +} + +void uiTableDataColor(const uiTableData *d, double *r, double *g, double *b, double *a) +{ + *r = d->u.color.r; + *g = d->u.color.g; + *b = d->u.color.b; + *a = d->u.color.a; +} From c04f3d3fbe15c31d5a6b37317b89eb2f721b07f5 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 17:39:02 -0400 Subject: [PATCH 026/166] Adjusted tablecolumn.m to use the new data functions. --- darwin/tablecolumn.m | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 6c4f666d..94a92733 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -56,7 +56,8 @@ static void layoutCellSubview(NSView *superview, NSView *subview, NSView *leadin static BOOL isCellEditable(uiTableModel *m, NSInteger row, int modelColumn) { - void *data; + uiTableData *data; + int value; switch (modelColumn) { case uiTableModelColumnNeverEditable: @@ -65,7 +66,9 @@ static BOOL isCellEditable(uiTableModel *m, NSInteger row, int modelColumn) return YES; } data = (*(m->mh->CellValue))(m->mh, m, row, modelColumn); - return uiTableModelTakeInt(data) != 0; + value = uiTableDataInt(data); + uiFreeTableData(data); + return value != 0; // TODO free data } @@ -227,7 +230,7 @@ struct textColumnCreateParams { - (void)uiprivUpdate:(NSInteger)row { - void *data; + uiTableData *data; BOOL editable; if (self->tv != nil) { @@ -235,30 +238,41 @@ struct textColumnCreateParams { BOOL editable; data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textModelColumn); - str = uiprivToNSString((char *) data); - uiprivFree(data); + str = uiprivToNSString(uiTableDataString(data)); + uiFreeTableData(data); [self->tf setStringValue:str]; [self->tf setEditable:isCellEditable(self->m, row, self->textEditableColumn)]; color = nil; - if (self->textParams.ColorModelColumn != -1) - color = (NSColor *) ((*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textParams.ColorModelColumn)); + if (self->textParams.ColorModelColumn != -1) { + double r, g, b, a; + + data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textParams.ColorModelColumn); + uiTableDataColor(data, &r, &g, &b, &a); + uiFreeTableData(data); + xx TODO + } if (color == nil) color = [NSColor controlTextColor]; [self->tf setColor:color]; // TODO release color } if (self->iv != nil) { + uiImage *img; + data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->imageModelColumn); - [self->iv setImage:uiprivImageNSImage((uiImage *) data)]; + img = uiTableDataImage(data); + uiFreeTableData(data); + [self->iv setImage:uiprivImageNSImage(img)]; } if (self->cb != nil) { data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->imageModelColumn); - if (TODO(data)) + if (uiTableDataInt(data) != 0) [self->cb setState:NSOnState]; else [self->cb setState:NSOffState]; + uiFreeTableData(data); [self->cb setEditable:isCellEditable(self->m, row, self->checkboxEditableColumn)]; } @@ -267,12 +281,13 @@ struct textColumnCreateParams { - (IBAction)uiprivOnTextFieldAction:(id)sender { NSInteger row; - const void *data; + uiTableData *data; row = [self->t->tv rowForView:self->tf]; - data = [[self->tf stringValue] UTF8String]; + data = uiNewTableDataString([[self->tf stringValue] UTF8String]); (*(self->m->mh->SetCellValue))(self->m->mh, self->m, row, self->textModelColumn, data); + uiFreeTableData(data); // always refresh the value in case the model rejected it [self uiprivUpdate:row]; } @@ -280,14 +295,13 @@ struct textColumnCreateParams { - (IBAction)uiprivOnCheckboxAction:(id)sender { NSInteger row; - int val; void *data; row = [self->t->tv rowForView:self->cb]; - val = [self->cb state] != NSOffState; - data = TODO(val); + data = uiNewTableDataInt([self->cb state] != NSOffState); (*(self->m->mh->SetCellValue))(self->m->mh, self->m, row, self->checkboxModelColumn, data); + uiFreeTableData(data); // always refresh the value in case the model rejected it [self uiprivUpdate:row]; } From 54ca41afb6e4eef9b44b713c8acee5cedf742d17 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 17:57:17 -0400 Subject: [PATCH 027/166] Moved common/table.c out of the way for now; it's not relevant with the proposed colum changes and definite data changes. --- common/CMakeLists.txt | 2 +- common/{table.c => OLD_table.c} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename common/{table.c => OLD_table.c} (100%) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 23cf663b..99ceda09 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -11,7 +11,7 @@ list(APPEND _LIBUI_SOURCES common/matrix.c common/opentype.c common/shouldquit.c - common/table.c +# common/table.c common/tabledata.c common/userbugs.c common/utf.c diff --git a/common/table.c b/common/OLD_table.c similarity index 100% rename from common/table.c rename to common/OLD_table.c From b74b987fffbf21c97453b1024d363358e6cf01ec Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 18:34:29 -0400 Subject: [PATCH 028/166] Added progressbar columns. --- darwin/tablecolumn.m | 66 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 94a92733..2bb9828b 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -9,6 +9,8 @@ #define checkboxTextColumnLeading 0 // these aren't provided by IB; let's just choose one #define checkboxColumnLeading imageColumnLeading +#define progressBarColumnLeading imageColumnLeading +#define progressBarColumnTrailing progressBarColumnLeading static void layoutCellSubview(NSView *superview, NSView *subview, NSView *leading, CGFloat leadingConstant, NSView *trailing, CGFloat trailingConstant, BOOL stretchy) { @@ -308,6 +310,70 @@ struct textColumnCreateParams { @end +@interface uiprivProgressBarColumnCellView : uiprivColumnCellView { + uiTable *t; + uiTableModel *m; + NSProgressIndicator *p; + int modelColumn; +} +- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc; +@end + +@implementation uiprivProgressBarColumnCellView + +- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc +{ + self = [super initWithFrame:r]; + if (self) { + self->t = table; + self->m = model; + self->modelColumn = mc; + + self->p = [[NSProgressIndicator alloc] initWithFrame:NSZeroRect]; + [self->p setControlSize:NSRegularControlSize]; + [self->p setBezeled:YES]; + [self->p setStyle:NSProgressIndicatorBarStyle]; + layoutCellSubview(self, self->p, + self, progressBarColumnLeading, + self, progressBarColumnTrailing, + YES); + } + return self; +} + +- (void)dealloc +{ + [self->p release]; + self->p = nil; + [super dealloc]; +} + +- (void)uiprivUpdate:(NSInteger)row +{ + uiTableData *data; + int value; + + data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->modelColumn); + value = uiTableDataInt(data); + uiFreeTableData(data); + if (value == -1) { + [self->p setIndeterminate:YES]; + [self->p startAnimation:self->p]; + } else if (value == 100) { + [self->p setIndeterminate:NO]; + [self->p setMaxValue:101]; + [self->p setDoubleValue:101]; + [self->p setDoubleValue:100]; + [self->p setMaxValue:100]; + } else { + [self->p setIndeterminate:NO]; + [self->p setDoubleValue:(value + 1)]; + [self->p setDoubleValue:value]; + } +} + +@end + void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, From 8ee5c61fe822801521719b5c9c5dde39e1c592b5 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 19:21:01 -0400 Subject: [PATCH 029/166] And added button columns. Now to start writing the rest of the uiTable glue. --- darwin/tablecolumn.m | 67 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 2bb9828b..47d0b8fb 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -253,7 +253,7 @@ struct textColumnCreateParams { data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textParams.ColorModelColumn); uiTableDataColor(data, &r, &g, &b, &a); uiFreeTableData(data); - xx TODO + // TODO } if (color == nil) color = [NSColor controlTextColor]; @@ -374,6 +374,71 @@ struct textColumnCreateParams { @end +@interface uiprivButtonColumnCellView : uiprivColumnCellView { + uiTable *t; + uiTableModel *m; + NSButton *b; + int modelColumn; + int editableColumn; +} +- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec; +- (IBAction)uiprivOnClicked:(id)sender; +@end + +@implementation uiprivProgressBarColumnCellView + +- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec +{ + self = [super initWithFrame:r]; + if (self) { + self->t = table; + self->m = model; + self->modelColumn = mc; + self->editableColumn = ec; + + self->b = [[NSButton alloc] initWithFrame:NSZeroRect]; + [self->b setButtonType:NSMomentaryPushInButton]; + [self->b setBordered:YES]; + [self->b setBezelStyle:NSRoundRectBezelStyle]; + uiDarwinSetControlFont(self->b, NSRegularControlSize); + [self->b setTarget:self]; + [self->b setAction:@selector(uiprivOnClicked:)]; + layoutCellSubview(self, self->b, + self, progressBarColumnLeading, + self, progressBarColumnTrailing, + YES); + } + return self; +} + +- (void)dealloc +{ + [self->p release]; + self->p = nil; + [super dealloc]; +} + +- (void)uiprivUpdate:(NSInteger)row +{ + uiTableData *data; + NSString *str; + BOOL editable; + + data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->modelColumn); + str = uiprivToNSString(uiTableDataString(data)); + uiFreeTableData(data); + [self->b setTitle:str]; + + [self->b setEditable:isCellEditable(self->m, row, self->editableColumn)]; +} + +- (id)uiprivOnClicked:(id)sender +{ + // TODO +} + +@end + void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, From da2a4c1e3600c08a750d73e9ab7d2a749337c314 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 19:59:05 -0400 Subject: [PATCH 030/166] Started building back the uiTable implementation. --- darwin/table.m | 271 +++++++++++++++++++++++++++++++++++++++++++ darwin/tablecolumn.m | 24 ++++ 2 files changed, 295 insertions(+) create mode 100644 darwin/table.m diff --git a/darwin/table.m b/darwin/table.m new file mode 100644 index 00000000..62b465fc --- /dev/null +++ b/darwin/table.m @@ -0,0 +1,271 @@ +xx 3 june 2018 +#import "uipriv_darwin.h" + +@interface uiprivTableModel : NSObject { + uiTableModel *m; +} +- (id)initWithModel:(uiTableModel *)model; +@end + +struct uiTableModel { + uiTableModelHandler *mh; + uiprivTableModel *m; + NSMutableArray *tables; +}; + +struct uiTable { + uiDarwinControl c; + NSScrollView *sv; + NSTableView *tv; + uiprivScrollViewData *d; + int backgroundColumn; +}; + +@implementation tableModel + +- (id)initWithModel:(uiTableModel *)m +{ + self = [super init]; + if (self) + self->libui_m = m; + return self; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tv +{ + return (*(self->m->mh->NumRows))(self->m->mh, self->m); +} + + - (NSView *)tableView:(NSTableView *)tv viewForTableColumn:(NSTableColumn *)cc row:(NSInteger)row +{ + uiprivTableColumn *c = (uiprivTableColumn *) cc; + xx TODO consider renaming this type to uiprivTableCellView + uiprivColumnCellView *cv; + + cv = (uiprivColumnCellView *) [tv makeViewWithIdentifier:[c identifier] owner:self]; + if (cv == nil) + cv = [c uiprivMakeCellView]; + [cv uiprivUpdate:row]; + return cv; +} + +- (void)tableView:(NSTableView *)nstv didAddRowView:(NSTableRowView *)rv forRow:(NSInteger)row +{ + xx TODO set background color +} + +@end + +=================== TODOTODO + +uiTableModel *uiNewTableModel(uiTableModelHandler *mh) +{ + uiTableModel *m; + + m = uiprivNew(uiTableModel); + m->mh = mh; + m->m = [[tableModel alloc] initWithModel:m]; + m->tables = [NSMutableArray new]; + return m; +} + +void *uiTableModelGiveColor(double r, double g, double b, double a) +{ + return [[NSColor colorWithSRGBRed:r green:g blue:b alpha:a] retain]; +} + +void uiFreeTableModel(uiTableModel *m) +{ + if ([m->tables count] != 0) + uiprivUserBug("You cannot free a uiTableModel while uiTables are using it."); + [m->tables release]; + [m->m release]; + uiprivFree(m); +} + +void uiTableModelRowInserted(uiTableModel *m, int newIndex) +{ + NSTableView *tv; + NSIndexSet *set; + + set = [NSIndexSet indexSetWithIndex:newIndex]; + for (tv in m->tables) + [tv insertRowsAtIndexes:set withAnimation:NSTableViewAnimationEffectNone]; + // set is autoreleased +} + +void uiTableModelRowChanged(uiTableModel *m, int index) +{ + NSTableView *tv; + NSIndexSet *set, *cols; + + set = [NSIndexSet indexSetWithIndex:index]; + for (tv in m->tables) { + cols = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, [[tv tableColumns] count])]; + [tv reloadDataForRowIndexes:set columnIndexes:cols]; + // TODO this isn't enough + [cols release]; + } + // set is autoreleased +} + +void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) +{ + NSTableView *tv; + NSIndexSet *set; + + set = [NSIndexSet indexSetWithIndex:oldIndex]; + for (tv in m->tables) + [tv removeRowsAtIndexes:set withAnimation:NSTableViewAnimationEffectNone]; + // set is autoreleased +} + +void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) +{ + tablePart *part; + + part = [tablePart new]; + part.type = partText; + part.textColumn = modelColumn; + part.expand = expand; + [c->parts addObject:part]; +} + +void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) +{ + tablePart *part; + + part = [tablePart new]; + part.type = partImage; + part.imageColumn = modelColumn; + part.expand = expand; + [c->parts addObject:part]; +} + +void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand) +{ + tablePart *part; + + part = [tablePart new]; + part.type = partButton; + part.textColumn = modelColumn; + part.expand = expand; + part.editable = 1; // editable by default + [c->parts addObject:part]; +} + +void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) +{ + tablePart *part; + + part = [tablePart new]; + part.type = partCheckbox; + part.valueColumn = modelColumn; + part.expand = expand; + part.editable = 1; // editable by default + [c->parts addObject:part]; +} + +void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand) +{ + tablePart *part; + + part = [tablePart new]; + part.type = partProgressBar; + part.valueColumn = modelColumn; + part.expand = expand; + [c->parts addObject:part]; +} + +void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) +{ + tablePart *p; + + p = (tablePart *) [c->parts objectAtIndex:part]; + p.editable = editable; +} + +void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) +{ + tablePart *p; + + p = (tablePart *) [c->parts objectAtIndex:part]; + p.textColorColumn = modelColumn; +} + +uiDarwinControlAllDefaultsExceptDestroy(uiTable, sv) + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + + // TODO + [t->sv release]; + uiFreeControl(uiControl(t)); +} + +uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) +{ + uiTableColumn *c; + + c = uiprivNew(uiTableColumn); + c->c = [[tableColumn alloc] initWithIdentifier:@""]; + c->c.libui_col = c; + // via Interface Builder + [c->c setResizingMask:(NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask)]; + // 10.10 adds -[NSTableColumn setTitle:]; before then we have to do this + [[c->c headerCell] setStringValue:uiprivToNSString(name)]; + // TODO is this sufficient? + [[c->c headerCell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]]; + c->parts = [NSMutableArray new]; + [t->tv addTableColumn:c->c]; + return c; +} + +void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) +{ + t->backgroundColumn = modelColumn; +} + +uiTable *uiNewTable(uiTableModel *model) +{ + uiTable *t; + uiprivScrollViewCreateParams p; + + uiDarwinNewControl(uiTable, t); + + t->tv = [[tableView alloc] initWithFrame:NSZeroRect]; + t->tv.libui_t = t; + + [t->tv setDataSource:model->m]; + [t->tv setDelegate:model->m]; + [t->tv reloadData]; + [model->tables addObject:t->tv]; + + // TODO is this sufficient? + [t->tv setAllowsColumnReordering:NO]; + [t->tv setAllowsColumnResizing:YES]; + [t->tv setAllowsMultipleSelection:NO]; + [t->tv setAllowsEmptySelection:YES]; + [t->tv setAllowsColumnSelection:NO]; + [t->tv setUsesAlternatingRowBackgroundColors:YES]; + [t->tv setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleRegular]; + [t->tv setGridStyleMask:NSTableViewGridNone]; + [t->tv setAllowsTypeSelect:YES]; + // TODO floatsGroupRows — do we even allow group rows? + + memset(&p, 0, sizeof (uiprivScrollViewCreateParams)); + p.DocumentView = t->tv; + // this is what Interface Builder sets it to + // TODO verify + p.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0]; + p.DrawsBackground = YES; + p.Bordered = YES; + p.HScroll = YES; + p.VScroll = YES; + t->sv = uiprivMkScrollView(&p, &(t->d)); + + t->backgroundColumn = -1; + + return t; +} diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 47d0b8fb..cb7624b3 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -47,6 +47,13 @@ static void layoutCellSubview(NSView *superview, NSView *subview, NSView *leadin - (void)uiprivUpdate:(NSInteger)row; @end +@interface uiprivTableColumn : NSTableColumn { + uiprivColumnCellView *(^mkCell)(void); +} +- (id)initWithIdentifier:(NSUserInterfaceItemIdentifier)ident mkCellView:(uiprivColumnCellView *(^)(void))f; +- (uiprivColumnCellView *)uiprivMakeCellView; +@end + @implementation uiprivColumnCellView - (void)uiprivUpdate:(NSInteger)row @@ -56,6 +63,23 @@ static void layoutCellSubview(NSView *superview, NSView *subview, NSView *leadin @end +@implementation uiprivTableColumn + +- (id)initWithIdentifier:(NSUserInterfaceItemIdentifier)ident mkCellView:(uiprivColumnCellView *(^)(void))f +{ + self = [super initWithIdentifier:ident]; + if (self) + self->mkCell = f; + return self; +} + +- (uiprivColumnCellView *)uiprivMakeCellView +{ + return (self->mkCell)(); +} + +@end + static BOOL isCellEditable(uiTableModel *m, NSInteger row, int modelColumn) { uiTableData *data; From 247d63be60587c5ced582a817ba386b953a89990 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 20:55:08 -0400 Subject: [PATCH 031/166] Added a table.h and simplified uiprivTableColumn into an abstract interface. --- darwin/table.h | 25 +++++++++++++++++++++++++ darwin/table.m | 19 +++---------------- darwin/tablecolumn.m | 38 ++++++++++---------------------------- 3 files changed, 38 insertions(+), 44 deletions(-) create mode 100644 darwin/table.h diff --git a/darwin/table.h b/darwin/table.h new file mode 100644 index 00000000..dbc26520 --- /dev/null +++ b/darwin/table.h @@ -0,0 +1,25 @@ +// 3 june 2018 + +// table.m +// TODO get rid of forward declaration +@class uiprivTableModel; +struct uiTableModel { + uiTableModelHandler *mh; + uiprivTableModel *m; + NSMutableArray *tables; +}; +struct uiTable { + uiDarwinControl c; + NSScrollView *sv; + NSTableView *tv; + uiprivScrollViewData *d; + int backgroundColumn; +}; + +// tablecolumn.m +@interface uiprivTableCellView : NSTableCellView +- (void)uiprivUpdate:(NSInteger)row; +@end +@interface uiprivTableColumn : NSTableColumn +- (uiprivColumnCellView *)uiprivMakeCellView; +@end diff --git a/darwin/table.m b/darwin/table.m index 62b465fc..ac2aef63 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -1,5 +1,6 @@ -xx 3 june 2018 +// 3 june 2018 #import "uipriv_darwin.h" +#import "table.h" @interface uiprivTableModel : NSObject { uiTableModel *m; @@ -7,21 +8,7 @@ xx 3 june 2018 - (id)initWithModel:(uiTableModel *)model; @end -struct uiTableModel { - uiTableModelHandler *mh; - uiprivTableModel *m; - NSMutableArray *tables; -}; - -struct uiTable { - uiDarwinControl c; - NSScrollView *sv; - NSTableView *tv; - uiprivScrollViewData *d; - int backgroundColumn; -}; - -@implementation tableModel +@implementation uiprivTableModel - (id)initWithModel:(uiTableModel *)m { diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index cb7624b3..e01fb546 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -1,5 +1,6 @@ // 3 june 2018 #import "uipriv_darwin.h" +#import "table.h" // values from interface builder #define textColumnLeading 2 @@ -43,18 +44,7 @@ static void layoutCellSubview(NSView *superview, NSView *subview, NSView *leadin @"uiTable cell subview bottom constraint")]; } -@interface uiprivColumnCellView : NSTableCellView -- (void)uiprivUpdate:(NSInteger)row; -@end - -@interface uiprivTableColumn : NSTableColumn { - uiprivColumnCellView *(^mkCell)(void); -} -- (id)initWithIdentifier:(NSUserInterfaceItemIdentifier)ident mkCellView:(uiprivColumnCellView *(^)(void))f; -- (uiprivColumnCellView *)uiprivMakeCellView; -@end - -@implementation uiprivColumnCellView +@implementation uiprivTableCellView - (void)uiprivUpdate:(NSInteger)row { @@ -65,17 +55,9 @@ static void layoutCellSubview(NSView *superview, NSView *subview, NSView *leadin @implementation uiprivTableColumn -- (id)initWithIdentifier:(NSUserInterfaceItemIdentifier)ident mkCellView:(uiprivColumnCellView *(^)(void))f +- (uiprivTableCellView *)uiprivMakeCellView { - self = [super initWithIdentifier:ident]; - if (self) - self->mkCell = f; - return self; -} - -- (uiprivColumnCellView *)uiprivMakeCellView -{ - return (self->mkCell)(); + [self doesNotRecognizeSelector:_cmd]; } @end @@ -119,7 +101,7 @@ struct textColumnCreateParams { int checkboxEditableColumn; }; -@interface uiprivTextImageCheckboxColumnCellView : uiprivColumnCellView { +@interface uiprivTextImageCheckboxTableCellView : uiprivTableCellView { uiTable *t; uiTableModel *m; @@ -140,7 +122,7 @@ struct textColumnCreateParams { - (IBAction)uiprivOnCheckboxAction:(id)sender; @end -@implementation uiprivTextColumnCellView +@implementation uiprivTextTableCellView - (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p { @@ -334,7 +316,7 @@ struct textColumnCreateParams { @end -@interface uiprivProgressBarColumnCellView : uiprivColumnCellView { +@interface uiprivProgressBarTableCellView : uiprivTableCellView { uiTable *t; uiTableModel *m; NSProgressIndicator *p; @@ -343,7 +325,7 @@ struct textColumnCreateParams { - (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc; @end -@implementation uiprivProgressBarColumnCellView +@implementation uiprivProgressBarTableCellView - (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc { @@ -398,7 +380,7 @@ struct textColumnCreateParams { @end -@interface uiprivButtonColumnCellView : uiprivColumnCellView { +@interface uiprivButtonTableCellView : uiprivTableCellView { uiTable *t; uiTableModel *m; NSButton *b; @@ -409,7 +391,7 @@ struct textColumnCreateParams { - (IBAction)uiprivOnClicked:(id)sender; @end -@implementation uiprivProgressBarColumnCellView +@implementation uiprivProgressBarTableCellView - (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec { From 6457e1668fbeeff2af228137fa90bb0e37adc326 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 21:39:49 -0400 Subject: [PATCH 032/166] Rewrote uiTableModelRowChanged() to properly update rows. We can do this now that we have the setup for row updates and reuse, but this is better than calling reloadData anyway (and reloadData doesn't update the row view, but we (almost) do, so...)... --- darwin/table.m | 33 +++++++++++++++++---------------- darwin/tablecolumn.m | 4 ++-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/darwin/table.m b/darwin/table.m index ac2aef63..70b11c3e 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -26,7 +26,7 @@ - (NSView *)tableView:(NSTableView *)tv viewForTableColumn:(NSTableColumn *)cc row:(NSInteger)row { uiprivTableColumn *c = (uiprivTableColumn *) cc; - xx TODO consider renaming this type to uiprivTableCellView + // TODO consider renaming this type to uiprivTableCellView uiprivColumnCellView *cv; cv = (uiprivColumnCellView *) [tv makeViewWithIdentifier:[c identifier] owner:self]; @@ -43,24 +43,17 @@ @end -=================== TODOTODO - uiTableModel *uiNewTableModel(uiTableModelHandler *mh) { uiTableModel *m; m = uiprivNew(uiTableModel); m->mh = mh; - m->m = [[tableModel alloc] initWithModel:m]; + m->m = [[uiprivTableModel alloc] initWithModel:m]; m->tables = [NSMutableArray new]; return m; } -void *uiTableModelGiveColor(double r, double g, double b, double a) -{ - return [[NSColor colorWithSRGBRed:r green:g blue:b alpha:a] retain]; -} - void uiFreeTableModel(uiTableModel *m) { if ([m->tables count] != 0) @@ -84,16 +77,22 @@ void uiTableModelRowInserted(uiTableModel *m, int newIndex) void uiTableModelRowChanged(uiTableModel *m, int index) { NSTableView *tv; - NSIndexSet *set, *cols; + NSTableRowView *rv; + NSUInteger i, n; + uiprivTableColumnView *cv; - set = [NSIndexSet indexSetWithIndex:index]; for (tv in m->tables) { - cols = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, [[tv tableColumns] count])]; - [tv reloadDataForRowIndexes:set columnIndexes:cols]; - // TODO this isn't enough - [cols release]; + rv = [tv rowViewForRow:index makeIfNecessary:NO]; + if (rv != nil) { + xx TODO update colors + } + n = [[tv tableColumns] count]; + for (i = 0; i < n; i++) { + cv = (uiprivTableCellView *) [tv viewForColumn:i row:index makeIfNecessary:NO]; + if (cv != nil) + [cv uiprivUpdate:index]; + } } - // set is autoreleased } void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) @@ -107,6 +106,8 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) // set is autoreleased } +=================== TODOTODO + void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) { tablePart *part; diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index e01fb546..01ff5b27 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -259,12 +259,12 @@ struct textColumnCreateParams { data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textParams.ColorModelColumn); uiTableDataColor(data, &r, &g, &b, &a); uiFreeTableData(data); - // TODO + color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a]; } if (color == nil) color = [NSColor controlTextColor]; [self->tf setColor:color]; - // TODO release color + // we don't own color in ether case; don't release } if (self->iv != nil) { uiImage *img; From 8fc4a9aaf561741d2ec23f756a07117c860cfb6a Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 22:37:36 -0400 Subject: [PATCH 033/166] Filled in the rest of tablecolumn.m: wrote the NSTableColumn subclasses and implemented the public functions. Let's build and see what breaks! --- darwin/table.h | 1 + darwin/table.m | 98 +---------------- darwin/tablecolumn.m | 246 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 218 insertions(+), 127 deletions(-) diff --git a/darwin/table.h b/darwin/table.h index dbc26520..0871a740 100644 --- a/darwin/table.h +++ b/darwin/table.h @@ -14,6 +14,7 @@ struct uiTable { NSTableView *tv; uiprivScrollViewData *d; int backgroundColumn; + uiTableModel *m; }; // tablecolumn.m diff --git a/darwin/table.m b/darwin/table.m index 70b11c3e..b046c6db 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -106,81 +106,6 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) // set is autoreleased } -=================== TODOTODO - -void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) -{ - tablePart *part; - - part = [tablePart new]; - part.type = partText; - part.textColumn = modelColumn; - part.expand = expand; - [c->parts addObject:part]; -} - -void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) -{ - tablePart *part; - - part = [tablePart new]; - part.type = partImage; - part.imageColumn = modelColumn; - part.expand = expand; - [c->parts addObject:part]; -} - -void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand) -{ - tablePart *part; - - part = [tablePart new]; - part.type = partButton; - part.textColumn = modelColumn; - part.expand = expand; - part.editable = 1; // editable by default - [c->parts addObject:part]; -} - -void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) -{ - tablePart *part; - - part = [tablePart new]; - part.type = partCheckbox; - part.valueColumn = modelColumn; - part.expand = expand; - part.editable = 1; // editable by default - [c->parts addObject:part]; -} - -void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand) -{ - tablePart *part; - - part = [tablePart new]; - part.type = partProgressBar; - part.valueColumn = modelColumn; - part.expand = expand; - [c->parts addObject:part]; -} - -void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) -{ - tablePart *p; - - p = (tablePart *) [c->parts objectAtIndex:part]; - p.editable = editable; -} - -void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) -{ - tablePart *p; - - p = (tablePart *) [c->parts objectAtIndex:part]; - p.textColorColumn = modelColumn; -} - uiDarwinControlAllDefaultsExceptDestroy(uiTable, sv) static void uiTableDestroy(uiControl *c) @@ -192,27 +117,10 @@ static void uiTableDestroy(uiControl *c) uiFreeControl(uiControl(t)); } -uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) -{ - uiTableColumn *c; - - c = uiprivNew(uiTableColumn); - c->c = [[tableColumn alloc] initWithIdentifier:@""]; - c->c.libui_col = c; - // via Interface Builder - [c->c setResizingMask:(NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask)]; - // 10.10 adds -[NSTableColumn setTitle:]; before then we have to do this - [[c->c headerCell] setStringValue:uiprivToNSString(name)]; - // TODO is this sufficient? - [[c->c headerCell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]]; - c->parts = [NSMutableArray new]; - [t->tv addTableColumn:c->c]; - return c; -} - void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) { t->backgroundColumn = modelColumn; + // TODO update all rows } uiTable *uiNewTable(uiTableModel *model) @@ -221,9 +129,9 @@ uiTable *uiNewTable(uiTableModel *model) uiprivScrollViewCreateParams p; uiDarwinNewControl(uiTable, t); + t->m = model; - t->tv = [[tableView alloc] initWithFrame:NSZeroRect]; - t->tv.libui_t = t; + t->tv = [[NSTableView alloc] initWithFrame:NSZeroRect]; [t->tv setDataSource:model->m]; [t->tv setDelegate:model->m]; diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 01ff5b27..339d4a6a 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -316,6 +316,33 @@ struct textColumnCreateParams { @end +@interface uiprivTextImageCheckboxTableColumn : uiprivTableColumn { + struct textColumnCreateParams params; +} +- (id)initWithIdentifier:(NSString *)ident params:(struct textColumnCreateParams *)p; +@end + +@implementation uiprivTextImageCheckboxTableColumn + +- (id)initWithIdentifier:(NSString *)ident params:(struct textColumnCreateParams *)p +{ + self = [super initWithIdentifier:ident]; + if (self) + self->params = *p; + return self; +} + +- (uiprivColumnCellView *)uiprivMakeCellView +{ + uiprivColumnCellView *cv; + + cv = [[uiprivTextImageCheckboxTableCellView alloc] initWithFrame:NSZeroRect params:&(self->params)]; + [cv setIdentifier:[self identifier]]; + return cv; +} + +@end + @interface uiprivProgressBarTableCellView : uiprivTableCellView { uiTable *t; uiTableModel *m; @@ -380,6 +407,33 @@ struct textColumnCreateParams { @end +@interface uiprivProgressBarTableColumn : uiprivTableColumn { + int modelColumn; +} +- (id)initWithIdentifier:(NSString *)ident modelColumn:(int)mc; +@end + +@implementation uiprivProgressBarTableColumn + +- (id)initWithIdentifier:(NSString *)ident modelColumn:(int)mc +{ + self = [super initWithIdentifier:ident]; + if (self) + self->modelColumn = mc; + return self; +} + +- (uiprivColumnCellView *)uiprivMakeCellView +{ + uiprivColumnCellView *cv; + + cv = [[uiprivProgressBarTableCellView alloc] initWithFrame:NSZeroRect modelColumn:self->modelColumn]; + [cv setIdentifier:[self identifier]]; + return cv; +} + +@end + @interface uiprivButtonTableCellView : uiprivTableCellView { uiTable *t; uiTableModel *m; @@ -445,41 +499,169 @@ struct textColumnCreateParams { @end -void uiTableAppendTextColumn(uiTable *t, - const char *name, - int textModelColumn, - int textEditableModelColumn, - uiTableTextColumnOptionalParams *params); +@interface uiprivButtonTableColumn : uiprivTableColumn { + int modelColumn; + int editableColumn; +} +- (id)initWithIdentifier:(NSString *)ident modelColumn:(int)mc editableColumn:(int)ec; +@end -void uiTableAppendImageColumn(uiTable *t, - const char *name, - int imageModelColumn); +@implementation uiprivButtonTableColumn -void uiTableAppendImageTextColumn(uiTable *t, - const char *name, - int imageModelColumn, - int textModelColumn, - int textEditableModelColumn, - uiTableTextColumnOptionalParams *textParams); +- (id)initWithIdentifier:(NSString *)ident modelColumn:(int)mc editableColumn:(int)ec +{ + self = [super initWithIdentifier:ident]; + if (self) { + self->modelColumn = mc; + self->editableColumn = ec; + } + return self; +} -void uiTableAppendCheckboxColumn(uiTable *t, - const char *name, - int checkboxModelColumn, - int checkboxEditableModelColumn); +- (uiprivColumnCellView *)uiprivMakeCellView +{ + uiprivColumnCellView *cv; -void uiTableAppendCheckboxTextColumn(uiTable *t, - const char *name, - int checkboxModelColumn, - int checkboxEditableModelColumn, - int textModelColumn, - int textEditableModelColumn, - uiTableTextColumnOptionalParams *textParams); + cv = [[uiprivButtonTableCellView alloc] initWithFrame:NSZeroRect modelColumn:self->modelColumn editableColumn:self->editableColumn]; + [cv setIdentifier:[self identifier]]; + return cv; +} -void uiTableAppendProgressBarColumn(uiTable *t, - const char *name, - int progressModelColumn); +@end -void uiTableAppendButtonColumn(uiTable *t, - const char *name, - int buttonTextModelColumn, - int buttonClickableModelColumn); +void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *params) +{ + struct textColumnCreateParams p; + uiprivTableColumn *col; + NSString *str; + + memset(&p, 0, sizeof (struct textColumnCreateParams)); + p.t = t; + p.m = t->m; + + p.makeTextField = YES; + p.textModelColumn = textModelColumn; + p.textEditableModelColumn = textEditableModelColumn; + if (params == NULL) + params = &defaultTextColumnOptionalParams; + p.textParams = *params; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColum alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + return col; +} + +void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) +{ + struct textColumnCreateParams p; + uiprivTableColumn *col; + NSString *str; + + memset(&p, 0, sizeof (struct textColumnCreateParams)); + p.t = t; + p.m = t->m; + + p.makeImage = YES; + p.imageModelColumn = imageModelColumn; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColum alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + return col; +} + +void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + struct textColumnCreateParams p; + uiprivTableColumn *col; + NSString *str; + + memset(&p, 0, sizeof (struct textColumnCreateParams)); + p.t = t; + p.m = t->m; + + p.makeTextField = YES; + p.textModelColumn = textModelColumn; + p.textEditableModelColumn = textEditableModelColumn; + if (params == NULL) + params = &defaultTextColumnOptionalParams; + p.textParams = *params; + + p.makeImage = YES; + p.imageModelColumn = imageModelColumn; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColum alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + return col; +} + +void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) +{ + struct textColumnCreateParams p; + uiprivTableColumn *col; + NSString *str; + + memset(&p, 0, sizeof (struct textColumnCreateParams)); + p.t = t; + p.m = t->m; + + p.makeCheckbox = YES; + p.checkboxModelColumn = checkboxModelColumn; + p.checkboxEditableColumn = checkboxEditableColumn; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColum alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + return col; +} + +void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + struct textColumnCreateParams p; + uiprivTableColumn *col; + NSString *str; + + memset(&p, 0, sizeof (struct textColumnCreateParams)); + p.t = t; + p.m = t->m; + + p.makeTextField = YES; + p.textModelColumn = textModelColumn; + p.textEditableModelColumn = textEditableModelColumn; + if (params == NULL) + params = &defaultTextColumnOptionalParams; + p.textParams = *params; + + p.makeCheckbox = YES; + p.checkboxModelColumn = checkboxModelColumn; + p.checkboxEditableColumn = checkboxEditableColumn; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColum alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + return col; +} + +void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) +{ + uiprivTableColumn *col; + NSString *str; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivProgressBarTableColum alloc] initWithIdentifier:str modelColumn:progressModelColumn]; + [col setTitle:str]; + return col; +} + +void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModelColumn, int buttonClickableModelColumn) +{ + uiprivTableColumn *col; + NSString *str; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivButtonTableColum alloc] initWithIdentifier:str modelColumn:buttonTextModelColumn editableColumn:buttonClickableModelColumn]; + [col setTitle:str]; + return col; +} From 2b428d50f6fc5135c6618f82c194004ece358827 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 23:06:44 -0400 Subject: [PATCH 034/166] And fixed build errors. Now I just need to rewrite the tester and try this out... --- common/tabledata.c | 4 +- darwin/CMakeLists.txt | 1 + darwin/table.h | 2 +- darwin/table.m | 18 +++---- darwin/tablecolumn.m | 114 +++++++++++++++++++++++------------------- 5 files changed, 75 insertions(+), 64 deletions(-) diff --git a/common/tabledata.c b/common/tabledata.c index 25710bcf..e66d4b4b 100644 --- a/common/tabledata.c +++ b/common/tabledata.c @@ -17,7 +17,7 @@ struct uiTableData { } u; }; -static uiTableData *newTableData(uiTableData type) +static uiTableData *newTableData(uiTableDataType type) { uiTableData *d; @@ -26,7 +26,7 @@ static uiTableData *newTableData(uiTableData type) return d; } -void uiFreeAttribute(uiTableData *a) +void uiFreeTableData(uiTableData *d) { switch (d->type) { case uiTableDataTypeString: diff --git a/darwin/CMakeLists.txt b/darwin/CMakeLists.txt index a262064e..bd7d576a 100644 --- a/darwin/CMakeLists.txt +++ b/darwin/CMakeLists.txt @@ -44,6 +44,7 @@ list(APPEND _LIBUI_SOURCES darwin/stddialogs.m darwin/tab.m darwin/table.m + darwin/tablecolumn.m darwin/text.m darwin/undocumented.m darwin/util.m diff --git a/darwin/table.h b/darwin/table.h index 0871a740..b38e9378 100644 --- a/darwin/table.h +++ b/darwin/table.h @@ -22,5 +22,5 @@ struct uiTable { - (void)uiprivUpdate:(NSInteger)row; @end @interface uiprivTableColumn : NSTableColumn -- (uiprivColumnCellView *)uiprivMakeCellView; +- (uiprivTableCellView *)uiprivMakeCellView; @end diff --git a/darwin/table.m b/darwin/table.m index b046c6db..5a7e7500 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -10,11 +10,11 @@ @implementation uiprivTableModel -- (id)initWithModel:(uiTableModel *)m +- (id)initWithModel:(uiTableModel *)model { self = [super init]; if (self) - self->libui_m = m; + self->m = model; return self; } @@ -27,9 +27,9 @@ { uiprivTableColumn *c = (uiprivTableColumn *) cc; // TODO consider renaming this type to uiprivTableCellView - uiprivColumnCellView *cv; + uiprivTableCellView *cv; - cv = (uiprivColumnCellView *) [tv makeViewWithIdentifier:[c identifier] owner:self]; + cv = (uiprivTableCellView *) [tv makeViewWithIdentifier:[c identifier] owner:self]; if (cv == nil) cv = [c uiprivMakeCellView]; [cv uiprivUpdate:row]; @@ -38,7 +38,7 @@ - (void)tableView:(NSTableView *)nstv didAddRowView:(NSTableRowView *)rv forRow:(NSInteger)row { - xx TODO set background color + // TODO set background color } @end @@ -79,16 +79,16 @@ void uiTableModelRowChanged(uiTableModel *m, int index) NSTableView *tv; NSTableRowView *rv; NSUInteger i, n; - uiprivTableColumnView *cv; + uiprivTableCellView *cv; for (tv in m->tables) { - rv = [tv rowViewForRow:index makeIfNecessary:NO]; + rv = [tv rowViewAtRow:index makeIfNecessary:NO]; if (rv != nil) { - xx TODO update colors + // TODO update colors } n = [[tv tableColumns] count]; for (i = 0; i < n; i++) { - cv = (uiprivTableCellView *) [tv viewForColumn:i row:index makeIfNecessary:NO]; + cv = (uiprivTableCellView *) [tv viewAtColumn:i row:index makeIfNecessary:NO]; if (cv != nil) [cv uiprivUpdate:index]; } diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 339d4a6a..fe71c79e 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -58,6 +58,7 @@ static void layoutCellSubview(NSView *superview, NSView *subview, NSView *leadin - (uiprivTableCellView *)uiprivMakeCellView { [self doesNotRecognizeSelector:_cmd]; + return nil; // appease compiler } @end @@ -122,11 +123,11 @@ struct textColumnCreateParams { - (IBAction)uiprivOnCheckboxAction:(id)sender; @end -@implementation uiprivTextTableCellView +@implementation uiprivTextImageCheckboxTableCellView - (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p { - self = [super initWithFrame:frame]; + self = [super initWithFrame:r]; if (self) { NSView *left; CGFloat leftConstant; @@ -151,7 +152,8 @@ struct textColumnCreateParams { left = nil; self->iv = nil; - if (p->makeImageView) { + // TODO rename to makeImageView + if (p->makeImage) { self->iv = [[NSImageView alloc] initWithFrame:NSZeroRect]; [self->iv setImageFrameStyle:NSImageFrameNone]; [self->iv setImageAlignment:NSImageAlignCenter]; @@ -239,11 +241,10 @@ struct textColumnCreateParams { - (void)uiprivUpdate:(NSInteger)row { uiTableData *data; - BOOL editable; - if (self->tv != nil) { + if (self->tf != nil) { NSString *str; - BOOL editable; + NSColor *color; data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textModelColumn); str = uiprivToNSString(uiTableDataString(data)); @@ -263,7 +264,7 @@ struct textColumnCreateParams { } if (color == nil) color = [NSColor controlTextColor]; - [self->tf setColor:color]; + [self->tf setTextColor:color]; // we don't own color in ether case; don't release } if (self->iv != nil) { @@ -282,7 +283,7 @@ struct textColumnCreateParams { [self->cb setState:NSOffState]; uiFreeTableData(data); - [self->cb setEditable:isCellEditable(self->m, row, self->checkboxEditableColumn)]; + [self->cb setEnabled:isCellEditable(self->m, row, self->checkboxEditableColumn)]; } } @@ -332,9 +333,9 @@ struct textColumnCreateParams { return self; } -- (uiprivColumnCellView *)uiprivMakeCellView +- (uiprivTableCellView *)uiprivMakeCellView { - uiprivColumnCellView *cv; + uiprivTableCellView *cv; cv = [[uiprivTextImageCheckboxTableCellView alloc] initWithFrame:NSZeroRect params:&(self->params)]; [cv setIdentifier:[self identifier]]; @@ -408,26 +409,32 @@ struct textColumnCreateParams { @end @interface uiprivProgressBarTableColumn : uiprivTableColumn { + uiTable *t; + // TODO remove the need for this given t (or make t not require m, one of the two) + uiTableModel *m; int modelColumn; } -- (id)initWithIdentifier:(NSString *)ident modelColumn:(int)mc; +- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc; @end @implementation uiprivProgressBarTableColumn -- (id)initWithIdentifier:(NSString *)ident modelColumn:(int)mc +- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc { self = [super initWithIdentifier:ident]; - if (self) + if (self) { + self->t = table; + self->m = model; self->modelColumn = mc; + } return self; } -- (uiprivColumnCellView *)uiprivMakeCellView +- (uiprivTableCellView *)uiprivMakeCellView { - uiprivColumnCellView *cv; + uiprivTableCellView *cv; - cv = [[uiprivProgressBarTableCellView alloc] initWithFrame:NSZeroRect modelColumn:self->modelColumn]; + cv = [[uiprivProgressBarTableCellView alloc] initWithFrame:NSZeroRect table:self->t model:self->m modelColumn:self->modelColumn]; [cv setIdentifier:[self identifier]]; return cv; } @@ -445,7 +452,7 @@ struct textColumnCreateParams { - (IBAction)uiprivOnClicked:(id)sender; @end -@implementation uiprivProgressBarTableCellView +@implementation uiprivButtonTableCellView - (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec { @@ -473,8 +480,8 @@ struct textColumnCreateParams { - (void)dealloc { - [self->p release]; - self->p = nil; + [self->b release]; + self->b = nil; [super dealloc]; } @@ -482,17 +489,16 @@ struct textColumnCreateParams { { uiTableData *data; NSString *str; - BOOL editable; data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->modelColumn); str = uiprivToNSString(uiTableDataString(data)); uiFreeTableData(data); [self->b setTitle:str]; - [self->b setEditable:isCellEditable(self->m, row, self->editableColumn)]; + [self->b setEnabled:isCellEditable(self->m, row, self->editableColumn)]; } -- (id)uiprivOnClicked:(id)sender +- (IBAction)uiprivOnClicked:(id)sender { // TODO } @@ -500,29 +506,33 @@ struct textColumnCreateParams { @end @interface uiprivButtonTableColumn : uiprivTableColumn { + uiTable *t; + uiTableModel *m; int modelColumn; int editableColumn; } -- (id)initWithIdentifier:(NSString *)ident modelColumn:(int)mc editableColumn:(int)ec; +- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec; @end @implementation uiprivButtonTableColumn -- (id)initWithIdentifier:(NSString *)ident modelColumn:(int)mc editableColumn:(int)ec +- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec { self = [super initWithIdentifier:ident]; if (self) { + self->t = table; + self->m = model; self->modelColumn = mc; self->editableColumn = ec; } return self; } -- (uiprivColumnCellView *)uiprivMakeCellView +- (uiprivTableCellView *)uiprivMakeCellView { - uiprivColumnCellView *cv; + uiprivTableCellView *cv; - cv = [[uiprivButtonTableCellView alloc] initWithFrame:NSZeroRect modelColumn:self->modelColumn editableColumn:self->editableColumn]; + cv = [[uiprivButtonTableCellView alloc] initWithFrame:NSZeroRect table:self->t model:self->m modelColumn:self->modelColumn editableColumn:self->editableColumn]; [cv setIdentifier:[self identifier]]; return cv; } @@ -541,15 +551,15 @@ void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, p.makeTextField = YES; p.textModelColumn = textModelColumn; - p.textEditableModelColumn = textEditableModelColumn; + p.textEditableColumn = textEditableModelColumn; if (params == NULL) params = &defaultTextColumnOptionalParams; p.textParams = *params; str = [NSString stringWithUTF8String:name]; - col = [[uiprivTextImageCheckboxTableColum alloc] initWithIdentifier:str params:&p]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; [col setTitle:str]; - return col; + [t->tv addTableColumn:col]; } void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) @@ -566,9 +576,9 @@ void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn p.imageModelColumn = imageModelColumn; str = [NSString stringWithUTF8String:name]; - col = [[uiprivTextImageCheckboxTableColum alloc] initWithIdentifier:str params:&p]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; [col setTitle:str]; - return col; + [t->tv addTableColumn:col]; } void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) @@ -583,18 +593,18 @@ void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelCo p.makeTextField = YES; p.textModelColumn = textModelColumn; - p.textEditableModelColumn = textEditableModelColumn; - if (params == NULL) - params = &defaultTextColumnOptionalParams; - p.textParams = *params; + p.textEditableColumn = textEditableModelColumn; + if (textParams == NULL) + textParams = &defaultTextColumnOptionalParams; + p.textParams = *textParams; p.makeImage = YES; p.imageModelColumn = imageModelColumn; str = [NSString stringWithUTF8String:name]; - col = [[uiprivTextImageCheckboxTableColum alloc] initWithIdentifier:str params:&p]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; [col setTitle:str]; - return col; + [t->tv addTableColumn:col]; } void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) @@ -609,12 +619,12 @@ void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModel p.makeCheckbox = YES; p.checkboxModelColumn = checkboxModelColumn; - p.checkboxEditableColumn = checkboxEditableColumn; + p.checkboxEditableColumn = checkboxEditableModelColumn; str = [NSString stringWithUTF8String:name]; - col = [[uiprivTextImageCheckboxTableColum alloc] initWithIdentifier:str params:&p]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; [col setTitle:str]; - return col; + [t->tv addTableColumn:col]; } void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) @@ -629,19 +639,19 @@ void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxM p.makeTextField = YES; p.textModelColumn = textModelColumn; - p.textEditableModelColumn = textEditableModelColumn; - if (params == NULL) - params = &defaultTextColumnOptionalParams; - p.textParams = *params; + p.textEditableColumn = textEditableModelColumn; + if (textParams == NULL) + textParams = &defaultTextColumnOptionalParams; + p.textParams = *textParams; p.makeCheckbox = YES; p.checkboxModelColumn = checkboxModelColumn; - p.checkboxEditableColumn = checkboxEditableColumn; + p.checkboxEditableColumn = checkboxEditableModelColumn; str = [NSString stringWithUTF8String:name]; - col = [[uiprivTextImageCheckboxTableColum alloc] initWithIdentifier:str params:&p]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; [col setTitle:str]; - return col; + [t->tv addTableColumn:col]; } void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) @@ -650,9 +660,9 @@ void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressMo NSString *str; str = [NSString stringWithUTF8String:name]; - col = [[uiprivProgressBarTableColum alloc] initWithIdentifier:str modelColumn:progressModelColumn]; + col = [[uiprivProgressBarTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:progressModelColumn]; [col setTitle:str]; - return col; + [t->tv addTableColumn:col]; } void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModelColumn, int buttonClickableModelColumn) @@ -661,7 +671,7 @@ void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModel NSString *str; str = [NSString stringWithUTF8String:name]; - col = [[uiprivButtonTableColum alloc] initWithIdentifier:str modelColumn:buttonTextModelColumn editableColumn:buttonClickableModelColumn]; + col = [[uiprivButtonTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:buttonTextModelColumn editableColumn:buttonClickableModelColumn]; [col setTitle:str]; - return col; + [t->tv addTableColumn:col]; } From 3bb050777b5fadaf867d9eb9237fd625fcedc18a Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Jun 2018 23:23:48 -0400 Subject: [PATCH 035/166] Started converting the test program. First column works fine, second segfaults. --- test/OLD_page16.c | 143 ++++++++++++++++++++++++++++++++++++++++++++++ test/page16.c | 64 +++++++++++---------- uitable.h | 8 --- 3 files changed, 177 insertions(+), 38 deletions(-) create mode 100644 test/OLD_page16.c diff --git a/test/OLD_page16.c b/test/OLD_page16.c new file mode 100644 index 00000000..80ac0139 --- /dev/null +++ b/test/OLD_page16.c @@ -0,0 +1,143 @@ +// 21 june 2016 +#include "test.h" + +static uiTableModelHandler mh; + +static int modelNumColumns(uiTableModelHandler *mh, uiTableModel *m) +{ + return 9; +} + +static uiTableModelColumnType modelColumnType(uiTableModelHandler *mh, uiTableModel *m, int column) +{ + if (column == 3 || column == 4) + return uiTableModelColumnColor; + if (column == 5) + return uiTableModelColumnImage; + if (column == 7 || column == 8) + return uiTableModelColumnInt; + return uiTableModelColumnString; +} + +static int modelNumRows(uiTableModelHandler *mh, uiTableModel *m) +{ + return 15; +} + +static uiImage *img[2]; +static char row9text[1024]; +static int yellowRow = -1; +static int checkStates[15]; + +static void *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col) +{ + char buf[256]; + + if (col == 3) { + if (row == yellowRow) + return uiTableModelGiveColor(1, 1, 0, 1); + if (row == 3) + return uiTableModelGiveColor(1, 0, 0, 1); + if (row == 11) + return uiTableModelGiveColor(0, 0.5, 1, 0.5); + return NULL; + } + if (col == 4) { + if ((row % 2) == 1) + return uiTableModelGiveColor(0.5, 0, 0.75, 1); + return NULL; + } + if (col == 5) { + if (row < 8) + return img[0]; + return img[1]; + } + if (col == 7) + return uiTableModelGiveInt(checkStates[row]); + if (col == 8) { + if (row == 0) + return uiTableModelGiveInt(0); + if (row == 13) + return uiTableModelGiveInt(100); + if (row == 14) + return uiTableModelGiveInt(-1); + return uiTableModelGiveInt(50); + } + switch (col) { + case 0: + sprintf(buf, "Row %d", row); + break; + case 2: + if (row == 9) + return uiTableModelStrdup(row9text); + // fall through + case 1: + strcpy(buf, "Part"); + break; + case 6: + strcpy(buf, "Make Yellow"); + break; + } + return uiTableModelStrdup(buf); +} + +static void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col, const void *val) +{ + if (row == 9 && col == 2) + strcpy(row9text, (const char *) val); + if (col == 6) + yellowRow = row; + if (col == 7) + checkStates[row] = uiTableModelTakeInt(val); +} + +uiBox *makePage16(void) +{ + uiBox *page16; + uiTableModel *m; + uiTable *t; + uiTableColumn *tc; + + img[0] = uiNewImage(16, 16); + appendImageNamed(img[0], "andlabs_16x16test_24june2016.png"); + appendImageNamed(img[0], "andlabs_32x32test_24june2016.png"); + img[1] = uiNewImage(16, 16); + appendImageNamed(img[1], "tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png"); + appendImageNamed(img[1], "tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png"); + + strcpy(row9text, "Part"); + + memset(checkStates, 0, 15 * sizeof (int)); + + page16 = newVerticalBox(); + + mh.NumColumns = modelNumColumns; + mh.ColumnType = modelColumnType; + mh.NumRows = modelNumRows; + mh.CellValue = modelCellValue; + mh.SetCellValue = modelSetCellValue; + m = uiNewTableModel(&mh); + + t = uiNewTable(m); + uiBoxAppend(page16, uiControl(t), 1); + + uiTableAppendTextColumn(t, "Column 1", 0); + + tc = uiTableAppendColumn(t, "Column 2"); + uiTableColumnAppendImagePart(tc, 5, 0); + uiTableColumnAppendTextPart(tc, 1, 0); + uiTableColumnAppendTextPart(tc, 2, 1); + uiTableColumnPartSetTextColor(tc, 1, 4); + uiTableColumnPartSetEditable(tc, 2, 1); + + uiTableSetRowBackgroundColorModelColumn(t, 3); + + tc = uiTableAppendColumn(t, "Buttons"); + uiTableColumnAppendCheckboxPart(tc, 7, 0); + uiTableColumnAppendButtonPart(tc, 6, 1); + + tc = uiTableAppendColumn(t, "Progress Bar"); + uiTableColumnAppendProgressBarPart(tc, 8, 0); + + return page16; +} diff --git a/test/page16.c b/test/page16.c index 80ac0139..6db33864 100644 --- a/test/page16.c +++ b/test/page16.c @@ -8,15 +8,15 @@ static int modelNumColumns(uiTableModelHandler *mh, uiTableModel *m) return 9; } -static uiTableModelColumnType modelColumnType(uiTableModelHandler *mh, uiTableModel *m, int column) +static uiTableDataType modelColumnType(uiTableModelHandler *mh, uiTableModel *m, int column) { if (column == 3 || column == 4) - return uiTableModelColumnColor; + return uiTableDataTypeColor; if (column == 5) - return uiTableModelColumnImage; + return uiTableDataTypeImage; if (column == 7 || column == 8) - return uiTableModelColumnInt; - return uiTableModelColumnString; + return uiTableDataTypeInt; + return uiTableDataTypeString; } static int modelNumRows(uiTableModelHandler *mh, uiTableModel *m) @@ -29,39 +29,39 @@ static char row9text[1024]; static int yellowRow = -1; static int checkStates[15]; -static void *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col) +static uiTableData *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col) { char buf[256]; if (col == 3) { if (row == yellowRow) - return uiTableModelGiveColor(1, 1, 0, 1); + return uiNewTableDataColor(1, 1, 0, 1); if (row == 3) - return uiTableModelGiveColor(1, 0, 0, 1); + return uiNewTableDataColor(1, 0, 0, 1); if (row == 11) - return uiTableModelGiveColor(0, 0.5, 1, 0.5); + return uiNewTableDataColor(0, 0.5, 1, 0.5); return NULL; } if (col == 4) { if ((row % 2) == 1) - return uiTableModelGiveColor(0.5, 0, 0.75, 1); + return uiNewTableDataColor(0.5, 0, 0.75, 1); return NULL; } if (col == 5) { if (row < 8) - return img[0]; - return img[1]; + return uiNewTableDataImage(img[0]); + return uiNewTableDataImage(img[1]); } if (col == 7) - return uiTableModelGiveInt(checkStates[row]); + return uiNewTableDataInt(checkStates[row]); if (col == 8) { if (row == 0) - return uiTableModelGiveInt(0); + return uiNewTableDataInt(0); if (row == 13) - return uiTableModelGiveInt(100); + return uiNewTableDataInt(100); if (row == 14) - return uiTableModelGiveInt(-1); - return uiTableModelGiveInt(50); + return uiNewTableDataInt(-1); + return uiNewTableDataInt(50); } switch (col) { case 0: @@ -69,7 +69,7 @@ static void *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, i break; case 2: if (row == 9) - return uiTableModelStrdup(row9text); + return uiNewTableDataString(row9text); // fall through case 1: strcpy(buf, "Part"); @@ -78,17 +78,17 @@ static void *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, i strcpy(buf, "Make Yellow"); break; } - return uiTableModelStrdup(buf); + return uiNewTableDataString(buf); } -static void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col, const void *val) +static void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col, const uiTableData *val) { if (row == 9 && col == 2) - strcpy(row9text, (const char *) val); + strcpy(row9text, uiTableDataString(val)); if (col == 6) yellowRow = row; if (col == 7) - checkStates[row] = uiTableModelTakeInt(val); + checkStates[row] = uiTableDataInt(val); } uiBox *makePage16(void) @@ -96,7 +96,7 @@ uiBox *makePage16(void) uiBox *page16; uiTableModel *m; uiTable *t; - uiTableColumn *tc; + uiTableTextColumnOptionalParams p; img[0] = uiNewImage(16, 16); appendImageNamed(img[0], "andlabs_16x16test_24june2016.png"); @@ -121,15 +121,18 @@ uiBox *makePage16(void) t = uiNewTable(m); uiBoxAppend(page16, uiControl(t), 1); - uiTableAppendTextColumn(t, "Column 1", 0); + uiTableAppendTextColumn(t, "Column 1", + 0, uiTableModelColumnNeverEditable, NULL); - tc = uiTableAppendColumn(t, "Column 2"); - uiTableColumnAppendImagePart(tc, 5, 0); - uiTableColumnAppendTextPart(tc, 1, 0); - uiTableColumnAppendTextPart(tc, 2, 1); - uiTableColumnPartSetTextColor(tc, 1, 4); - uiTableColumnPartSetEditable(tc, 2, 1); + memset(&p, 0, sizeof (uiTableTextColumnOptionalParams)); + p.ColorModelColumn = 4; + uiTableAppendImageTextColumn(t, "Column 2", + 5, + 1, uiTableModelColumnNeverEditable, &p); + uiTableAppendTextColumn(t, "Editable", + 2, uiTableModelColumnAlwaysEditable, NULL); +#if 0 uiTableSetRowBackgroundColorModelColumn(t, 3); tc = uiTableAppendColumn(t, "Buttons"); @@ -138,6 +141,7 @@ uiBox *makePage16(void) tc = uiTableAppendColumn(t, "Progress Bar"); uiTableColumnAppendProgressBarPart(tc, 8, 0); +#endif return page16; } diff --git a/uitable.h b/uitable.h index bd95cb53..077e77f9 100644 --- a/uitable.h +++ b/uitable.h @@ -47,14 +47,6 @@ struct uiTableModelHandler { void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const uiTableData *); }; -_UI_EXTERN void *uiTableModelStrdup(const char *str); -// TODO rename the strdup one to this too -_UI_EXTERN void *uiTableModelGiveColor(double r, double g, double b, double a); -_UI_EXTERN void *uiTableModelGiveInt(int i); -// TODO TakeString -// TODO add const -_UI_EXTERN int uiTableModelTakeInt(void *v); - _UI_EXTERN uiTableModel *uiNewTableModel(uiTableModelHandler *mh); _UI_EXTERN void uiFreeTableModel(uiTableModel *m); _UI_EXTERN void uiTableModelRowInserted(uiTableModel *m, int newIndex); From 2cfbb0144e9fae814267153e2c18cc9bc0634f14 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 4 Jun 2018 18:17:05 -0400 Subject: [PATCH 036/166] Fixed segfaults. Now to fix logic errors. (Technically one of the two changes here is a logic error too.) --- darwin/tablecolumn.m | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index fe71c79e..95217c13 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -154,6 +154,8 @@ struct textColumnCreateParams { self->iv = nil; // TODO rename to makeImageView if (p->makeImage) { + self->imageModelColumn = p->imageModelColumn; + self->iv = [[NSImageView alloc] initWithFrame:NSZeroRect]; [self->iv setImageFrameStyle:NSImageFrameNone]; [self->iv setImageAlignment:NSImageAlignCenter]; @@ -173,6 +175,9 @@ struct textColumnCreateParams { self->cb = nil; if (p->makeCheckbox) { + self->checkboxModelColumn = p->checkboxModelColumn; + self->checkboxEditableColumn = p->checkboxEditableColumn; + self->cb = [[NSButton alloc] initWithFrame:NSZeroRect]; [self->cb setTitle:@""]; [self->cb setButtonType:NSSwitchButton]; @@ -258,9 +263,12 @@ struct textColumnCreateParams { double r, g, b, a; data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textParams.ColorModelColumn); - uiTableDataColor(data, &r, &g, &b, &a); - uiFreeTableData(data); - color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a]; + // TODO document this is allowed + if (data != NULL) { + uiTableDataColor(data, &r, &g, &b, &a); + uiFreeTableData(data); + color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a]; + } } if (color == nil) color = [NSColor controlTextColor]; From 8a0ca54e93d6f29a3ef062b8a99a1fb31056d9b7 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 4 Jun 2018 20:03:05 -0400 Subject: [PATCH 037/166] Fixed constraint issues in tablecolumn.m for text-containing columns. I guess that one constraint function needs to go... --- darwin/tablecolumn.m | 125 ++++++++++++++++++++++++++++++------------- 1 file changed, 87 insertions(+), 38 deletions(-) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 95217c13..bbe11b4d 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -129,12 +129,11 @@ struct textColumnCreateParams { { self = [super initWithFrame:r]; if (self) { - NSView *left; - CGFloat leftConstant; - CGFloat leftTextConstant; + NSMutableArray *constraints; self->t = p->t; self->m = p->m; + constraints = [NSMutableArray new]; self->tf = nil; if (p->makeTextField) { @@ -146,10 +145,30 @@ struct textColumnCreateParams { // TODO set wrap and ellipsize modes? [self->tf setTarget:self]; [self->tf setAction:@selector(uiprivOnTextFieldAction:)]; + [self->tf setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addSubview:self->tf]; - } - left = nil; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + self->tf, NSLayoutAttributeLeading, + 1, -textColumnLeading, + @"uiTable cell text leading constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + self->tf, NSLayoutAttributeTop, + 1, 0, + @"uiTable cell text top constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + self->tf, NSLayoutAttributeTrailing, + 1, textColumnTrailing, + @"uiTable cell text trailing constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + self->tf, NSLayoutAttributeBottom, + 1, 0, + @"uiTable cell text bottom constraint")]; + } self->iv = nil; // TODO rename to makeImageView @@ -162,15 +181,42 @@ struct textColumnCreateParams { [self->iv setImageScaling:NSImageScaleProportionallyDown]; [self->iv setAnimates:NO]; [self->iv setEditable:NO]; - [self->iv addConstraint:uiprivMkConstraint(self->iv, NSLayoutAttributeWidth, + [self->iv setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:self->iv]; + + [constraints addObject:uiprivMkConstraint(self->iv, NSLayoutAttributeWidth, NSLayoutRelationEqual, self->iv, NSLayoutAttributeHeight, 1, 0, @"uiTable image squareness constraint")]; - [self addSubview:self->iv]; - left = self->iv; - leftConstant = imageColumnLeading; - leftTextConstant = imageTextColumnLeading; + if (self->tf != nil) { + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeLeading, + 1, -imageColumnLeading, + @"uiTable cell image leading constraint")]; + [constraints replaceObjectAtIndex:0 + withObject:uiprivMkConstraint(self->iv, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + self->tf, NSLayoutAttributeLeading, + 1, -imageTextColumnLeading, + @"uiTable cell image-text spacing constraint")]; + } else + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeCenterX, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeCenterX, + 1, 0, + @"uiTable cell image centering constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeTop, + 1, 0, + @"uiTable cell image top constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeBottom, + 1, 0, + @"uiTable cell image bottom constraint")]; } self->cb = nil; @@ -185,37 +231,40 @@ struct textColumnCreateParams { [self->cb setBordered:NO]; [self->cb setTransparent:NO]; uiDarwinSetControlFont(self->cb, NSRegularControlSize); + [self->cb setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addSubview:self->cb]; - left = self->cb; - leftConstant = checkboxColumnLeading; - leftTextConstant = checkboxTextColumnLeading; + + if (self->tf != nil) { + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + self->cb, NSLayoutAttributeLeading, + 1, -imageColumnLeading, + @"uiTable cell checkbox leading constraint")]; + [constraints replaceObjectAtIndex:0 + withObject:uiprivMkConstraint(self->cb, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + self->tf, NSLayoutAttributeLeading, + 1, -imageTextColumnLeading, + @"uiTable cell checkbox-text spacing constraint")]; + } else + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeCenterX, + NSLayoutRelationEqual, + self->cb, NSLayoutAttributeCenterX, + 1, 0, + @"uiTable cell checkbox centering constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + self->cb, NSLayoutAttributeTop, + 1, 0, + @"uiTable cell checkbox top constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + self->cb, NSLayoutAttributeBottom, + 1, 0, + @"uiTable cell checkbox bottom constraint")]; } - if (self->tf != nil && left == nil) - layoutCellSubview(self, self->tf, - self, textColumnLeading, - self, textColumnTrailing, - YES); - else if (self->tf != nil) { - layoutCellSubview(self, left, - self, leftConstant, - nil, 0, - NO); - layoutCellSubview(self, self->tf, - left, leftTextConstant, - self, textColumnTrailing, - YES); - } else { - layoutCellSubview(self, left, - nil, 0, - nil, 0, - NO); - [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeCenterX, - NSLayoutRelationEqual, - left, NSLayoutAttributeCenterX, - 1, 0, - @"uiTable image/checkbox centering constraint")]; - } + [self addConstraints:constraints]; // take advantage of NSTableCellView-provided accessibility features if (self->tf != nil) From 71e02a5c6e39e883f8a36d2f14ca6692d13e87fa Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 4 Jun 2018 20:09:09 -0400 Subject: [PATCH 038/166] And set up the constraints for the other column types. --- darwin/tablecolumn.m | 90 +++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index bbe11b4d..f8af5e11 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -12,37 +12,8 @@ #define checkboxColumnLeading imageColumnLeading #define progressBarColumnLeading imageColumnLeading #define progressBarColumnTrailing progressBarColumnLeading - -static void layoutCellSubview(NSView *superview, NSView *subview, NSView *leading, CGFloat leadingConstant, NSView *trailing, CGFloat trailingConstant, BOOL stretchy) -{ - [subview setTranslatesAutoresizingMaskIntoConstraints:NO]; - if (stretchy) - [subview setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal]; - else - [subview setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; - if (leading != nil) - [superview addConstraint:uiprivMkConstraint(leading, NSLayoutAttributeLeading, - NSLayoutRelationEqual, - subview, NSLayoutAttributeLeading, - 1, -leadingConstant, - @"uiTable cell subview leading constraint")]; - [superview addConstraint:uiprivMkConstraint(superview, NSLayoutAttributeTop, - NSLayoutRelationEqual, - subview, NSLayoutAttributeTop, - 1, 0, - @"uiTable cell subview top constraint")]; - if (trailing != nil) - [superview addConstraint:uiprivMkConstraint(trailing, NSLayoutAttributeTrailing, - NSLayoutRelationEqual, - subview, NSLayoutAttributeLeading, - 1, trailingConstant, - @"uiTable cell subview trailing constraint")]; - [superview addConstraint:uiprivMkConstraint(superview, NSLayoutAttributeBottom, - NSLayoutRelationEqual, - subview, NSLayoutAttributeBottom, - 1, 0, - @"uiTable cell subview bottom constraint")]; -} +#define buttonColumnLeading imageColumnLeading +#define buttonColumnTrailing buttonColumnLeading @implementation uiprivTableCellView @@ -148,6 +119,7 @@ struct textColumnCreateParams { [self->tf setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addSubview:self->tf]; + // TODO for all three controls: set hugging and compression resistance properly [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading, NSLayoutRelationEqual, self->tf, NSLayoutAttributeLeading, @@ -424,10 +396,30 @@ struct textColumnCreateParams { [self->p setControlSize:NSRegularControlSize]; [self->p setBezeled:YES]; [self->p setStyle:NSProgressIndicatorBarStyle]; - layoutCellSubview(self, self->p, - self, progressBarColumnLeading, - self, progressBarColumnTrailing, - YES); + [self->p setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:self->p]; + + // TODO set hugging and compression resistance properly + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + self->p, NSLayoutAttributeLeading, + 1, -progressBarColumnLeading, + @"uiTable cell progressbar leading constraint")]; + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + self->p, NSLayoutAttributeTop, + 1, 0, + @"uiTable cell progressbar top constraint")]; + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + self->p, NSLayoutAttributeTrailing, + 1, progressBarColumnTrailing, + @"uiTable cell progressbar trailing constraint")]; + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + self->p, NSLayoutAttributeBottom, + 1, 0, + @"uiTable cell progressbar bottom constraint")]; } return self; } @@ -527,10 +519,30 @@ struct textColumnCreateParams { uiDarwinSetControlFont(self->b, NSRegularControlSize); [self->b setTarget:self]; [self->b setAction:@selector(uiprivOnClicked:)]; - layoutCellSubview(self, self->b, - self, progressBarColumnLeading, - self, progressBarColumnTrailing, - YES); + [self->b setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:self->b]; + + // TODO set hugging and compression resistance properly + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + self->b, NSLayoutAttributeLeading, + 1, -buttonColumnLeading, + @"uiTable cell button leading constraint")]; + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + self->b, NSLayoutAttributeTop, + 1, 0, + @"uiTable cell button top constraint")]; + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + self->b, NSLayoutAttributeTrailing, + 1, buttonColumnTrailing, + @"uiTable cell button trailing constraint")]; + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + self->b, NSLayoutAttributeBottom, + 1, 0, + @"uiTable cell button bottom constraint")]; } return self; } From 8c611cf95d2190165f64495c03f9078da1bcd2f6 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 4 Jun 2018 20:13:35 -0400 Subject: [PATCH 039/166] And added the rest of the column types to the tester. --- test/page16.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/page16.c b/test/page16.c index 6db33864..434cad60 100644 --- a/test/page16.c +++ b/test/page16.c @@ -134,14 +134,15 @@ uiBox *makePage16(void) #if 0 uiTableSetRowBackgroundColorModelColumn(t, 3); - - tc = uiTableAppendColumn(t, "Buttons"); - uiTableColumnAppendCheckboxPart(tc, 7, 0); - uiTableColumnAppendButtonPart(tc, 6, 1); - - tc = uiTableAppendColumn(t, "Progress Bar"); - uiTableColumnAppendProgressBarPart(tc, 8, 0); #endif + uiTableAppendCheckboxColumn(t, "Checkboxes", + 7, uiTableModelColumnAlwaysEditable); + uiTableAppendButtonColumn(t, "Buttons", + 6, uiTableModelColumnAlwaysEditable); + + uiTableAppendProgressBarColumn(t, "Progress Bar", + 8); + return page16; } From 3fa15d5277c4299e1b2aefe5100c74463c2d6ba5 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 4 Jun 2018 20:17:15 -0400 Subject: [PATCH 040/166] Fixed graphical glitches in the OS X Table. Finally. SOMEHOW. --- darwin/table.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/darwin/table.m b/darwin/table.m index 5a7e7500..5d5d8d5e 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -161,6 +161,11 @@ uiTable *uiNewTable(uiTableModel *model) p.VScroll = YES; t->sv = uiprivMkScrollView(&p, &(t->d)); + // TODO WHY DOES THIS REMOVE ALL GRAPHICAL GLITCHES? + // I got the idea from http://jwilling.com/blog/optimized-nstableview-scrolling/ but that was on an unrelated problem I didn't seem to have (although I have small-ish tables to start with) + // I don't get layer-backing... am I supposed to layer-back EVERYTHING manually? I need to check Interface Builder again... + [t->sv setWantsLayer:YES]; + t->backgroundColumn = -1; return t; From 0a6a781be75a458fbbd4ca2f683fc080fa0a1468 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 4 Jun 2018 22:14:05 -0400 Subject: [PATCH 041/166] And implemented row background colors and button clicks. OS X implementation done for now! --- darwin/table.m | 63 +++++++++++++++++++++++++++++++++++++++----- darwin/tablecolumn.m | 7 ++++- test/page16.c | 11 +++++--- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/darwin/table.m b/darwin/table.m index 5d5d8d5e..1d5093f9 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -8,6 +8,56 @@ - (id)initWithModel:(uiTableModel *)model; @end +// TODO we really need to clean up the sharing of the table and model variables... +@interface uiprivTableView : NSTableView { + uiTable *uiprivT; + uiTableModel *uiprivM; +} +- (id)initWithFrame:(NSRect)r uiprivT:(uiTable *)t uiprivM:(uiTableModel *)m; +@end + +@implementation uiprivTableView + +- (id)initWithFrame:(NSRect)r uiprivT:(uiTable *)t uiprivM:(uiTableModel *)m +{ + self = [super initWithFrame:r]; + if (self) { + self->uiprivT = t; + self->uiprivM = m; + } + return self; +} + +// TODO is this correct for overflow scrolling? +static void setBackgroundColor(uiprivTableView *t, NSTableRowView *rv, NSInteger row) +{ + uiTableData *data; + NSColor *color; + double r, g, b, a; + + if (t->uiprivT->backgroundColumn == -1) + return; + data = (*(t->uiprivM->mh->CellValue))(t->uiprivM->mh, t->uiprivM, row, t->uiprivT->backgroundColumn); + if (data != NULL) { + uiTableDataColor(data, &r, &g, &b, &a); + uiFreeTableData(data); + color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a]; + } else { + NSArray *colors; + NSInteger index; + + // this usage is primarily a guess; hopefully it is correct for the non-two color case... (TODO) + // it does seem to be correct for the two-color case, judging from comparing against the value of backgroundColor before changing it (and no, nil does not work; it just sets to white) + colors = [NSColor controlAlternatingRowBackgroundColors]; + index = row % [colors count]; + color = (NSColor *) [colors objectAtIndex:index]; + } + [rv setBackgroundColor:color]; + // color is autoreleased in all cases +} + +@end + @implementation uiprivTableModel - (id)initWithModel:(uiTableModel *)model @@ -36,9 +86,9 @@ return cv; } -- (void)tableView:(NSTableView *)nstv didAddRowView:(NSTableRowView *)rv forRow:(NSInteger)row +- (void)tableView:(NSTableView *)tv didAddRowView:(NSTableRowView *)rv forRow:(NSInteger)row { - // TODO set background color + setBackgroundColor((uiprivTableView *) tv, rv, row); } @end @@ -76,16 +126,15 @@ void uiTableModelRowInserted(uiTableModel *m, int newIndex) void uiTableModelRowChanged(uiTableModel *m, int index) { - NSTableView *tv; + uiprivTableView *tv; NSTableRowView *rv; NSUInteger i, n; uiprivTableCellView *cv; for (tv in m->tables) { rv = [tv rowViewAtRow:index makeIfNecessary:NO]; - if (rv != nil) { - // TODO update colors - } + if (rv != nil) + setBackgroundColor(tv, rv, index); n = [[tv tableColumns] count]; for (i = 0; i < n; i++) { cv = (uiprivTableCellView *) [tv viewAtColumn:i row:index makeIfNecessary:NO]; @@ -131,7 +180,7 @@ uiTable *uiNewTable(uiTableModel *model) uiDarwinNewControl(uiTable, t); t->m = model; - t->tv = [[NSTableView alloc] initWithFrame:NSZeroRect]; + t->tv = [[uiprivTableView alloc] initWithFrame:NSZeroRect uiprivT:t uiprivM:t->m]; [t->tv setDataSource:model->m]; [t->tv setDelegate:model->m]; diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index f8af5e11..531137f0 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -569,7 +569,12 @@ struct textColumnCreateParams { - (IBAction)uiprivOnClicked:(id)sender { - // TODO + NSInteger row; + + row = [self->t->tv rowForView:self->b]; + (*(self->m->mh->SetCellValue))(self->m->mh, self->m, + row, self->modelColumn, NULL); + // TODO document we DON'T update the cell after doing this } @end diff --git a/test/page16.c b/test/page16.c index 434cad60..d00bd39c 100644 --- a/test/page16.c +++ b/test/page16.c @@ -85,8 +85,15 @@ static void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, { if (row == 9 && col == 2) strcpy(row9text, uiTableDataString(val)); - if (col == 6) + if (col == 6) { + int prevYellowRow; + + prevYellowRow = yellowRow; yellowRow = row; + if (prevYellowRow != -1) + uiTableModelRowChanged(m, prevYellowRow); + uiTableModelRowChanged(m, yellowRow); + } if (col == 7) checkStates[row] = uiTableDataInt(val); } @@ -132,9 +139,7 @@ uiBox *makePage16(void) uiTableAppendTextColumn(t, "Editable", 2, uiTableModelColumnAlwaysEditable, NULL); -#if 0 uiTableSetRowBackgroundColorModelColumn(t, 3); -#endif uiTableAppendCheckboxColumn(t, "Checkboxes", 7, uiTableModelColumnAlwaysEditable); From 3c063c71d5f29539a66b99843e365c1115e81e7d Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 4 Jun 2018 22:15:05 -0400 Subject: [PATCH 042/166] And one more TODO before we move on. --- darwin/tablecolumn.m | 1 + 1 file changed, 1 insertion(+) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 531137f0..dbc85360 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -327,6 +327,7 @@ struct textColumnCreateParams { row, self->textModelColumn, data); uiFreeTableData(data); // always refresh the value in case the model rejected it + // TODO document that we do this, but not for the whole row (or decide to do both, or do neither...) [self uiprivUpdate:row]; } From 7a5577db947aa28382ccda56ce197c99f3419724 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 4 Jun 2018 23:28:46 -0400 Subject: [PATCH 043/166] Deleted now-irrelevant code from OLD_table.m. --- darwin/OLD_table.m | 543 --------------------------------------------- 1 file changed, 543 deletions(-) diff --git a/darwin/OLD_table.m b/darwin/OLD_table.m index 66666863..18231f55 100644 --- a/darwin/OLD_table.m +++ b/darwin/OLD_table.m @@ -2,519 +2,24 @@ #import "uipriv_darwin.h" // TODOs -// - initial state of table view is off // - header cell seems off // - background color shows up for a line or two below selection // - editable NSTextFields have no intrinsic width -// - changing a part property does not refresh views // - is the Y position of checkbox cells correct? -// - progressbars appear ABOVE the table header -// - threaded animation (which was known to have some issues: https://stackoverflow.com/questions/18142801/nstableview-nsprogressindicator-flickering-issues) is NOT the cause; happens regardless of setting (or does the setting not stick? TODO) - -// LONGTERM -// - reuse row views instead of creating a new one each time - -@interface tableModel : NSObject { - uiTableModel *libui_m; -} -- (id)initWithModel:(uiTableModel *)m; -- (IBAction)onAction:(id)sender; -@end - -enum { - partText, - partImage, - partButton, - partCheckbox, - partProgressBar, -}; - -@interface tablePart : NSObject -@property int type; -@property int textColumn; -@property int textColorColumn; -@property int imageColumn; -@property int valueColumn; -@property int expand; -@property int editable; -- (NSView *)mkView:(uiTableModel *)m row:(int)row; -@end - -@interface tableColumn : NSTableColumn -@property uiTableColumn *libui_col; -@end - -@interface tableView : NSTableView -@property uiTable *libui_t; -@end - -struct uiTableModel { - uiTableModelHandler *mh; - tableModel *m; - NSMutableArray *tables; -}; - -struct uiTableColumn { - tableColumn *c; - NSMutableArray *parts; -}; - -struct uiTable { - uiDarwinControl c; - NSScrollView *sv; - tableView *tv; - uiprivScrollViewData *d; - int backgroundColumn; -}; - -@implementation tableModel - -- (id)initWithModel:(uiTableModel *)m -{ - self = [super init]; - if (self) - self->libui_m = m; - return self; -} - -- (NSInteger)numberOfRowsInTableView:(NSTableView *)tv -{ - uiTableModelHandler *mh = self->libui_m->mh; - - return (*(mh->NumRows))(mh, self->libui_m); -} - -// these are according to Interface Builder -#define xleft 2 -#define xmiddle 7 /* between images and text, anyway; let's just use it for everything to be simpler */ -#define xright 3 - - - (NSView *)tableView:(NSTableView *)tv viewForTableColumn:(NSTableColumn *)cc row:(NSInteger)row -{ - NSTableCellView *v; - tableColumn *c = (tableColumn *) cc; - tablePart *part; - NSMutableArray *views; - NSView *view, *prev; - - v = [[NSTableCellView alloc] initWithFrame:NSZeroRect]; - - views = [NSMutableArray new]; - for (part in c.libui_col->parts) - [views addObject:[part mkView:self->libui_m row:row]]; - if ([views count] == 0) // empty (TODO allow?) - goto done; - - // add to v and arrange horizontally - prev = nil; - for (view in views) { - [v addSubview:view]; - // TODO set [v imageView] and [v textField] as appropriate? - if (prev == nil) { // first view - [v addConstraint:uiprivMkConstraint(v, NSLayoutAttributeLeading, - NSLayoutRelationEqual, - view, NSLayoutAttributeLeading, - 1, -xleft, - @"uiTableColumn first part horizontal constraint")]; - prev = view; - continue; - } - [v addConstraint:uiprivMkConstraint(prev, NSLayoutAttributeTrailing, - NSLayoutRelationEqual, - view, NSLayoutAttributeLeading, - 1, -xmiddle, - @"uiTableColumn middle horizontal constraint")]; - prev = view; - } - [v addConstraint:uiprivMkConstraint(prev, NSLayoutAttributeTrailing, - NSLayoutRelationEqual, - v, NSLayoutAttributeTrailing, - 1, -xright, - @"uiTableColumn last part horizontal constraint")]; - - // and vertically - for (view in views) { - [v addConstraint:uiprivMkConstraint(view, NSLayoutAttributeCenterY, - NSLayoutRelationEqual, - v, NSLayoutAttributeCenterY, - 1, 0, - @"uiTableColumn part vertical constraint")]; - // TODO avoid the need for this hack - if ([view isKindOfClass:[NSImageView class]]) - [v addConstraint:uiprivMkConstraint(view, NSLayoutAttributeTop, - NSLayoutRelationEqual, - v, NSLayoutAttributeTop, - 1, 0, - @"uiTableColumn part vertical top constraint")]; - } - -done: - [views release]; - [v setTranslatesAutoresizingMaskIntoConstraints:NO]; - // TODO autorelease? - return v; -} - -- (void)tableView:(NSTableView *)nstv didAddRowView:(NSTableRowView *)rv forRow:(NSInteger)row -{ - uiTableModel *m = self->libui_m; - tableView *tv = (tableView *) nstv; - uiTable *t = tv.libui_t; - NSColor *color; - - if (t->backgroundColumn == -1) - return; - color = (NSColor *) ((*(m->mh->CellValue))(m->mh, m, row, t->backgroundColumn)); - if (color == nil) - return; - [rv setBackgroundColor:color]; - // TODO autorelease color? or release it? -} - -- (IBAction)onAction:(id)sender -{ - uiTableModel *m = self->libui_m; - NSView *view = (NSView *) sender; - NSTableView *tv; - NSInteger row; - const void *data; - - row = -1; - for (tv in m->tables) { - row = [tv rowForView:view]; - if (row != -1) - break; - } - if (row == -1) - uiprivImplBug("table model action triggered on view with no associated table"); - - if ([view isKindOfClass:[NSTextField class]]) - data = [[((NSTextField *) view) stringValue] UTF8String]; - else if ([view isKindOfClass:[NSButton class]]) { - NSButton *b; - - b = (NSButton *) view; -// if ([b buttonType] == NSSwitchButton) - data = uiTableModelGiveInt([b state] == NSOnState); -// TODO there is no buttonType getter -if(1); else - data = NULL; - } else - uiprivImplBug("table model editing action triggered on non-editable view"); - - // note the use of [view tag] — we need the model column, which we store in the view tag for relevant views below - (*(m->mh->SetCellValue))(m->mh, m, - row, [view tag], - data); - // always refresh the value in case the model rejected it - // TODO only affect tv? - uiTableModelRowChanged(m, row); -} - -@end @implementation tablePart -- (id)init -{ - self = [super init]; - if (self) { - self.textColumn = -1; - self.textColorColumn = -1; - } - return self; -} - - (NSView *)mkView:(uiTableModel *)m row:(int)row { - void *data; - NSString *str; - NSView *view; - NSTextField *tf; - NSImageView *iv; - NSButton *b; - NSProgressIndicator *p; - int value; - - switch (self.type) { - case partText: - data = (*(m->mh->CellValue))(m->mh, m, row, self.textColumn); - str = uiprivToNSString((char *) data); - uiprivFree(data); - tf = uiprivNewLabel(str); - // TODO set wrap and ellipsize modes? - if (self.textColorColumn != -1) { - NSColor *color; - - color = (NSColor *) ((*(m->mh->CellValue))(m->mh, m, row, self.textColorColumn)); - if (color != nil) - [tf setTextColor:color]; - // TODO release color - } - if (self.editable) { - [tf setEditable:YES]; - [tf setTarget:m->m]; - [tf setAction:@selector(onAction:)]; - } - [tf setTag:self.textColumn]; - view = tf; - break; - case partImage: - data = (*(m->mh->CellValue))(m->mh, m, row, self.imageColumn); - iv = [[NSImageView alloc] initWithFrame:NSZeroRect]; - [iv setImage:uiprivImageNSImage((uiImage *) data)]; - [iv setImageFrameStyle:NSImageFrameNone]; - [iv setImageAlignment:NSImageAlignCenter]; - [iv setImageScaling:NSImageScaleProportionallyDown]; - [iv setAnimates:NO]; - [iv setEditable:NO]; - [iv addConstraint:uiprivMkConstraint(iv, NSLayoutAttributeWidth, - NSLayoutRelationEqual, - iv, NSLayoutAttributeHeight, - 1, 0, - @"uiTable image squareness constraint")]; - [iv setTag:self.imageColumn]; - view = iv; - break; - case partButton: - // TODO buttons get clipped - data = (*(m->mh->CellValue))(m->mh, m, row, self.textColumn); - str = uiprivToNSString((char *) data); - b = [[NSButton alloc] initWithFrame:NSZeroRect]; - [b setTitle:str]; - [b setButtonType:NSMomentaryPushInButton]; - [b setBordered:YES]; - [b setBezelStyle:NSRoundRectBezelStyle]; - uiDarwinSetControlFont(b, NSRegularControlSize); - if (self.editable) { - [b setTarget:m->m]; - [b setAction:@selector(onAction:)]; - } else - [b setEnabled:NO]; - [b setTag:self.textColumn]; - view = b; - break; - case partCheckbox: - data = (*(m->mh->CellValue))(m->mh, m, row, self.valueColumn); - b = [[NSButton alloc] initWithFrame:NSZeroRect]; - [b setTitle:@""]; - [b setButtonType:NSSwitchButton]; - // doesn't seem to have an associated bezel style - [b setBordered:NO]; - [b setTransparent:NO]; - uiDarwinSetControlFont(b, NSRegularControlSize); - if (uiTableModelTakeInt(data) != 0) - [b setState:NSOnState]; - else - [b setState:NSOffState]; - if (self.editable) { - [b setTarget:m->m]; - [b setAction:@selector(onAction:)]; - } else - [b setEnabled:NO]; - [b setTag:self.valueColumn]; - view = b; - break; - case partProgressBar: - data = (*(m->mh->CellValue))(m->mh, m, row, self.valueColumn); - value = uiTableModelTakeInt(data); - // TODO no intrinsic width - p = [[NSProgressIndicator alloc] initWithFrame:NSZeroRect]; - [p setControlSize:NSRegularControlSize]; - [p setBezeled:YES]; - [p setStyle:NSProgressIndicatorBarStyle]; - if (value == -1) { - [p setIndeterminate:YES]; - [p startAnimation:p]; - } else if (value == 100) { - [p setIndeterminate:NO]; - [p setMaxValue:101]; - [p setDoubleValue:101]; - [p setDoubleValue:100]; - [p setMaxValue:100]; - } else { - [p setIndeterminate:NO]; - [p setDoubleValue:(value + 1)]; - [p setDoubleValue:value]; - } - view = p; - break; - } - // if stretchy, don't hug, otherwise hug forcibly if (self.expand) [view setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal]; else [view setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; - [view setTranslatesAutoresizingMaskIntoConstraints:NO]; - // TODO autorelease? - return view; } @end -@implementation tableColumn -@end - -@implementation tableView -@end - -void *uiTableModelStrdup(const char *str) -{ - // TODO don't we have this already? - char *dup; - - dup = (char *) uiprivAlloc((strlen(str) + 1) * sizeof (char), "char[]"); - strcpy(dup, str); - return dup; -} - -uiTableModel *uiNewTableModel(uiTableModelHandler *mh) -{ - uiTableModel *m; - - m = uiprivNew(uiTableModel); - m->mh = mh; - m->m = [[tableModel alloc] initWithModel:m]; - m->tables = [NSMutableArray new]; - return m; -} - -void *uiTableModelGiveColor(double r, double g, double b, double a) -{ - return [[NSColor colorWithSRGBRed:r green:g blue:b alpha:a] retain]; -} - -void uiFreeTableModel(uiTableModel *m) -{ - if ([m->tables count] != 0) - uiprivUserBug("You cannot free a uiTableModel while uiTables are using it."); - [m->tables release]; - [m->m release]; - uiprivFree(m); -} - -void uiTableModelRowInserted(uiTableModel *m, int newIndex) -{ - NSTableView *tv; - NSIndexSet *set; - - set = [NSIndexSet indexSetWithIndex:newIndex]; - for (tv in m->tables) - [tv insertRowsAtIndexes:set withAnimation:NSTableViewAnimationEffectNone]; - // set is autoreleased -} - -void uiTableModelRowChanged(uiTableModel *m, int index) -{ - NSTableView *tv; - NSIndexSet *set, *cols; - - set = [NSIndexSet indexSetWithIndex:index]; - for (tv in m->tables) { - cols = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, [[tv tableColumns] count])]; - [tv reloadDataForRowIndexes:set columnIndexes:cols]; - // TODO this isn't enough - [cols release]; - } - // set is autoreleased -} - -void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) -{ - NSTableView *tv; - NSIndexSet *set; - - set = [NSIndexSet indexSetWithIndex:oldIndex]; - for (tv in m->tables) - [tv removeRowsAtIndexes:set withAnimation:NSTableViewAnimationEffectNone]; - // set is autoreleased -} - -void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) -{ - tablePart *part; - - part = [tablePart new]; - part.type = partText; - part.textColumn = modelColumn; - part.expand = expand; - [c->parts addObject:part]; -} - -void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) -{ - tablePart *part; - - part = [tablePart new]; - part.type = partImage; - part.imageColumn = modelColumn; - part.expand = expand; - [c->parts addObject:part]; -} - -void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand) -{ - tablePart *part; - - part = [tablePart new]; - part.type = partButton; - part.textColumn = modelColumn; - part.expand = expand; - part.editable = 1; // editable by default - [c->parts addObject:part]; -} - -void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) -{ - tablePart *part; - - part = [tablePart new]; - part.type = partCheckbox; - part.valueColumn = modelColumn; - part.expand = expand; - part.editable = 1; // editable by default - [c->parts addObject:part]; -} - -void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand) -{ - tablePart *part; - - part = [tablePart new]; - part.type = partProgressBar; - part.valueColumn = modelColumn; - part.expand = expand; - [c->parts addObject:part]; -} - -void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) -{ - tablePart *p; - - p = (tablePart *) [c->parts objectAtIndex:part]; - p.editable = editable; -} - -void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) -{ - tablePart *p; - - p = (tablePart *) [c->parts objectAtIndex:part]; - p.textColorColumn = modelColumn; -} - -uiDarwinControlAllDefaultsExceptDestroy(uiTable, sv) - -static void uiTableDestroy(uiControl *c) -{ - uiTable *t = uiTable(c); - - // TODO - [t->sv release]; - uiFreeControl(uiControl(t)); -} - uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) { uiTableColumn *c; @@ -532,51 +37,3 @@ uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) [t->tv addTableColumn:c->c]; return c; } - -void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) -{ - t->backgroundColumn = modelColumn; -} - -uiTable *uiNewTable(uiTableModel *model) -{ - uiTable *t; - uiprivScrollViewCreateParams p; - - uiDarwinNewControl(uiTable, t); - - t->tv = [[tableView alloc] initWithFrame:NSZeroRect]; - t->tv.libui_t = t; - - [t->tv setDataSource:model->m]; - [t->tv setDelegate:model->m]; - [t->tv reloadData]; - [model->tables addObject:t->tv]; - - // TODO is this sufficient? - [t->tv setAllowsColumnReordering:NO]; - [t->tv setAllowsColumnResizing:YES]; - [t->tv setAllowsMultipleSelection:NO]; - [t->tv setAllowsEmptySelection:YES]; - [t->tv setAllowsColumnSelection:NO]; - [t->tv setUsesAlternatingRowBackgroundColors:YES]; - [t->tv setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleRegular]; - [t->tv setGridStyleMask:NSTableViewGridNone]; - [t->tv setAllowsTypeSelect:YES]; - // TODO floatsGroupRows — do we even allow group rows? - - memset(&p, 0, sizeof (uiprivScrollViewCreateParams)); - p.DocumentView = t->tv; - // this is what Interface Builder sets it to - // TODO verify - p.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0]; - p.DrawsBackground = YES; - p.Bordered = YES; - p.HScroll = YES; - p.VScroll = YES; - t->sv = uiprivMkScrollView(&p, &(t->d)); - - t->backgroundColumn = -1; - - return t; -} From 94fa10b35ebc6908683370cbe1f511b2fabdf8f1 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 4 Jun 2018 23:39:52 -0400 Subject: [PATCH 044/166] Started rewriting GTK+ uiTable. Did uiTableModel first. --- unix/OLD_table.c | 406 +++++++++++++++++++++++++++++++++++++++++++++++ unix/table.c | 51 +++--- 2 files changed, 432 insertions(+), 25 deletions(-) create mode 100644 unix/OLD_table.c diff --git a/unix/OLD_table.c b/unix/OLD_table.c new file mode 100644 index 00000000..3774345c --- /dev/null +++ b/unix/OLD_table.c @@ -0,0 +1,406 @@ +// 26 june 2016 +#include "uipriv_unix.h" + +void *uiTableModelStrdup(const char *str) +{ + return g_strdup(str); +} + +void *uiTableModelGiveColor(double r, double g, double b, double a) +{ + GdkRGBA rgba; + + rgba.red = r; + rgba.green = g; + rgba.blue = b; + rgba.alpha = a; + return gdk_rgba_copy(&rgba); +} + +uiTableModel *uiNewTableModel(uiTableModelHandler *mh) +{ + uiTableModel *m; + + m = uiTableModel(g_object_new(uiTableModelType, NULL)); + m->mh = mh; + return m; +} + +void uiFreeTableModel(uiTableModel *m) +{ + g_object_unref(m); +} + +void uiTableModelRowInserted(uiTableModel *m, int newIndex) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_indices(newIndex, -1); + iter.stamp = STAMP_GOOD; + iter.user_data = GINT_TO_POINTER(newIndex); + gtk_tree_model_row_inserted(GTK_TREE_MODEL(m), path, &iter); + gtk_tree_path_free(path); +} + +void uiTableModelRowChanged(uiTableModel *m, int index) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_indices(index, -1); + iter.stamp = STAMP_GOOD; + iter.user_data = GINT_TO_POINTER(index); + gtk_tree_model_row_changed(GTK_TREE_MODEL(m), path, &iter); + gtk_tree_path_free(path); +} + +void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) +{ + GtkTreePath *path; + + path = gtk_tree_path_new_from_indices(oldIndex, -1); + gtk_tree_model_row_deleted(GTK_TREE_MODEL(m), path); + gtk_tree_path_free(path); +} + +enum { + partText, + partImage, + partButton, + partCheckbox, + partProgressBar, +}; + +struct tablePart { + int type; + int textColumn; + int imageColumn; + int valueColumn; + int colorColumn; + GtkCellRenderer *r; + uiTable *tv; // for pixbufs and background color +}; + +struct uiTableColumn { + GtkTreeViewColumn *c; + uiTable *tv; // for pixbufs and background color + GPtrArray *parts; +}; + +struct uiTable { + uiUnixControl c; + GtkWidget *widget; + GtkContainer *scontainer; + GtkScrolledWindow *sw; + GtkWidget *treeWidget; + GtkTreeView *tv; + GPtrArray *columns; + uiTableModel *model; + int backgroundColumn; +}; + +// use the same size as GtkFileChooserWidget's treeview +// TODO refresh when icon theme changes +// TODO doesn't work when scaled +// TODO is this even necessary? +static void setImageSize(GtkCellRenderer *r) +{ + gint size; + gint width, height; + gint xpad, ypad; + + size = 16; // fallback used by GtkFileChooserWidget + if (gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height) != FALSE) + size = MAX(width, height); + gtk_cell_renderer_get_padding(r, &xpad, &ypad); + gtk_cell_renderer_set_fixed_size(r, + 2 * xpad + size, + 2 * ypad + size); +} + +static void applyColor(GtkTreeModel *mm, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop, const char *propSet) +{ + GValue value = G_VALUE_INIT; + GdkRGBA *rgba; + + gtk_tree_model_get_value(mm, iter, modelColumn, &value); + rgba = (GdkRGBA *) g_value_get_boxed(&value); + if (rgba != NULL) + g_object_set(r, prop, rgba, NULL); + else + g_object_set(r, propSet, FALSE, NULL); + g_value_unset(&value); +} + +static void dataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *mm, GtkTreeIter *iter, gpointer data) +{ + struct tablePart *part = (struct tablePart *) data; + GValue value = G_VALUE_INIT; + const gchar *str; + uiImage *img; + int pval; + + switch (part->type) { + case partText: + gtk_tree_model_get_value(mm, iter, part->textColumn, &value); + str = g_value_get_string(&value); + g_object_set(r, "text", str, NULL); + if (part->colorColumn != -1) + applyColor(mm, iter, + part->colorColumn, + r, "foreground-rgba", "foreground-set"); + break; + case partImage: +//TODO setImageSize(r); + gtk_tree_model_get_value(mm, iter, part->imageColumn, &value); + img = (uiImage *) g_value_get_pointer(&value); + g_object_set(r, "surface", + uiprivImageAppropriateSurface(img, part->tv->treeWidget), + NULL); + break; + case partButton: + gtk_tree_model_get_value(mm, iter, part->textColumn, &value); + str = g_value_get_string(&value); + g_object_set(r, "text", str, NULL); + break; + case partCheckbox: + gtk_tree_model_get_value(mm, iter, part->valueColumn, &value); + g_object_set(r, "active", g_value_get_int(&value) != 0, NULL); + break; + case partProgressBar: + gtk_tree_model_get_value(mm, iter, part->valueColumn, &value); + pval = g_value_get_int(&value); + if (pval == -1) { + // TODO + } else + g_object_set(r, + "pulse", -1, + "value", pval, + NULL); + break; + } + g_value_unset(&value); + + if (part->tv->backgroundColumn != -1) + applyColor(mm, iter, + part->tv->backgroundColumn, + r, "cell-background-rgba", "cell-background-set"); +} + +static void onEdited(struct tablePart *part, int column, const char *pathstr, const void *data) +{ + GtkTreePath *path; + int row; + uiTableModel *m; + + path = gtk_tree_path_new_from_string(pathstr); + row = gtk_tree_path_get_indices(path)[0]; + gtk_tree_path_free(path); + m = part->tv->model; + (*(m->mh->SetCellValue))(m->mh, m, row, column, data); + // and update + uiTableModelRowChanged(m, row); +} + +static void appendPart(uiTableColumn *c, struct tablePart *part, GtkCellRenderer *r, int expand) +{ + part->r = r; + gtk_tree_view_column_pack_start(c->c, part->r, expand != 0); + gtk_tree_view_column_set_cell_data_func(c->c, part->r, dataFunc, part, NULL); + g_ptr_array_add(c->parts, part); +} + +static void textEdited(GtkCellRendererText *renderer, gchar *path, gchar *newText, gpointer data) +{ + struct tablePart *part = (struct tablePart *) data; + + onEdited(part, part->textColumn, path, newText); +} + +void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + GtkCellRenderer *r; + + part = uiprivNew(struct tablePart); + part->type = partText; + part->textColumn = modelColumn; + part->tv = c->tv; + part->colorColumn = -1; + + r = gtk_cell_renderer_text_new(); + g_object_set(r, "editable", FALSE, NULL); + g_signal_connect(r, "edited", G_CALLBACK(textEdited), part); + + appendPart(c, part, r, expand); +} + +void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + + part = uiprivNew(struct tablePart); + part->type = partImage; + part->imageColumn = modelColumn; + part->tv = c->tv; + appendPart(c, part, + gtk_cell_renderer_pixbuf_new(), + expand); +} + +// TODO wrong type here +static void buttonClicked(GtkCellRenderer *r, gchar *pathstr, gpointer data) +{ + struct tablePart *part = (struct tablePart *) data; + + onEdited(part, part->textColumn, pathstr, NULL); +} + +void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + GtkCellRenderer *r; + + part = uiprivNew(struct tablePart); + part->type = partButton; + part->textColumn = modelColumn; + part->tv = c->tv; + + r = uiprivNewCellRendererButton(); + g_object_set(r, "sensitive", TRUE, NULL); // editable by default + g_signal_connect(r, "clicked", G_CALLBACK(buttonClicked), part); + + appendPart(c, part, r, expand); +} + +// yes, we need to do all this twice :| +static void checkboxToggled(GtkCellRendererToggle *r, gchar *pathstr, gpointer data) +{ + struct tablePart *part = (struct tablePart *) data; + GtkTreePath *path; + int row; + uiTableModel *m; + void *value; + int intval; + + path = gtk_tree_path_new_from_string(pathstr); + row = gtk_tree_path_get_indices(path)[0]; + gtk_tree_path_free(path); + m = part->tv->model; + value = (*(m->mh->CellValue))(m->mh, m, row, part->valueColumn); + intval = !uiTableModelTakeInt(value); + onEdited(part, part->valueColumn, pathstr, uiTableModelGiveInt(intval)); +} + +void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + GtkCellRenderer *r; + + part = uiprivNew(struct tablePart); + part->type = partCheckbox; + part->valueColumn = modelColumn; + part->tv = c->tv; + + r = gtk_cell_renderer_toggle_new(); + g_object_set(r, "sensitive", TRUE, NULL); // editable by default + g_signal_connect(r, "toggled", G_CALLBACK(checkboxToggled), part); + + appendPart(c, part, r, expand); +} + +void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + + part = uiprivNew(struct tablePart); + part->type = partProgressBar; + part->valueColumn = modelColumn; + part->tv = c->tv; + appendPart(c, part, + gtk_cell_renderer_progress_new(), + expand); +} + +void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) +{ + struct tablePart *p; + + p = (struct tablePart *) g_ptr_array_index(c->parts, part); + switch (p->type) { + case partImage: + case partProgressBar: + return; + case partButton: + case partCheckbox: + g_object_set(p->r, "sensitive", editable != 0, NULL); + return; + } + g_object_set(p->r, "editable", editable != 0, NULL); +} + +void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) +{ + struct tablePart *p; + + p = (struct tablePart *) g_ptr_array_index(c->parts, part); + p->colorColumn = modelColumn; + // TODO refresh table +} + +uiUnixControlAllDefaultsExceptDestroy(uiTable) + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + + // TODO + g_object_unref(t->widget); + uiFreeControl(uiControl(t)); +} + +uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) +{ + uiTableColumn *c; + + c = uiprivNew(uiTableColumn); + c->c = gtk_tree_view_column_new(); + gtk_tree_view_column_set_resizable(c->c, TRUE); + gtk_tree_view_column_set_title(c->c, name); + gtk_tree_view_append_column(t->tv, c->c); + c->tv = t; // TODO rename field to t, cascade + c->parts = g_ptr_array_new(); + return c; +} + +void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) +{ + t->backgroundColumn = modelColumn; + // TODO refresh table +} + +uiTable *uiNewTable(uiTableModel *model) +{ + uiTable *t; + + uiUnixNewControl(uiTable, t); + + t->model = model; + t->backgroundColumn = -1; + + t->widget = gtk_scrolled_window_new(NULL, NULL); + t->scontainer = GTK_CONTAINER(t->widget); + t->sw = GTK_SCROLLED_WINDOW(t->widget); + gtk_scrolled_window_set_shadow_type(t->sw, GTK_SHADOW_IN); + + t->treeWidget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(t->model)); + t->tv = GTK_TREE_VIEW(t->treeWidget); + // TODO set up t->tv + + gtk_container_add(t->scontainer, t->treeWidget); + // and make the tree view visible; only the scrolled window's visibility is controlled by libui + gtk_widget_show(t->treeWidget); + + return t; +} diff --git a/unix/table.c b/unix/table.c index d8cbceaf..179d36b3 100644 --- a/unix/table.c +++ b/unix/table.c @@ -56,13 +56,13 @@ static GType uiTableModel_get_column_type(GtkTreeModel *mm, gint index) uiTableModel *m = uiTableModel(mm); switch ((*(m->mh->ColumnType))(m->mh, m, index)) { - case uiTableModelColumnString: + case uiTableDataTypeString: return G_TYPE_STRING; - case uiTableModelColumnImage: + case uiTableDataTypeImage: return G_TYPE_POINTER; - case uiTableModelColumnInt: + case uiTableDataTypeInt: return G_TYPE_INT; - case uiTableModelColumnColor: + case uiTableDataTypeColor: return GDK_TYPE_RGBA; } // TODO @@ -108,7 +108,9 @@ static void uiTableModel_get_value(GtkTreeModel *mm, GtkTreeIter *iter, gint col { uiTableModel *m = uiTableModel(mm); gint row; - void *data; + uiTableData *data; + double r, g, b, a; + GdkRGBA rgba; if (iter->stamp != STAMP_GOOD) return; @@ -117,19 +119,32 @@ static void uiTableModel_get_value(GtkTreeModel *mm, GtkTreeIter *iter, gint col switch ((*(m->mh->ColumnType))(m->mh, m, column)) { case uiTableModelColumnString: g_value_init(value, G_TYPE_STRING); - g_value_take_string(value, (char *) data); + g_value_set_string(value, uiTableDataString(data)); + uiFreeTableData(data); return; case uiTableModelColumnImage: g_value_init(value, G_TYPE_POINTER); - g_value_set_pointer(value, data); + g_value_set_pointer(value, uiTableDataImage(data)); + uiFreeTableData(data); return; case uiTableModelColumnInt: g_value_init(value, G_TYPE_INT); - g_value_set_int(value, uiTableModelTakeInt(data)); + g_value_set_int(value, uiTableDataInt(data)); + uiFreeTableData(data); return; case uiTableModelColumnColor: g_value_init(value, GDK_TYPE_RGBA); - g_value_take_boxed(value, data); + if (data == NULL) { + g_value_set_boxed(value, NULL); + return; + } + uiTableDataColor(data, &r, &g, &b, &a); + uiFreeTableData(data); + rgba.red = r; + rgba.green = g; + rgba.blue = b; + rgba.alpha = a; + g_value_set_boxed(value, &rgba); return; } // TODO @@ -237,22 +252,6 @@ static void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface) // don't specify ref_node() or unref_node() } -void *uiTableModelStrdup(const char *str) -{ - return g_strdup(str); -} - -void *uiTableModelGiveColor(double r, double g, double b, double a) -{ - GdkRGBA rgba; - - rgba.red = r; - rgba.green = g; - rgba.blue = b; - rgba.alpha = a; - return gdk_rgba_copy(&rgba); -} - uiTableModel *uiNewTableModel(uiTableModelHandler *mh) { uiTableModel *m; @@ -300,6 +299,8 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) gtk_tree_path_free(path); } +============================ TODOTODO + enum { partText, partImage, From 75a5a050cbfbe38ab67b198e1fe0d21837f3261c Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 4 Jun 2018 23:46:30 -0400 Subject: [PATCH 045/166] Split the uiTableModel GTK+ code into its own file, tablemodel.c. --- unix/CMakeLists.txt | 1 + unix/table.c | 301 +------------------------------------------- unix/table.h | 18 +++ unix/tablemodel.c | 283 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 303 insertions(+), 300 deletions(-) create mode 100644 unix/table.h create mode 100644 unix/tablemodel.c diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 1393b92a..5c9425d2 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -44,6 +44,7 @@ list(APPEND _LIBUI_SOURCES unix/stddialogs.c unix/tab.c unix/table.c + unix/tablemodel.c unix/text.c unix/util.c unix/window.c diff --git a/unix/table.c b/unix/table.c index 179d36b3..f9d593ae 100644 --- a/unix/table.c +++ b/unix/table.c @@ -1,305 +1,6 @@ // 26 june 2016 #include "uipriv_unix.h" - -#define uiTableModelType (uiTableModel_get_type()) -#define uiTableModel(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uiTableModelType, uiTableModel)) -#define isuiTableModel(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uiTableModelType)) -#define uiTableModelClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uiTableModelType, uiTableModelClass)) -#define isuiTableModelClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uiTableModel)) -#define getuiTableModelClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uiTableModelType, uiTableModelClass)) - -typedef struct uiTableModelClass uiTableModelClass; - -struct uiTableModel { - GObject parent_instance; - uiTableModelHandler *mh; -}; - -struct uiTableModelClass { - GObjectClass parent_class; -}; - -static void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface); - -G_DEFINE_TYPE_WITH_CODE(uiTableModel, uiTableModel, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, uiTableModel_gtk_tree_model_interface_init)) - -static void uiTableModel_init(uiTableModel *m) -{ - // nothing to do -} - -static void uiTableModel_dispose(GObject *obj) -{ - G_OBJECT_CLASS(uiTableModel_parent_class)->dispose(obj); -} - -static void uiTableModel_finalize(GObject *obj) -{ - G_OBJECT_CLASS(uiTableModel_parent_class)->finalize(obj); -} - -static GtkTreeModelFlags uiTableModel_get_flags(GtkTreeModel *mm) -{ - return GTK_TREE_MODEL_LIST_ONLY; -} - -static gint uiTableModel_get_n_columns(GtkTreeModel *mm) -{ - uiTableModel *m = uiTableModel(mm); - - return (*(m->mh->NumColumns))(m->mh, m); -} - -static GType uiTableModel_get_column_type(GtkTreeModel *mm, gint index) -{ - uiTableModel *m = uiTableModel(mm); - - switch ((*(m->mh->ColumnType))(m->mh, m, index)) { - case uiTableDataTypeString: - return G_TYPE_STRING; - case uiTableDataTypeImage: - return G_TYPE_POINTER; - case uiTableDataTypeInt: - return G_TYPE_INT; - case uiTableDataTypeColor: - return GDK_TYPE_RGBA; - } - // TODO - return G_TYPE_INVALID; -} - -#define STAMP_GOOD 0x1234 -#define STAMP_BAD 0x5678 - -static gboolean uiTableModel_get_iter(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreePath *path) -{ - uiTableModel *m = uiTableModel(mm); - gint row; - - if (gtk_tree_path_get_depth(path) != 1) - goto bad; - row = gtk_tree_path_get_indices(path)[0]; - if (row < 0) - goto bad; - if (row >= (*(m->mh->NumRows))(m->mh, m)) - goto bad; - iter->stamp = STAMP_GOOD; - iter->user_data = GINT_TO_POINTER(row); - return TRUE; -bad: - iter->stamp = STAMP_BAD; - return FALSE; -} - -// GtkListStore returns NULL on error; let's do that too -static GtkTreePath *uiTableModel_get_path(GtkTreeModel *mm, GtkTreeIter *iter) -{ - gint row; - - if (iter->stamp != STAMP_GOOD) - return NULL; - row = GPOINTER_TO_INT(iter->user_data); - return gtk_tree_path_new_from_indices(row, -1); -} - -// GtkListStore leaves value empty on failure; let's do the same -static void uiTableModel_get_value(GtkTreeModel *mm, GtkTreeIter *iter, gint column, GValue *value) -{ - uiTableModel *m = uiTableModel(mm); - gint row; - uiTableData *data; - double r, g, b, a; - GdkRGBA rgba; - - if (iter->stamp != STAMP_GOOD) - return; - row = GPOINTER_TO_INT(iter->user_data); - data = (*(m->mh->CellValue))(m->mh, m, row, column); - switch ((*(m->mh->ColumnType))(m->mh, m, column)) { - case uiTableModelColumnString: - g_value_init(value, G_TYPE_STRING); - g_value_set_string(value, uiTableDataString(data)); - uiFreeTableData(data); - return; - case uiTableModelColumnImage: - g_value_init(value, G_TYPE_POINTER); - g_value_set_pointer(value, uiTableDataImage(data)); - uiFreeTableData(data); - return; - case uiTableModelColumnInt: - g_value_init(value, G_TYPE_INT); - g_value_set_int(value, uiTableDataInt(data)); - uiFreeTableData(data); - return; - case uiTableModelColumnColor: - g_value_init(value, GDK_TYPE_RGBA); - if (data == NULL) { - g_value_set_boxed(value, NULL); - return; - } - uiTableDataColor(data, &r, &g, &b, &a); - uiFreeTableData(data); - rgba.red = r; - rgba.green = g; - rgba.blue = b; - rgba.alpha = a; - g_value_set_boxed(value, &rgba); - return; - } - // TODO -} - -static gboolean uiTableModel_iter_next(GtkTreeModel *mm, GtkTreeIter *iter) -{ - uiTableModel *m = uiTableModel(mm); - gint row; - - if (iter->stamp != STAMP_GOOD) - return FALSE; - row = GPOINTER_TO_INT(iter->user_data); - row++; - if (row >= (*(m->mh->NumRows))(m->mh, m)) { - iter->stamp = STAMP_BAD; - return FALSE; - } - iter->user_data = GINT_TO_POINTER(row); - return TRUE; -} - -static gboolean uiTableModel_iter_previous(GtkTreeModel *mm, GtkTreeIter *iter) -{ - gint row; - - if (iter->stamp != STAMP_GOOD) - return FALSE; - row = GPOINTER_TO_INT(iter->user_data); - row--; - if (row < 0) { - iter->stamp = STAMP_BAD; - return FALSE; - } - iter->user_data = GINT_TO_POINTER(row); - return TRUE; -} - -static gboolean uiTableModel_iter_children(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent) -{ - return gtk_tree_model_iter_nth_child(mm, iter, parent, 0); -} - -static gboolean uiTableModel_iter_has_child(GtkTreeModel *mm, GtkTreeIter *iter) -{ - return FALSE; -} - -static gint uiTableModel_iter_n_children(GtkTreeModel *mm, GtkTreeIter *iter) -{ - uiTableModel *m = uiTableModel(mm); - - if (iter != NULL) - return 0; - return (*(m->mh->NumRows))(m->mh, m); -} - -static gboolean uiTableModel_iter_nth_child(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent, gint n) -{ - uiTableModel *m = uiTableModel(mm); - - if (iter->stamp != STAMP_GOOD) - return FALSE; - if (parent != NULL) - goto bad; - if (n < 0) - goto bad; - if (n >= (*(m->mh->NumRows))(m->mh, m)) - goto bad; - iter->stamp = STAMP_GOOD; - iter->user_data = GINT_TO_POINTER(n); - return TRUE; -bad: - iter->stamp = STAMP_BAD; - return FALSE; -} - -gboolean uiTableModel_iter_parent(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *child) -{ - iter->stamp = STAMP_BAD; - return FALSE; -} - -static void uiTableModel_class_init(uiTableModelClass *class) -{ - G_OBJECT_CLASS(class)->dispose = uiTableModel_dispose; - G_OBJECT_CLASS(class)->finalize = uiTableModel_finalize; -} - -static void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface) -{ - iface->get_flags = uiTableModel_get_flags; - iface->get_n_columns = uiTableModel_get_n_columns; - iface->get_column_type = uiTableModel_get_column_type; - iface->get_iter = uiTableModel_get_iter; - iface->get_path = uiTableModel_get_path; - iface->get_value = uiTableModel_get_value; - iface->iter_next = uiTableModel_iter_next; - iface->iter_previous = uiTableModel_iter_previous; - iface->iter_children = uiTableModel_iter_children; - iface->iter_has_child = uiTableModel_iter_has_child; - iface->iter_n_children = uiTableModel_iter_n_children; - iface->iter_nth_child = uiTableModel_iter_nth_child; - iface->iter_parent = uiTableModel_iter_parent; - // don't specify ref_node() or unref_node() -} - -uiTableModel *uiNewTableModel(uiTableModelHandler *mh) -{ - uiTableModel *m; - - m = uiTableModel(g_object_new(uiTableModelType, NULL)); - m->mh = mh; - return m; -} - -void uiFreeTableModel(uiTableModel *m) -{ - g_object_unref(m); -} - -void uiTableModelRowInserted(uiTableModel *m, int newIndex) -{ - GtkTreePath *path; - GtkTreeIter iter; - - path = gtk_tree_path_new_from_indices(newIndex, -1); - iter.stamp = STAMP_GOOD; - iter.user_data = GINT_TO_POINTER(newIndex); - gtk_tree_model_row_inserted(GTK_TREE_MODEL(m), path, &iter); - gtk_tree_path_free(path); -} - -void uiTableModelRowChanged(uiTableModel *m, int index) -{ - GtkTreePath *path; - GtkTreeIter iter; - - path = gtk_tree_path_new_from_indices(index, -1); - iter.stamp = STAMP_GOOD; - iter.user_data = GINT_TO_POINTER(index); - gtk_tree_model_row_changed(GTK_TREE_MODEL(m), path, &iter); - gtk_tree_path_free(path); -} - -void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) -{ - GtkTreePath *path; - - path = gtk_tree_path_new_from_indices(oldIndex, -1); - gtk_tree_model_row_deleted(GTK_TREE_MODEL(m), path); - gtk_tree_path_free(path); -} - -============================ TODOTODO +#include "table.h" enum { partText, diff --git a/unix/table.h b/unix/table.h new file mode 100644 index 00000000..a023e7f5 --- /dev/null +++ b/unix/table.h @@ -0,0 +1,18 @@ +// 4 june 2018 + +// tablemodel.c +#define uiTableModelType (uiTableModel_get_type()) +#define uiTableModel(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uiTableModelType, uiTableModel)) +#define isuiTableModel(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uiTableModelType)) +#define uiTableModelClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uiTableModelType, uiTableModelClass)) +#define isuiTableModelClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uiTableModel)) +#define getuiTableModelClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uiTableModelType, uiTableModelClass)) +typedef struct uiTableModelClass uiTableModelClass; +struct uiTableModel { + GObject parent_instance; + uiTableModelHandler *mh; +}; +struct uiTableModelClass { + GObjectClass parent_class; +}; +extern GType uiTableModel_get_type(void); diff --git a/unix/tablemodel.c b/unix/tablemodel.c new file mode 100644 index 00000000..7ecc05ed --- /dev/null +++ b/unix/tablemodel.c @@ -0,0 +1,283 @@ +// 26 june 2016 +#include "uipriv_unix.h" +#include "table.h" + +static void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface); + +G_DEFINE_TYPE_WITH_CODE(uiTableModel, uiTableModel, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, uiTableModel_gtk_tree_model_interface_init)) + +static void uiTableModel_init(uiTableModel *m) +{ + // nothing to do +} + +static void uiTableModel_dispose(GObject *obj) +{ + G_OBJECT_CLASS(uiTableModel_parent_class)->dispose(obj); +} + +static void uiTableModel_finalize(GObject *obj) +{ + G_OBJECT_CLASS(uiTableModel_parent_class)->finalize(obj); +} + +static GtkTreeModelFlags uiTableModel_get_flags(GtkTreeModel *mm) +{ + return GTK_TREE_MODEL_LIST_ONLY; +} + +static gint uiTableModel_get_n_columns(GtkTreeModel *mm) +{ + uiTableModel *m = uiTableModel(mm); + + return (*(m->mh->NumColumns))(m->mh, m); +} + +static GType uiTableModel_get_column_type(GtkTreeModel *mm, gint index) +{ + uiTableModel *m = uiTableModel(mm); + + switch ((*(m->mh->ColumnType))(m->mh, m, index)) { + case uiTableDataTypeString: + return G_TYPE_STRING; + case uiTableDataTypeImage: + return G_TYPE_POINTER; + case uiTableDataTypeInt: + return G_TYPE_INT; + case uiTableDataTypeColor: + return GDK_TYPE_RGBA; + } + // TODO + return G_TYPE_INVALID; +} + +#define STAMP_GOOD 0x1234 +#define STAMP_BAD 0x5678 + +static gboolean uiTableModel_get_iter(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreePath *path) +{ + uiTableModel *m = uiTableModel(mm); + gint row; + + if (gtk_tree_path_get_depth(path) != 1) + goto bad; + row = gtk_tree_path_get_indices(path)[0]; + if (row < 0) + goto bad; + if (row >= (*(m->mh->NumRows))(m->mh, m)) + goto bad; + iter->stamp = STAMP_GOOD; + iter->user_data = GINT_TO_POINTER(row); + return TRUE; +bad: + iter->stamp = STAMP_BAD; + return FALSE; +} + +// GtkListStore returns NULL on error; let's do that too +static GtkTreePath *uiTableModel_get_path(GtkTreeModel *mm, GtkTreeIter *iter) +{ + gint row; + + if (iter->stamp != STAMP_GOOD) + return NULL; + row = GPOINTER_TO_INT(iter->user_data); + return gtk_tree_path_new_from_indices(row, -1); +} + +// GtkListStore leaves value empty on failure; let's do the same +static void uiTableModel_get_value(GtkTreeModel *mm, GtkTreeIter *iter, gint column, GValue *value) +{ + uiTableModel *m = uiTableModel(mm); + gint row; + uiTableData *data; + double r, g, b, a; + GdkRGBA rgba; + + if (iter->stamp != STAMP_GOOD) + return; + row = GPOINTER_TO_INT(iter->user_data); + data = (*(m->mh->CellValue))(m->mh, m, row, column); + switch ((*(m->mh->ColumnType))(m->mh, m, column)) { + case uiTableModelColumnString: + g_value_init(value, G_TYPE_STRING); + g_value_set_string(value, uiTableDataString(data)); + uiFreeTableData(data); + return; + case uiTableModelColumnImage: + g_value_init(value, G_TYPE_POINTER); + g_value_set_pointer(value, uiTableDataImage(data)); + uiFreeTableData(data); + return; + case uiTableModelColumnInt: + g_value_init(value, G_TYPE_INT); + g_value_set_int(value, uiTableDataInt(data)); + uiFreeTableData(data); + return; + case uiTableModelColumnColor: + g_value_init(value, GDK_TYPE_RGBA); + if (data == NULL) { + g_value_set_boxed(value, NULL); + return; + } + uiTableDataColor(data, &r, &g, &b, &a); + uiFreeTableData(data); + rgba.red = r; + rgba.green = g; + rgba.blue = b; + rgba.alpha = a; + g_value_set_boxed(value, &rgba); + return; + } + // TODO +} + +static gboolean uiTableModel_iter_next(GtkTreeModel *mm, GtkTreeIter *iter) +{ + uiTableModel *m = uiTableModel(mm); + gint row; + + if (iter->stamp != STAMP_GOOD) + return FALSE; + row = GPOINTER_TO_INT(iter->user_data); + row++; + if (row >= (*(m->mh->NumRows))(m->mh, m)) { + iter->stamp = STAMP_BAD; + return FALSE; + } + iter->user_data = GINT_TO_POINTER(row); + return TRUE; +} + +static gboolean uiTableModel_iter_previous(GtkTreeModel *mm, GtkTreeIter *iter) +{ + gint row; + + if (iter->stamp != STAMP_GOOD) + return FALSE; + row = GPOINTER_TO_INT(iter->user_data); + row--; + if (row < 0) { + iter->stamp = STAMP_BAD; + return FALSE; + } + iter->user_data = GINT_TO_POINTER(row); + return TRUE; +} + +static gboolean uiTableModel_iter_children(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent) +{ + return gtk_tree_model_iter_nth_child(mm, iter, parent, 0); +} + +static gboolean uiTableModel_iter_has_child(GtkTreeModel *mm, GtkTreeIter *iter) +{ + return FALSE; +} + +static gint uiTableModel_iter_n_children(GtkTreeModel *mm, GtkTreeIter *iter) +{ + uiTableModel *m = uiTableModel(mm); + + if (iter != NULL) + return 0; + return (*(m->mh->NumRows))(m->mh, m); +} + +static gboolean uiTableModel_iter_nth_child(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent, gint n) +{ + uiTableModel *m = uiTableModel(mm); + + if (iter->stamp != STAMP_GOOD) + return FALSE; + if (parent != NULL) + goto bad; + if (n < 0) + goto bad; + if (n >= (*(m->mh->NumRows))(m->mh, m)) + goto bad; + iter->stamp = STAMP_GOOD; + iter->user_data = GINT_TO_POINTER(n); + return TRUE; +bad: + iter->stamp = STAMP_BAD; + return FALSE; +} + +gboolean uiTableModel_iter_parent(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *child) +{ + iter->stamp = STAMP_BAD; + return FALSE; +} + +static void uiTableModel_class_init(uiTableModelClass *class) +{ + G_OBJECT_CLASS(class)->dispose = uiTableModel_dispose; + G_OBJECT_CLASS(class)->finalize = uiTableModel_finalize; +} + +static void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface) +{ + iface->get_flags = uiTableModel_get_flags; + iface->get_n_columns = uiTableModel_get_n_columns; + iface->get_column_type = uiTableModel_get_column_type; + iface->get_iter = uiTableModel_get_iter; + iface->get_path = uiTableModel_get_path; + iface->get_value = uiTableModel_get_value; + iface->iter_next = uiTableModel_iter_next; + iface->iter_previous = uiTableModel_iter_previous; + iface->iter_children = uiTableModel_iter_children; + iface->iter_has_child = uiTableModel_iter_has_child; + iface->iter_n_children = uiTableModel_iter_n_children; + iface->iter_nth_child = uiTableModel_iter_nth_child; + iface->iter_parent = uiTableModel_iter_parent; + // don't specify ref_node() or unref_node() +} + +uiTableModel *uiNewTableModel(uiTableModelHandler *mh) +{ + uiTableModel *m; + + m = uiTableModel(g_object_new(uiTableModelType, NULL)); + m->mh = mh; + return m; +} + +void uiFreeTableModel(uiTableModel *m) +{ + g_object_unref(m); +} + +void uiTableModelRowInserted(uiTableModel *m, int newIndex) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_indices(newIndex, -1); + iter.stamp = STAMP_GOOD; + iter.user_data = GINT_TO_POINTER(newIndex); + gtk_tree_model_row_inserted(GTK_TREE_MODEL(m), path, &iter); + gtk_tree_path_free(path); +} + +void uiTableModelRowChanged(uiTableModel *m, int index) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_indices(index, -1); + iter.stamp = STAMP_GOOD; + iter.user_data = GINT_TO_POINTER(index); + gtk_tree_model_row_changed(GTK_TREE_MODEL(m), path, &iter); + gtk_tree_path_free(path); +} + +void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) +{ + GtkTreePath *path; + + path = gtk_tree_path_new_from_indices(oldIndex, -1); + gtk_tree_model_row_deleted(GTK_TREE_MODEL(m), path); + gtk_tree_path_free(path); +} From 18d8a8fe2271b28c4fa91a55e694786059561427 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 5 Jun 2018 22:00:54 -0400 Subject: [PATCH 046/166] Converted column functions and editable handlers on GTK+. --- unix/table.c | 298 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 194 insertions(+), 104 deletions(-) diff --git a/unix/table.c b/unix/table.c index f9d593ae..698a0237 100644 --- a/unix/table.c +++ b/unix/table.c @@ -2,30 +2,6 @@ #include "uipriv_unix.h" #include "table.h" -enum { - partText, - partImage, - partButton, - partCheckbox, - partProgressBar, -}; - -struct tablePart { - int type; - int textColumn; - int imageColumn; - int valueColumn; - int colorColumn; - GtkCellRenderer *r; - uiTable *tv; // for pixbufs and background color -}; - -struct uiTableColumn { - GtkTreeViewColumn *c; - uiTable *tv; // for pixbufs and background color - GPtrArray *parts; -}; - struct uiTable { uiUnixControl c; GtkWidget *widget; @@ -33,14 +9,13 @@ struct uiTable { GtkScrolledWindow *sw; GtkWidget *treeWidget; GtkTreeView *tv; - GPtrArray *columns; uiTableModel *model; int backgroundColumn; }; // use the same size as GtkFileChooserWidget's treeview // TODO refresh when icon theme changes -// TODO doesn't work when scaled +// TODO doesn't work when scaled? // TODO is this even necessary? static void setImageSize(GtkCellRenderer *r) { @@ -57,12 +32,12 @@ static void setImageSize(GtkCellRenderer *r) 2 * ypad + size); } -static void applyColor(GtkTreeModel *mm, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop, const char *propSet) +static void applyColor(GtkTreeModel *m, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop, const char *propSet) { GValue value = G_VALUE_INIT; GdkRGBA *rgba; - gtk_tree_model_get_value(mm, iter, modelColumn, &value); + gtk_tree_model_get_value(m, iter, modelColumn, &value); rgba = (GdkRGBA *) g_value_get_boxed(&value); if (rgba != NULL) g_object_set(r, prop, rgba, NULL); @@ -71,76 +46,217 @@ static void applyColor(GtkTreeModel *mm, GtkTreeIter *iter, int modelColumn, Gtk g_value_unset(&value); } -static void dataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *mm, GtkTreeIter *iter, gpointer data) +static void setEditable(uiTableModel *m, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop) { - struct tablePart *part = (struct tablePart *) data; + uiTableData *data; GValue value = G_VALUE_INIT; - const gchar *str; - uiImage *img; - int pval; + int value; + gboolean editable; - switch (part->type) { - case partText: - gtk_tree_model_get_value(mm, iter, part->textColumn, &value); - str = g_value_get_string(&value); - g_object_set(r, "text", str, NULL); - if (part->colorColumn != -1) - applyColor(mm, iter, - part->colorColumn, - r, "foreground-rgba", "foreground-set"); + switch (modelColumn) { + case uiTableModelColumnNeverEditable: + editable = FALSE; break; - case partImage: -//TODO setImageSize(r); - gtk_tree_model_get_value(mm, iter, part->imageColumn, &value); - img = (uiImage *) g_value_get_pointer(&value); - g_object_set(r, "surface", - uiprivImageAppropriateSurface(img, part->tv->treeWidget), - NULL); - break; - case partButton: - gtk_tree_model_get_value(mm, iter, part->textColumn, &value); - str = g_value_get_string(&value); - g_object_set(r, "text", str, NULL); - break; - case partCheckbox: - gtk_tree_model_get_value(mm, iter, part->valueColumn, &value); - g_object_set(r, "active", g_value_get_int(&value) != 0, NULL); - break; - case partProgressBar: - gtk_tree_model_get_value(mm, iter, part->valueColumn, &value); - pval = g_value_get_int(&value); - if (pval == -1) { - // TODO - } else - g_object_set(r, - "pulse", -1, - "value", pval, - NULL); + case uiTableModelColumnAlwaysEditable: + editable = TRUE; break; + default: + gtk_tree_model_get_value(m, iter, p->editableColumn, &value); + editable = gtk_value_get_int(&value) != 0; + g_value_unset(&value); } - g_value_unset(&value); + g_object_set(r, "editable", editable, NULL); +} - if (part->tv->backgroundColumn != -1) - applyColor(mm, iter, - part->tv->backgroundColumn, +static void applyBackgroundColor(uiTable *t, GtkTreeModel *m, GtkTreeIter *iter, GtkCellRenderer *r) +{ + if (t->backgroundColumn != -1) + applyColor(m, iter, t->backgroundColumn, r, "cell-background-rgba", "cell-background-set"); } -static void onEdited(struct tablePart *part, int column, const char *pathstr, const void *data) +static void onEdited(uiTableModel *m, int column, const char *pathstr, const uiTableData *data, GtkTreeIter *iter) { GtkTreePath *path; int row; - uiTableModel *m; path = gtk_tree_path_new_from_string(pathstr); row = gtk_tree_path_get_indices(path)[0]; + if (iter != NULL) + gtk_tree_model_convert_path_to_iter(m, path, iter); gtk_tree_path_free(path); - m = part->tv->model; (*(m->mh->SetCellValue))(m->mh, m, row, column, data); - // and update - uiTableModelRowChanged(m, row); } +// TODO deduplicate this between platforms +static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { + .ColorModelColumn = -1, +}; + +struct textColumnParams { + uiTable *t; + uiTableModel *m; + int modelColumn; + int editableColumn; + uiTableTextColumnOptionalParams params; +}; + +static void textColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + struct textColumnParams *p = (struct textColumnParams *) data; + GValue value = G_VALUE_INIT; + const gchar *str; + gboolean editable; + + gtk_tree_model_get_value(m, iter, p->modelColumn, &value); + str = g_value_get_string(&value); + g_object_set(r, "text", str, NULL); + g_value_unset(&value); + + setEditable(m, iter, p->editableColumn, r, "editable"); + + if (p->params.ColorModelColumn != -1) + applyColor(m, iter, p->params.ColorModelColumn, + r, "foreground-rgba", "foreground-set"); + + applyBackgroundColor(p->t, m, iter, r); +} + +static void textColumnEdited(GtkCellRendererText *renderer, gchar *path, gchar *newText, gpointer data) +{ + struct textColumnParams *p = (struct textColumnParams *) data; + uiTableData *data; + GtkTreeIter iter; + + data = uiNewTableDataString(newText); + onEdited(p->m, p->textColumn, path, data, &iter); + uiFreeData(data); + // and update the column TODO copy comment here + textColumnDataFunc(NULL, r, GTK_TREE_MODEL(p->m), &iter, data); +} + +struct imageColumnParams { + uiTable *t; + int modelColumn; +}; + +static void imageColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + struct imageColumnParams *p = (struct imageColumnParams *) data; + GValue value = G_VALUE_INIT; + uiImage *img; + +//TODO setImageSize(r); + gtk_tree_model_get_value(m, iter, p->modelColumn, &value); + img = (uiImage *) g_value_get_pointer(&value); + g_object_set(r, "surface", + uiprivImageAppropriateSurface(img, p->t->treeWidget), + NULL); + g_value_unset(&value); + + applyBackgroundColor(p->t, m, iter, r); +} + +struct checkboxColumnParams { + uiTable *t; + uiTableModel *m; + int modelColumn; + int editableColumn; +}; + +static void checkboxColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + struct checkboxColumnParams *p = (struct checkboxColumnParams *) data; + GValue value = G_VALUE_INIT; + gboolean active; + + gtk_tree_model_get_value(m, iter, p->modelColumn, &value); + active = g_value_get_int(&value) != 0; + g_object_set(r, "active", active, NULL); + g_value_unset(&value); + + setEditable(m, iter, p->editableColumn, r, "activatable"); + + applyBackgroundColor(p->t, m, iter, r); +} + +static void checkboxColumnToggled(GtkCellRendererToggle *r, gchar *pathstr, gpointer data) +{ + struct checkboxColumnParams *p = (struct checkboxColumnParams *) data; + GValue value = G_VALUE_INIT; + int v; + uiTableData *data; + GtkTreeIter iter; + + gtk_tree_model_get_value(p->m, iter, p->modelColumn, &value); + v = g_value_get_int(&value); + g_value_unset(&value); + data = uiNewTableDataInt(!v); + onEdited(p->m, p->modelColumn, path, data, &iter); + uiFreeData(data); + // and update the column TODO copy comment here + // TODO avoid fetching the model data twice + checkboxColumnDataFunc(NULL, r, GTK_TREE_MODEL(p->m), &iter, data); +} + +struct progressBarColumnParams { + uiTable *t; + int modelColumn; +}; + +static void progressBarDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + struct progressBarColumnParams *p = (struct progressBarColumnParams *) data; + GValue value = G_VALUE_INIT; + int pval; + + gtk_tree_model_get_value(m, iter, p->modelColumn, &value); + pval = g_value_get_int(&value); + if (pval == -1) { + // TODO + } else + g_object_set(r, + "pulse", -1, + "value", pval, + NULL); + g_value_unset(&value); + + applyBackgroundColor(p->t, m, iter, r); +} + +struct buttonColumnParams { + uiTable *t; + uiTableModel *m; + int modelColumn; + int clickableColumn; +}; + +static void buttonDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + struct buttonColumnParams *p = (struct buttonColumnParams *) data; + GValue value = G_VALUE_INIT; + const gchar *str; + gboolean clickable; + + gtk_tree_model_get_value(m, iter, p->modelColumn, &value); + str = g_value_get_string(&value); + g_object_set(r, "text", str, NULL); + g_value_unset(&value); + + setEditable(m, iter, p->clickableColumn, r, "clickable"); + + applyBackgroundColor(p->t, m, iter, r); +} + +static void buttonColumnClicked(uiprivCellRendererButton *r, gchar *pathstr, gpointer data) +{ + struct buttonColumnParams *p = (struct buttonColumnParams *) data; + + onEdited(p->m, p->modelColumn, path, NULL, NULL); +} + +=================== TODOTODO + static void appendPart(uiTableColumn *c, struct tablePart *part, GtkCellRenderer *r, int expand) { part->r = r; @@ -149,13 +265,6 @@ static void appendPart(uiTableColumn *c, struct tablePart *part, GtkCellRenderer g_ptr_array_add(c->parts, part); } -static void textEdited(GtkCellRendererText *renderer, gchar *path, gchar *newText, gpointer data) -{ - struct tablePart *part = (struct tablePart *) data; - - onEdited(part, part->textColumn, path, newText); -} - void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) { struct tablePart *part; @@ -212,25 +321,6 @@ void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand appendPart(c, part, r, expand); } -// yes, we need to do all this twice :| -static void checkboxToggled(GtkCellRendererToggle *r, gchar *pathstr, gpointer data) -{ - struct tablePart *part = (struct tablePart *) data; - GtkTreePath *path; - int row; - uiTableModel *m; - void *value; - int intval; - - path = gtk_tree_path_new_from_string(pathstr); - row = gtk_tree_path_get_indices(path)[0]; - gtk_tree_path_free(path); - m = part->tv->model; - value = (*(m->mh->CellValue))(m->mh, m, row, part->valueColumn); - intval = !uiTableModelTakeInt(value); - onEdited(part, part->valueColumn, pathstr, uiTableModelGiveInt(intval)); -} - void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) { struct tablePart *part; From b7151388e392836dc294d2253a959c94ba7a7412 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 5 Jun 2018 22:47:11 -0400 Subject: [PATCH 047/166] Started rewriting the column constructors. --- unix/table.c | 77 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 21 deletions(-) diff --git a/unix/table.c b/unix/table.c index 698a0237..2806e743 100644 --- a/unix/table.c +++ b/unix/table.c @@ -10,6 +10,7 @@ struct uiTable { GtkWidget *treeWidget; GtkTreeView *tv; uiTableModel *model; + GPtrArray *columnParams; int backgroundColumn; }; @@ -255,34 +256,68 @@ static void buttonColumnClicked(uiprivCellRendererButton *r, gchar *pathstr, gpo onEdited(p->m, p->modelColumn, path, NULL, NULL); } -=================== TODOTODO - -static void appendPart(uiTableColumn *c, struct tablePart *part, GtkCellRenderer *r, int expand) +static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *params) { - part->r = r; - gtk_tree_view_column_pack_start(c->c, part->r, expand != 0); - gtk_tree_view_column_set_cell_data_func(c->c, part->r, dataFunc, part, NULL); - g_ptr_array_add(c->parts, part); -} - -void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) -{ - struct tablePart *part; + struct textColumnParams *p; GtkCellRenderer *r; - part = uiprivNew(struct tablePart); - part->type = partText; - part->textColumn = modelColumn; - part->tv = c->tv; - part->colorColumn = -1; + p = uiprivNew(struct textColumnParams); + p->t = t; + xx TODO get rid of these fields in favor of t->m + p->m = t->m; + p->modelColumn = textModelColumn; + p->editableColumn = textEditableModelColumn; + if (params != NULL) + p->params = *params; + else + p->params = defaultTextColumnOptionalParams; r = gtk_cell_renderer_text_new(); - g_object_set(r, "editable", FALSE, NULL); - g_signal_connect(r, "edited", G_CALLBACK(textEdited), part); - - appendPart(c, part, r, expand); + gtk_tree_view_column_pack_start(c, r, TRUE); + gtk_tree_view_column_set_cell_data_func(c, r, textColumnDataFunc, p, NULL); + g_signal_connect(r, "edited", G_CALLBACK(textColumnEdited), p); + g_ptr_array_add(c->columnParams, p); } +void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *params) +{ + GtkTreeViewColumn *c; + + c = gtk_tree_view_column_new(); + gtk_tree_view_column_set_resizable(c, TRUE); + gtk_tree_view_column_set_title(c, name); + gtk_tree_view_append_column(t->tv, c); + addTextColumn(t, c, textModelColumn, textEditableModelColumn, params); +} + +_UI_EXTERN void uiTableAppendImageColumn(uiTable *t, + const char *name, + int imageModelColumn); +_UI_EXTERN void uiTableAppendImageTextColumn(uiTable *t, + const char *name, + int imageModelColumn, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *textParams); +_UI_EXTERN void uiTableAppendCheckboxColumn(uiTable *t, + const char *name, + int checkboxModelColumn, + int checkboxEditableModelColumn); +_UI_EXTERN void uiTableAppendCheckboxTextColumn(uiTable *t, + const char *name, + int checkboxModelColumn, + int checkboxEditableModelColumn, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *textParams); +_UI_EXTERN void uiTableAppendProgressBarColumn(uiTable *t, + const char *name, + int progressModelColumn); +_UI_EXTERN void uiTableAppendButtonColumn(uiTable *t, + const char *name, + int buttonTextModelColumn, + int buttonClickableModelColumn); + void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) { struct tablePart *part; From d6ac22a20a6423656e2d589e06b750d75146653b Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Thu, 7 Jun 2018 21:50:38 -0400 Subject: [PATCH 048/166] Finished rewriting table.c. Now to test. --- unix/table.c | 219 +++++++++++++++++++++++---------------------------- 1 file changed, 100 insertions(+), 119 deletions(-) diff --git a/unix/table.c b/unix/table.c index 2806e743..e8d5a52b 100644 --- a/unix/table.c +++ b/unix/table.c @@ -205,7 +205,7 @@ struct progressBarColumnParams { int modelColumn; }; -static void progressBarDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +static void progressBarColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) { struct progressBarColumnParams *p = (struct progressBarColumnParams *) data; GValue value = G_VALUE_INIT; @@ -232,7 +232,7 @@ struct buttonColumnParams { int clickableColumn; }; -static void buttonDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +static void buttonColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) { struct buttonColumnParams *p = (struct buttonColumnParams *) data; GValue value = G_VALUE_INIT; @@ -244,11 +244,12 @@ static void buttonDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeMode g_object_set(r, "text", str, NULL); g_value_unset(&value); - setEditable(m, iter, p->clickableColumn, r, "clickable"); + setEditable(m, iter, p->clickableColumn, r, "sensitive"); applyBackgroundColor(p->t, m, iter, r); } +// TODO wrong type here static void buttonColumnClicked(uiprivCellRendererButton *r, gchar *pathstr, gpointer data) { struct buttonColumnParams *p = (struct buttonColumnParams *) data; @@ -256,6 +257,17 @@ static void buttonColumnClicked(uiprivCellRendererButton *r, gchar *pathstr, gpo onEdited(p->m, p->modelColumn, path, NULL, NULL); } +static GtkTreeViewColumn *addColumn(uiTable *t, const char *name) +{ + GtkTreeViewColumn *c; + + c = gtk_tree_view_column_new(); + gtk_tree_view_column_set_resizable(c, TRUE); + gtk_tree_view_column_set_title(c, name); + gtk_tree_view_append_column(t->tv, c); + return c; +} + static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *params) { struct textColumnParams *p; @@ -263,7 +275,7 @@ static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, p = uiprivNew(struct textColumnParams); p->t = t; - xx TODO get rid of these fields in favor of t->m + // TODO get rid of these fields in favor of t->m p->m = t->m; p->modelColumn = textModelColumn; p->editableColumn = textEditableModelColumn; @@ -279,137 +291,120 @@ static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, g_ptr_array_add(c->columnParams, p); } +// TODO rename modelCOlumn and params everywhere void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *params) { GtkTreeViewColumn *c; - c = gtk_tree_view_column_new(); - gtk_tree_view_column_set_resizable(c, TRUE); - gtk_tree_view_column_set_title(c, name); - gtk_tree_view_append_column(t->tv, c); + c = addColumn(t, name); addTextColumn(t, c, textModelColumn, textEditableModelColumn, params); } -_UI_EXTERN void uiTableAppendImageColumn(uiTable *t, - const char *name, - int imageModelColumn); -_UI_EXTERN void uiTableAppendImageTextColumn(uiTable *t, - const char *name, - int imageModelColumn, - int textModelColumn, - int textEditableModelColumn, - uiTableTextColumnOptionalParams *textParams); -_UI_EXTERN void uiTableAppendCheckboxColumn(uiTable *t, - const char *name, - int checkboxModelColumn, - int checkboxEditableModelColumn); -_UI_EXTERN void uiTableAppendCheckboxTextColumn(uiTable *t, - const char *name, - int checkboxModelColumn, - int checkboxEditableModelColumn, - int textModelColumn, - int textEditableModelColumn, - uiTableTextColumnOptionalParams *textParams); -_UI_EXTERN void uiTableAppendProgressBarColumn(uiTable *t, - const char *name, - int progressModelColumn); -_UI_EXTERN void uiTableAppendButtonColumn(uiTable *t, - const char *name, - int buttonTextModelColumn, - int buttonClickableModelColumn); - -void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) +static void addImageColumn(uiTable *t, GtkTreeViewColumn *c, int imageModelColumn) { - struct tablePart *part; - - part = uiprivNew(struct tablePart); - part->type = partImage; - part->imageColumn = modelColumn; - part->tv = c->tv; - appendPart(c, part, - gtk_cell_renderer_pixbuf_new(), - expand); -} - -// TODO wrong type here -static void buttonClicked(GtkCellRenderer *r, gchar *pathstr, gpointer data) -{ - struct tablePart *part = (struct tablePart *) data; - - onEdited(part, part->textColumn, pathstr, NULL); -} - -void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand) -{ - struct tablePart *part; + struct imageColumnParams *p; GtkCellRenderer *r; - part = uiprivNew(struct tablePart); - part->type = partButton; - part->textColumn = modelColumn; - part->tv = c->tv; + p = uiprivNew(struct imageColumnParams); + p->t = t; + p->modelColumn = imageModelColumn; - r = uiprivNewCellRendererButton(); - g_object_set(r, "sensitive", TRUE, NULL); // editable by default - g_signal_connect(r, "clicked", G_CALLBACK(buttonClicked), part); - - appendPart(c, part, r, expand); + r = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(c, r, FALSE); + gtk_tree_view_column_set_cell_data_func(c, r, imageColumnDataFunc, p, NULL); + g_ptr_array_add(c->columnParams, p); } -void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) +void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) { - struct tablePart *part; + GtkTreeViewColumn *c; + + c = addColumn(t, name); + addImageColumn(t, c, imageModelColumn); +} + +void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + GtkTreeViewColumn *c; + + c = addColumn(t, name); + addImageColumn(t, c, imageModelColumn); + addTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams); +} + +static void uiTableAppendCheckboxColumn(uiTable *t, GtkTableViewColumn *c, int checkboxModelColumn, int checkboxEditableModelColumn) +{ + struct checkboxColumnParams *p; GtkCellRenderer *r; - part = uiprivNew(struct tablePart); - part->type = partCheckbox; - part->valueColumn = modelColumn; - part->tv = c->tv; + p = uiprivNew(struct checkboxColumnParams); + p->t = t; + p->m = t->m; + p->modelColumn = checkboxModelColumn; + p->editableColumn = checkboxEditableModelColumn; r = gtk_cell_renderer_toggle_new(); - g_object_set(r, "sensitive", TRUE, NULL); // editable by default - g_signal_connect(r, "toggled", G_CALLBACK(checkboxToggled), part); - - appendPart(c, part, r, expand); + gtk_tree_view_column_pack_start(c, r, FALSE); + gtk_tree_view_column_set_cell_data_func(c, r, checkboxColumnDataFunc, p, NULL); + g_signal_connect(r, "toggled", G_CALLBACK(checkboxColumnToggled), part); + g_ptr_array_add(c->columnParams, p); } -void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand) +void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) { - struct tablePart *part; + GtkTreeViewColumn *c; - part = uiprivNew(struct tablePart); - part->type = partProgressBar; - part->valueColumn = modelColumn; - part->tv = c->tv; - appendPart(c, part, - gtk_cell_renderer_progress_new(), - expand); + c = addColumn(t, name); + addCheckboxColumn(t, c, checkboxModelColumn, checkboxEditableModelColumn); } -void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) +void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) { - struct tablePart *p; + GtkTreeViewColumn *c; - p = (struct tablePart *) g_ptr_array_index(c->parts, part); - switch (p->type) { - case partImage: - case partProgressBar: - return; - case partButton: - case partCheckbox: - g_object_set(p->r, "sensitive", editable != 0, NULL); - return; - } - g_object_set(p->r, "editable", editable != 0, NULL); + c = addColumn(t, name); + addCheckboxColumn(t, c, checkboxModelColumn, checkboxEditableModelColumn); + addTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams); } -void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) +void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) { - struct tablePart *p; + GtkTreeViewColumn *c; + struct progressBarColumnParams *p; + GtkCellRenderer *r; - p = (struct tablePart *) g_ptr_array_index(c->parts, part); - p->colorColumn = modelColumn; - // TODO refresh table + c = addColumn(t, name); + + p = uiprivNew(struct progressBarColumnParams); + p->t = t; + // TODO make progress and progressBar consistent everywhere + p->modelColumn = progressModelColumn; + + r = gtk_cell_renderer_progress_new(); + gtk_tree_view_column_pack_start(c, r, TRUE); + gtk_tree_view_column_set_cell_data_func(c, r, progressBarColumnDataFunc, p, NULL); + g_ptr_array_add(c->columnParams, p); +} + +void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModelColumn, int buttonClickableModelColumn) +{ + GtkTreeViewColumn *c; + struct buttonColumnParams *p; + GtkCellRenderer *r; + + c = addColumn(t, name); + + p = uiprivNew(struct buttonColumnParams); + p->t = t; + p->m = t->m; + p->modelColumn = buttonModelColumn; + p->clickableColumn = buttonClickableModelColumn; + + r = uiprivNewCellRendererButton(); + gtk_tree_view_column_pack_start(c, r, TRUE); + gtk_tree_view_column_set_cell_data_func(c, r, buttonColumnDataFunc, p, NULL); + g_signal_connect(r, "clicked", G_CALLBACK(buttonColumnClicked), p); + g_ptr_array_add(c->columnParams, p); } uiUnixControlAllDefaultsExceptDestroy(uiTable) @@ -423,20 +418,6 @@ static void uiTableDestroy(uiControl *c) uiFreeControl(uiControl(t)); } -uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) -{ - uiTableColumn *c; - - c = uiprivNew(uiTableColumn); - c->c = gtk_tree_view_column_new(); - gtk_tree_view_column_set_resizable(c->c, TRUE); - gtk_tree_view_column_set_title(c->c, name); - gtk_tree_view_append_column(t->tv, c->c); - c->tv = t; // TODO rename field to t, cascade - c->parts = g_ptr_array_new(); - return c; -} - void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) { t->backgroundColumn = modelColumn; From 66ca3315cf53b229fdf73a9d6a9d7342afdc75ca Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Thu, 7 Jun 2018 22:25:17 -0400 Subject: [PATCH 049/166] Fixed compile errors. Now for runtime errors, if any! --- unix/table.c | 73 ++++++++++++++++++++++++----------------------- unix/tablemodel.c | 8 +++--- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/unix/table.c b/unix/table.c index e8d5a52b..fe347e4e 100644 --- a/unix/table.c +++ b/unix/table.c @@ -49,9 +49,7 @@ static void applyColor(GtkTreeModel *m, GtkTreeIter *iter, int modelColumn, GtkC static void setEditable(uiTableModel *m, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop) { - uiTableData *data; GValue value = G_VALUE_INIT; - int value; gboolean editable; switch (modelColumn) { @@ -62,8 +60,8 @@ static void setEditable(uiTableModel *m, GtkTreeIter *iter, int modelColumn, Gtk editable = TRUE; break; default: - gtk_tree_model_get_value(m, iter, p->editableColumn, &value); - editable = gtk_value_get_int(&value) != 0; + gtk_tree_model_get_value(GTK_TREE_MODEL(m), iter, modelColumn, &value); + editable = g_value_get_int(&value) != 0; g_value_unset(&value); } g_object_set(r, "editable", editable, NULL); @@ -84,7 +82,7 @@ static void onEdited(uiTableModel *m, int column, const char *pathstr, const uiT path = gtk_tree_path_new_from_string(pathstr); row = gtk_tree_path_get_indices(path)[0]; if (iter != NULL) - gtk_tree_model_convert_path_to_iter(m, path, iter); + gtk_tree_model_get_iter(GTK_TREE_MODEL(m), iter, path); gtk_tree_path_free(path); (*(m->mh->SetCellValue))(m->mh, m, row, column, data); } @@ -107,14 +105,13 @@ static void textColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTree struct textColumnParams *p = (struct textColumnParams *) data; GValue value = G_VALUE_INIT; const gchar *str; - gboolean editable; gtk_tree_model_get_value(m, iter, p->modelColumn, &value); str = g_value_get_string(&value); g_object_set(r, "text", str, NULL); g_value_unset(&value); - setEditable(m, iter, p->editableColumn, r, "editable"); + setEditable(p->m, iter, p->editableColumn, r, "editable"); if (p->params.ColorModelColumn != -1) applyColor(m, iter, p->params.ColorModelColumn, @@ -123,17 +120,17 @@ static void textColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTree applyBackgroundColor(p->t, m, iter, r); } -static void textColumnEdited(GtkCellRendererText *renderer, gchar *path, gchar *newText, gpointer data) +static void textColumnEdited(GtkCellRendererText *r, gchar *path, gchar *newText, gpointer data) { struct textColumnParams *p = (struct textColumnParams *) data; - uiTableData *data; + uiTableData *tdata; GtkTreeIter iter; - data = uiNewTableDataString(newText); - onEdited(p->m, p->textColumn, path, data, &iter); - uiFreeData(data); + tdata = uiNewTableDataString(newText); + onEdited(p->m, p->modelColumn, path, tdata, &iter); + uiFreeTableData(tdata); // and update the column TODO copy comment here - textColumnDataFunc(NULL, r, GTK_TREE_MODEL(p->m), &iter, data); + textColumnDataFunc(NULL, GTK_CELL_RENDERER(r), GTK_TREE_MODEL(p->m), &iter, data); } struct imageColumnParams { @@ -176,7 +173,7 @@ static void checkboxColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, Gtk g_object_set(r, "active", active, NULL); g_value_unset(&value); - setEditable(m, iter, p->editableColumn, r, "activatable"); + setEditable(p->m, iter, p->editableColumn, r, "activatable"); applyBackgroundColor(p->t, m, iter, r); } @@ -186,18 +183,22 @@ static void checkboxColumnToggled(GtkCellRendererToggle *r, gchar *pathstr, gpoi struct checkboxColumnParams *p = (struct checkboxColumnParams *) data; GValue value = G_VALUE_INIT; int v; - uiTableData *data; + uiTableData *tdata; + GtkTreePath *path; GtkTreeIter iter; - gtk_tree_model_get_value(p->m, iter, p->modelColumn, &value); + path = gtk_tree_path_new_from_string(pathstr); + gtk_tree_model_get_iter(GTK_TREE_MODEL(p->m), &iter, path); + gtk_tree_path_free(path); + gtk_tree_model_get_value(GTK_TREE_MODEL(p->m), &iter, p->modelColumn, &value); v = g_value_get_int(&value); g_value_unset(&value); - data = uiNewTableDataInt(!v); - onEdited(p->m, p->modelColumn, path, data, &iter); - uiFreeData(data); + tdata = uiNewTableDataInt(!v); + onEdited(p->m, p->modelColumn, pathstr, tdata, NULL); + uiFreeTableData(tdata); // and update the column TODO copy comment here // TODO avoid fetching the model data twice - checkboxColumnDataFunc(NULL, r, GTK_TREE_MODEL(p->m), &iter, data); + checkboxColumnDataFunc(NULL, GTK_CELL_RENDERER(r), GTK_TREE_MODEL(p->m), &iter, data); } struct progressBarColumnParams { @@ -237,24 +238,23 @@ static void buttonColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTr struct buttonColumnParams *p = (struct buttonColumnParams *) data; GValue value = G_VALUE_INIT; const gchar *str; - gboolean clickable; gtk_tree_model_get_value(m, iter, p->modelColumn, &value); str = g_value_get_string(&value); g_object_set(r, "text", str, NULL); g_value_unset(&value); - setEditable(m, iter, p->clickableColumn, r, "sensitive"); + setEditable(p->m, iter, p->clickableColumn, r, "sensitive"); applyBackgroundColor(p->t, m, iter, r); } // TODO wrong type here -static void buttonColumnClicked(uiprivCellRendererButton *r, gchar *pathstr, gpointer data) +static void buttonColumnClicked(GtkCellRenderer *r, gchar *pathstr, gpointer data) { struct buttonColumnParams *p = (struct buttonColumnParams *) data; - onEdited(p->m, p->modelColumn, path, NULL, NULL); + onEdited(p->m, p->modelColumn, pathstr, NULL, NULL); } static GtkTreeViewColumn *addColumn(uiTable *t, const char *name) @@ -275,8 +275,8 @@ static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, p = uiprivNew(struct textColumnParams); p->t = t; - // TODO get rid of these fields in favor of t->m - p->m = t->m; + // TODO get rid of these fields AND rename t->model in favor of t->m + p->m = t->model; p->modelColumn = textModelColumn; p->editableColumn = textEditableModelColumn; if (params != NULL) @@ -288,7 +288,7 @@ static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, gtk_tree_view_column_pack_start(c, r, TRUE); gtk_tree_view_column_set_cell_data_func(c, r, textColumnDataFunc, p, NULL); g_signal_connect(r, "edited", G_CALLBACK(textColumnEdited), p); - g_ptr_array_add(c->columnParams, p); + g_ptr_array_add(t->columnParams, p); } // TODO rename modelCOlumn and params everywhere @@ -312,7 +312,7 @@ static void addImageColumn(uiTable *t, GtkTreeViewColumn *c, int imageModelColum r = gtk_cell_renderer_pixbuf_new(); gtk_tree_view_column_pack_start(c, r, FALSE); gtk_tree_view_column_set_cell_data_func(c, r, imageColumnDataFunc, p, NULL); - g_ptr_array_add(c->columnParams, p); + g_ptr_array_add(t->columnParams, p); } void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) @@ -332,22 +332,22 @@ void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelCo addTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams); } -static void uiTableAppendCheckboxColumn(uiTable *t, GtkTableViewColumn *c, int checkboxModelColumn, int checkboxEditableModelColumn) +static void addCheckboxColumn(uiTable *t, GtkTreeViewColumn *c, int checkboxModelColumn, int checkboxEditableModelColumn) { struct checkboxColumnParams *p; GtkCellRenderer *r; p = uiprivNew(struct checkboxColumnParams); p->t = t; - p->m = t->m; + p->m = t->model; p->modelColumn = checkboxModelColumn; p->editableColumn = checkboxEditableModelColumn; r = gtk_cell_renderer_toggle_new(); gtk_tree_view_column_pack_start(c, r, FALSE); gtk_tree_view_column_set_cell_data_func(c, r, checkboxColumnDataFunc, p, NULL); - g_signal_connect(r, "toggled", G_CALLBACK(checkboxColumnToggled), part); - g_ptr_array_add(c->columnParams, p); + g_signal_connect(r, "toggled", G_CALLBACK(checkboxColumnToggled), p); + g_ptr_array_add(t->columnParams, p); } void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) @@ -383,7 +383,7 @@ void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressMo r = gtk_cell_renderer_progress_new(); gtk_tree_view_column_pack_start(c, r, TRUE); gtk_tree_view_column_set_cell_data_func(c, r, progressBarColumnDataFunc, p, NULL); - g_ptr_array_add(c->columnParams, p); + g_ptr_array_add(t->columnParams, p); } void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModelColumn, int buttonClickableModelColumn) @@ -396,15 +396,15 @@ void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModel p = uiprivNew(struct buttonColumnParams); p->t = t; - p->m = t->m; - p->modelColumn = buttonModelColumn; + p->m = t->model; + p->modelColumn = buttonTextModelColumn; p->clickableColumn = buttonClickableModelColumn; r = uiprivNewCellRendererButton(); gtk_tree_view_column_pack_start(c, r, TRUE); gtk_tree_view_column_set_cell_data_func(c, r, buttonColumnDataFunc, p, NULL); g_signal_connect(r, "clicked", G_CALLBACK(buttonColumnClicked), p); - g_ptr_array_add(c->columnParams, p); + g_ptr_array_add(t->columnParams, p); } uiUnixControlAllDefaultsExceptDestroy(uiTable) @@ -431,6 +431,7 @@ uiTable *uiNewTable(uiTableModel *model) uiUnixNewControl(uiTable, t); t->model = model; + t->columnParams = g_ptr_array_new(); t->backgroundColumn = -1; t->widget = gtk_scrolled_window_new(NULL, NULL); diff --git a/unix/tablemodel.c b/unix/tablemodel.c index 7ecc05ed..3c6ee6d3 100644 --- a/unix/tablemodel.c +++ b/unix/tablemodel.c @@ -100,22 +100,22 @@ static void uiTableModel_get_value(GtkTreeModel *mm, GtkTreeIter *iter, gint col row = GPOINTER_TO_INT(iter->user_data); data = (*(m->mh->CellValue))(m->mh, m, row, column); switch ((*(m->mh->ColumnType))(m->mh, m, column)) { - case uiTableModelColumnString: + case uiTableDataTypeString: g_value_init(value, G_TYPE_STRING); g_value_set_string(value, uiTableDataString(data)); uiFreeTableData(data); return; - case uiTableModelColumnImage: + case uiTableDataTypeImage: g_value_init(value, G_TYPE_POINTER); g_value_set_pointer(value, uiTableDataImage(data)); uiFreeTableData(data); return; - case uiTableModelColumnInt: + case uiTableDataTypeInt: g_value_init(value, G_TYPE_INT); g_value_set_int(value, uiTableDataInt(data)); uiFreeTableData(data); return; - case uiTableModelColumnColor: + case uiTableDataTypeColor: g_value_init(value, GDK_TYPE_RGBA); if (data == NULL) { g_value_set_boxed(value, NULL); From 7a40bdfb3f073a94bcd513204747ca61d6643d99 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Thu, 7 Jun 2018 22:30:43 -0400 Subject: [PATCH 050/166] Amazingly, it worked the first time! Just a quick logic error here. --- unix/table.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix/table.c b/unix/table.c index fe347e4e..2e85fbd8 100644 --- a/unix/table.c +++ b/unix/table.c @@ -64,7 +64,7 @@ static void setEditable(uiTableModel *m, GtkTreeIter *iter, int modelColumn, Gtk editable = g_value_get_int(&value) != 0; g_value_unset(&value); } - g_object_set(r, "editable", editable, NULL); + g_object_set(r, prop, editable, NULL); } static void applyBackgroundColor(uiTable *t, GtkTreeModel *m, GtkTreeIter *iter, GtkCellRenderer *r) From 0adad7743a211efe8a95a2ee8eb923aa8d198588 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Thu, 7 Jun 2018 22:54:01 -0400 Subject: [PATCH 051/166] Drop mixing of C and C++ class allocations, including placement new. This is the easiest change I can make to the Windows table code for now... --- windows/table.cpp | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index c5dc9ef3..db070f44 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -2,7 +2,7 @@ struct uiTableModel { uiTableModelHandler *mh; - std::vector tables; + std::vector *tables; }; struct uiTableColumn { @@ -16,31 +16,23 @@ struct uiTable { uiWindowsControl c; uiTableModel *model; HWND hwnd; - std::vector columns; + std::vector *columns; }; -void *uiTableModelStrdup(const char *str) -{ - return strdup(str); -} - -void *uiTableModelGiveColor(double r, double g, double b, double a) -{ - return 0; // not implemented -} - uiTableModel *uiNewTableModel(uiTableModelHandler *mh) { uiTableModel *m; - m = new uiTableModel(); + m = uiprivNew(uiTableModel); m->mh = mh; + m->tables = new std::vector; return m; } void uiFreeTableModel(uiTableModel *m) { - delete m; + delete m->tables; + uiprivFree(m); } void uiTableModelRowInserted(uiTableModel *m, int newIndex) @@ -51,21 +43,21 @@ void uiTableModelRowInserted(uiTableModel *m, int newIndex) item.mask = 0; item.iItem = newIndex; item.iSubItem = 0; - for (auto t : m->tables) + for (auto t : *(m->tables)) if (SendMessageW(t->hwnd, LVM_INSERTITEM, 0, (LPARAM) (&item)) == (LRESULT) (-1)) logLastError(L"error calling LVM_INSERTITEM in uiTableModelRowInserted()"); } void uiTableModelRowChanged(uiTableModel *m, int index) { - for (auto t : m->tables) + for (auto t : *(m->tables)) if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) index, 0) == (LRESULT) (-1)) logLastError(L"error calling LVM_UPDATE in uiTableModelRowChanged()"); } void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) { - for (auto t : m->tables) + for (auto t : *(m->tables)) if (SendMessageW(t->hwnd, LVM_DELETEITEM, (WPARAM) oldIndex, 0) == (LRESULT) (-1)) logLastError(L"error calling LVM_DELETEITEM in uiTableModelRowDeleted()"); } @@ -81,7 +73,7 @@ void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) c->modelColumn = modelColumn; // work out appropriate listview index for the column - for (auto candidate : t->columns) { + for (auto candidate : *(t->columns)) { if (candidate == c) break; if (candidate->modelColumn >= 0) @@ -140,7 +132,7 @@ uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) c->t = t; c->modelColumn = -1; // -1 = unassigned // we defer the actual ListView_InsertColumn call until a part is added... - t->columns.push_back(c); + t->columns->push_back(c); return c; } @@ -165,11 +157,11 @@ static void uiTableDestroy(uiControl *c) } } // free the columns - for (auto col : t->columns) { + for (auto col : *(t->columns)) { uiprivFree(col->name); uiprivFree(col); } - t->columns.~vector(); // (created with placement new, so just call dtor directly) + delete t->columns; uiFreeControl(uiControl(t)); } @@ -217,9 +209,9 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) break; row = item->iItem; col = item->iSubItem; - if (col < 0 || col >= (int)t->columns.size()) + if (col < 0 || col >= (int)t->columns->size()) break; - tc = (uiTableColumn *)t->columns[col]; + tc = (uiTableColumn *)t->columns->at(col); mcol = tc->modelColumn; typ = (*mh->ColumnType)(mh, t->model, mcol); @@ -247,7 +239,8 @@ uiTable *uiNewTable(uiTableModel *model) int n; uiWindowsNewControl(uiTable, t); - new(&t->columns) std::vector(); // (initialising in place) + + t->columns = new std::vector; t->model = model; t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, WC_LISTVIEW, L"", From ca2115ca5773036f1326ea95b1f2368d9f2c89f3 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 8 Jun 2018 01:23:11 -0400 Subject: [PATCH 052/166] Removed some old files from wintable that we won't use anymore. --- windows/tablepart.cpp | 95 ------------------------------------------- windows/tableutil.cpp | 32 --------------- 2 files changed, 127 deletions(-) delete mode 100644 windows/tablepart.cpp delete mode 100644 windows/tableutil.cpp diff --git a/windows/tablepart.cpp b/windows/tablepart.cpp deleted file mode 100644 index c31932d7..00000000 --- a/windows/tablepart.cpp +++ /dev/null @@ -1,95 +0,0 @@ -// 30 june 2016 -// TODO includes - -typedef struct tablePartDrawParams tablePartDrawParams; -typedef struct tablePartMinimumSizeParams tableDrawMinimumSizeParams; -typedef struct tablePartEditingParams tablePartEditingParams; - -struct tablePartDrawParams { - HWND hwnd; - HDC hdc; - RECT *r; - bool selected; - bool focused; - bool hovering; - uiTableModel *model; - int row; -}; - -struct tablePartMinimumSizeParams { - HWND hwnd; - HDC hdc; - uiTableModel *model; - int row; -}; - -enum { - partEventDoNothing, - partEventRedraw, - partEventEdit, -}; - -struct tablePartEditingParams { - HWND newHWND; -}; - -enum { - partEditContinue, - partEditDone, -}; - -class tablePart { -public: - // needed so we can delete a tablePart - virtual ~tablePart() {} - - virtual HRESULT Draw(tablePartDrawParams *p) = 0; - virtual HRESULT MinimumSize(tablePartMinimumSizeParams *p, int *width, int *height) = 0; - - // returns a partEvent constant - virtual int MouseMove(int x, int y, RECT *cell) = 0; - virtual int MouseLeave(void) = 0; - virtual int LButtonDown(int x, int y, int count, RECT *cell) = 0; - virtual int LButtonUp(int x, int y, RECT *cell) = 0; - virtual int CaptureBroken(void) = 0; - virtual int KeyDown(void) = 0; - virtual int KeyUp(void) = 0; - - // editing; all optional - virtual int StartEditing(tablePartEditingParams *p) { return editDone; } - virtual int EditChildWM_COMMAND(WORD code, LRESULT *lr) { return editDone; } - virtual void FinishEditing(uiTableModel *model, int row) {} - virtual void CancelEditing(void) {} - - // TODO tooltips - // TODO timers and animations - - // optional methods - virtual void SetTextColorColumn(int col) {} - virtual void SetEditable(bool editable) {} -}; - -class tablePartText : public tablePart { - int textColumn; - int colorColumn; -public: - tablePartText(int tc) - { - this->textColumn = tc; - this->colorColumn = -1; - } - - // TODO figure out vertical alignment - virtual HRESULT Draw(tablePartDrawParams *p) - { - } - - virtual HRESULT MinimumSize(tablePartMinimumSizeParams *p, int *width, int *height) - { - } -}; - -tablePart *newTablePartText(int tc) -{ - return new tablePartText(tc); -} diff --git a/windows/tableutil.cpp b/windows/tableutil.cpp deleted file mode 100644 index e6e5bb14..00000000 --- a/windows/tableutil.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// 1 july 2016 -// TODO includes - -void tableGetClientRect(HWND hwnd, RECT *r) -{ - if (GetClientRect(hwnd, r) == 0) { - r->left = 0; - r->top = 0; - r->right = 0; - r->bottom = 0; - } -} - -void tableGetWindowRect(HWND hwnd, RECT *r) -{ - if (GetWindowRect(hwnd, r) == 0) { - r->left = 0; - r->top = 0; - r->right = 0; - r->bottom = 0; - } -} - -void tableGetTextExtentPoint32W(HDC dc, const WSTR *str, int len, SIZE *s) -{ - if (len == -1) - len = wcslen(str); - if (GetTextExtentPoint32W(dc, str, len, s) == 0) { - s->cx = 0; - s->cy = 0; - } -} From 3aa16e844d36a5b9b13f9fb0a6a95a4acee74f45 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 8 Jun 2018 01:35:23 -0400 Subject: [PATCH 053/166] Adjusted uiTableModel on Windows for owner-data list views. --- windows/table.cpp | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index db070f44..a71e9be9 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -35,19 +35,32 @@ void uiFreeTableModel(uiTableModel *m) uiprivFree(m); } +// TODO document that when this is called, the model must return the new row count when asked void uiTableModelRowInserted(uiTableModel *m, int newIndex) { LVITEMW item; + int newCount; + newCount = (*(m->mh->NumRows))(m->mh, m); ZeroMemory(&item, sizeof (LVITEMW)); item.mask = 0; item.iItem = newIndex; item.iSubItem = 0; - for (auto t : *(m->tables)) + for (auto t : *(m->tables)) { + // actually insert the rows + if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) newCount, LVSICF_NOINVALIDATEALL) == 0) + logLastError(L"error calling LVM_SETITEMCOUNT in uiTableModelRowInserted()"); + // and redraw every row from the new row down to simulate adding it + if (SendMessageW(t->hwnd, LVM_REDRAWITEMS, (WPARAM) newIndex, (LPARAM) (newCount - 1)) == FALSE) + logLastError(L"error calling LVM_REDRAWITEMS in uiTableModelRowInserted()"); + + // update selection state if (SendMessageW(t->hwnd, LVM_INSERTITEM, 0, (LPARAM) (&item)) == (LRESULT) (-1)) - logLastError(L"error calling LVM_INSERTITEM in uiTableModelRowInserted()"); + logLastError(L"error calling LVM_INSERTITEM in uiTableModelRowInserted() to update selection state"); + } } +// TODO compare LVM_UPDATE and LVM_REDRAWITEMS void uiTableModelRowChanged(uiTableModel *m, int index) { for (auto t : *(m->tables)) @@ -55,11 +68,26 @@ void uiTableModelRowChanged(uiTableModel *m, int index) logLastError(L"error calling LVM_UPDATE in uiTableModelRowChanged()"); } +// TODO document that when this is called, the model must return the OLD row count when asked +// TODO for this and the above, see what GTK+ requires and adjust accordingly void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) { - for (auto t : *(m->tables)) + int newCount; + + newCount = (*(m->mh->NumRows))(m->mh, m); + newCount--; + for (auto t : *(m->tables)) { + // update selection state if (SendMessageW(t->hwnd, LVM_DELETEITEM, (WPARAM) oldIndex, 0) == (LRESULT) (-1)) - logLastError(L"error calling LVM_DELETEITEM in uiTableModelRowDeleted()"); + logLastError(L"error calling LVM_DELETEITEM in uiTableModelRowDeleted() to update selection state"); + + // actually delete the rows + if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) newCount, LVSICF_NOINVALIDATEALL) == 0) + logLastError(L"error calling LVM_SETITEMCOUNT in uiTableModelRowDeleted()"); + // and redraw every row from the new nth row down to simulate removing the old nth row + if (SendMessageW(t->hwnd, LVM_REDRAWITEMS, (WPARAM) oldIndex, (LPARAM) (newCount - 1)) == FALSE) + logLastError(L"error calling LVM_REDRAWITEMS in uiTableModelRowDeleted()"); + } } void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) From 516eb312fc325b5e049420b070b98eb19412bbb0 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 8 Jun 2018 21:16:06 -0400 Subject: [PATCH 054/166] Rearranged stuff in table.cpp on Windows and started rewriting it to handle owner-data in the new API. --- windows/table.cpp | 221 ++++++++++++++++++++-------------------------- 1 file changed, 94 insertions(+), 127 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index a71e9be9..529485f9 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -5,18 +5,19 @@ struct uiTableModel { std::vector *tables; }; -struct uiTableColumn { - uiTable *t; - WCHAR *name; - // don't really support parts (but this would part=>column mappings if we did) - int modelColumn; // -1 = none +struct columnParams { + int textModelColumn; + int textEditableColumn; }; struct uiTable { uiWindowsControl c; uiTableModel *model; HWND hwnd; - std::vector *columns; + std::vector *columns; + // MSDN says we have to keep LVN_GETDISPINFO strings we allocate around at least until "two additional LVN_GETDISPINFO messages have been sent". + // we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue + std::queue *dispinfoStrings; }; uiTableModel *uiNewTableModel(uiTableModelHandler *mh) @@ -90,6 +91,93 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) } } +static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) +{ + struct columnParams *p; + uiTableData *data; + WCHAR *wstr; + + wstr = t->dipsinfoString->front(); + t->dispinfoString->pop(); + uiprivFree(wstr); + + p = (*(t->columns))[nm->item.iSubItem]; + // TODO is this condition ever not going to be true for libui? + if ((nm->item.mask & LVIF_TEXT) != 0) + if (p->textModelColumn != -1) { + data = (*(t->model->mh->Value))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); + wstr = toUTF16(uiTableDataString(data)); + uiFreeTableData(data); + nm->item.pszText = wstr; + t->dispinfoString->push(wstr); + } + + return 0; +} + +static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) +{ + uiTable *t = uiTable(c); + uiTableModelHandler *mh = t->model->mh; + + switch (nmhdr->code) { + case LVN_GETDISPINFO: + *lResult = onLVN_GETDISPINFO(t, (NMLVDISPINFOW *) nmhdr); + return TRUE; + } + return FALSE; +} + +$ TODO =================== + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + uiTableModel *model = t->model; + std::vector::iterator it; + + uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); + uiWindowsEnsureDestroyWindow(t->hwnd); + // detach table from model + for (it = model->tables.begin(); it != model->tables.end(); it++) { + if (*it == t) { + model->tables.erase(it); + break; + } + } + // free the columns + for (auto col : *(t->columns)) { + uiprivFree(col->name); + uiprivFree(col); + } + delete t->columns; + uiFreeControl(uiControl(t)); +} + +uiWindowsControlAllDefaultsExceptDestroy(uiTable) + +// suggested listview sizing from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing: +// "columns widths that avoid truncated data x an integral number of items" +// Don't think that'll cut it when some cells have overlong data (eg +// stupidly long URLs). So for now, just hardcode a minimum. +// TODO: Investigate using LVM_GETHEADER/HDM_LAYOUT here... +#define tableMinWidth 107 /* in line with other controls */ +#define tableMinHeight (14*3) /* header + 2 lines (roughly) */ + +static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height) +{ + uiTable *t = uiTable(c); + uiWindowsSizing sizing; + int x, y; + + x = tableMinWidth; + y = tableMinHeight; + uiWindowsGetSizing(t->hwnd, &sizing); + uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); + *width = x; + *height = y; +} + void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) { uiTable *t = c->t; @@ -117,40 +205,11 @@ void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) logLastError(L"error calling LVM_INSERTCOLUMN in uiTableColumnPartSetTextPart()"); } -void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) -{ - // not implemented -} - -void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand) -{ - // not implemented -} - -void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) -{ - // not implemented -} - -void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand) -{ - // not implemented -} - void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) { // TODO } -void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) -{ - // not implemented -} - -// uiTable implementation - -uiWindowsControlAllDefaultsExceptDestroy(uiTable) - uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) { uiTableColumn *c; @@ -169,98 +228,6 @@ void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) // not implemented } -static void uiTableDestroy(uiControl *c) -{ - uiTable *t = uiTable(c); - uiTableModel *model = t->model; - std::vector::iterator it; - - uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); - uiWindowsEnsureDestroyWindow(t->hwnd); - // detach table from model - for (it = model->tables.begin(); it != model->tables.end(); it++) { - if (*it == t) { - model->tables.erase(it); - break; - } - } - // free the columns - for (auto col : *(t->columns)) { - uiprivFree(col->name); - uiprivFree(col); - } - delete t->columns; - uiFreeControl(uiControl(t)); -} - -// suggested listview sizing from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing: -// "columns widths that avoid truncated data x an integral number of items" -// Don't think that'll cut it when some cells have overlong data (eg -// stupidly long URLs). So for now, just hardcode a minimum. -// TODO: Investigate using LVM_GETHEADER/HDM_LAYOUT here... -#define tableMinWidth 107 /* in line with other controls */ -#define tableMinHeight (14*3) /* header + 2 lines (roughly) */ - -static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height) -{ - uiTable *t = uiTable(c); - uiWindowsSizing sizing; - int x, y; - - x = tableMinWidth; - y = tableMinHeight; - uiWindowsGetSizing(t->hwnd, &sizing); - uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); - *width = x; - *height = y; -} - -static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) -{ - uiTable *t = uiTable(c); - uiTableModelHandler *mh = t->model->mh; - BOOL ret = FALSE; - - switch (nmhdr->code) { - case LVN_GETDISPINFO: - { - NMLVDISPINFOW *di; - LVITEMW *item; - int row, col; - uiTableColumn *tc; - int mcol; - uiTableModelColumnType typ; - - di = (NMLVDISPINFOW *)nmhdr; - item = &(di->item); - if (!(item->mask & LVIF_TEXT)) - break; - row = item->iItem; - col = item->iSubItem; - if (col < 0 || col >= (int)t->columns->size()) - break; - tc = (uiTableColumn *)t->columns->at(col); - mcol = tc->modelColumn; - typ = (*mh->ColumnType)(mh, t->model, mcol); - - if (typ == uiTableModelColumnString) { - void* data; - int n; - - data = (*(mh->CellValue))(mh, t->model, row, mcol); - n = MultiByteToWideChar(CP_UTF8, 0, (const char *)data, -1, item->pszText, item->cchTextMax); - // make sure clipped strings are nul-terminated - if (n >= item->cchTextMax) - item->pszText[item->cchTextMax-1] = L'\0'; - } else - item->pszText[0] = L'\0'; - break; - } - } - *lResult = 0; - return ret; -} - uiTable *uiNewTable(uiTableModel *model) { uiTable *t; From 41d63bd0cf7b8cc9b53dd2442cf04a9bf0236284 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 8 Jun 2018 21:44:55 -0400 Subject: [PATCH 055/166] And finished rewriting table.cpp. That wasn't too bad for just text columns. Let's hope it works. --- windows/table.cpp | 153 +++++++++++++++++++++++++++++++++------------- 1 file changed, 110 insertions(+), 43 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 529485f9..63288773 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -5,9 +5,24 @@ struct uiTableModel { std::vector *tables; }; +static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { + .ColorModelColumn = -1, +}; + struct columnParams { int textModelColumn; int textEditableColumn; + uiTableTextColumnOptionalParams textParams; + + int imageModelColumn; + + int checkboxModelColumn; + int checkboxEditableModelColumn; + + int progressBarModelColumn; + + int buttonModelColumn; + int buttonClickableModelColumn; }; struct uiTable { @@ -15,6 +30,7 @@ struct uiTable { uiTableModel *model; HWND hwnd; std::vector *columns; + WPARAM nColumns; // MSDN says we have to keep LVN_GETDISPINFO strings we allocate around at least until "two additional LVN_GETDISPINFO messages have been sent". // we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue std::queue *dispinfoStrings; @@ -98,8 +114,9 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) WCHAR *wstr; wstr = t->dipsinfoString->front(); + if (wstr != NULL) + uiprivFree(wstr); t->dispinfoString->pop(); - uiprivFree(wstr); p = (*(t->columns))[nm->item.iSubItem]; // TODO is this condition ever not going to be true for libui? @@ -128,28 +145,33 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) return FALSE; } -$ TODO =================== - static void uiTableDestroy(uiControl *c) { uiTable *t = uiTable(c); uiTableModel *model = t->model; std::vector::iterator it; + WCHAR *wstr; uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); uiWindowsEnsureDestroyWindow(t->hwnd); // detach table from model for (it = model->tables.begin(); it != model->tables.end(); it++) { if (*it == t) { - model->tables.erase(it); + model->tables->erase(it); break; } } - // free the columns - for (auto col : *(t->columns)) { - uiprivFree(col->name); - uiprivFree(col); + // empty the string queue + while (t->dispinfoStrings->size() != 0) { + wstr = t->dispinfoStrings->front(); + if (wstr != NULL) + uiprivFree(wstr); + t->dispinfoStrings->pop(); } + delete t->dispinfoStrings; + // free the columns + for (auto col : *(t->columns)) + uiprivFree(col); delete t->columns; uiFreeControl(uiControl(t)); } @@ -162,7 +184,7 @@ uiWindowsControlAllDefaultsExceptDestroy(uiTable) // stupidly long URLs). So for now, just hardcode a minimum. // TODO: Investigate using LVM_GETHEADER/HDM_LAYOUT here... #define tableMinWidth 107 /* in line with other controls */ -#define tableMinHeight (14*3) /* header + 2 lines (roughly) */ +#define tableMinHeight (14 * 3) /* header + 2 lines (roughly) */ static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height) { @@ -176,51 +198,97 @@ static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height) uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); *width = x; *height = y; -} +}' -void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) +$ TODO =================== + +static struct columnParams *appendColumn(uiTable *t, const char *name, int colfmt) { - uiTable *t = c->t; - int lvIndex = 0; + WCHAR *wstr; LVCOLUMNW lvc; - - if (c->modelColumn >= 0) - return; // multiple parts not implemented - c->modelColumn = modelColumn; - - // work out appropriate listview index for the column - for (auto candidate : *(t->columns)) { - if (candidate == c) - break; - if (candidate->modelColumn >= 0) - lvIndex++; - } + struct columnParams *p; ZeroMemory(&lvc, sizeof (LVCOLUMNW)); - lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; /* | LVCF_SUBITEM; */ - lvc.fmt = LVCFMT_LEFT; - lvc.cx = 120; // TODO - lvc.pszText = c->name; - if (SendMessageW(c->t->hwnd, LVM_INSERTCOLUMN, (WPARAM) lvIndex, (LPARAM) (&lvc)) == (LRESULT) (-1)) - logLastError(L"error calling LVM_INSERTCOLUMN in uiTableColumnPartSetTextPart()"); + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; + lvc.fmt = colfmt; + lvc.cx = 120; // TODO + wstr = toUTF16(name); + lvc.pszText = wstr; + if (SendMessageW(t->hwnd, LVM_INSERTCOLUMNW, t->nColumns, (LPARAM) (&lvc)) == (LRESULT) (-1)) + logLastError(L"error calling LVM_INSERTCOLUMNW in appendColumn()"); + uiprivFree(wstr); + t->nColumns++; + + p = uiprivNew(struct columnParams); + p->textModelColumn = -1; + p->imageModelColumn = -1; + p->checkboxModelColumn = -1; + p->progressBarModelColumn = -1; + p->buttonModelColumn = -1; + t->columns->push_back(p); + return p; } -void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) +void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *params) +{ + struct columnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->textModelColumn = textModelColumn; + p->textEditableColumn = textEditableModelColumn; + if (params != NULL) + p->textParams = *params; + else + p->textParams = defaultTextColumnOptionalParams; +} + +void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) { // TODO } -uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) +void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) { - uiTableColumn *c; - - c = uiprivNew(uiTableColumn); - c->name = toUTF16(name); - c->t = t; - c->modelColumn = -1; // -1 = unassigned - // we defer the actual ListView_InsertColumn call until a part is added... - t->columns->push_back(c); - return c; + struct columnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT | LVCFMT_IMAGE); + p->textModelColumn = textModelColumn; + p->textEditableColumn = textEditableModelColumn; + if (params != NULL) + p->textParams = *params; + else + p->textParams = defaultTextColumnOptionalParams; + p->imageModelColumn = imageModelColumn; +} + +void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) +{ + // TODO +} + +void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + struct columnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->textModelColumn = textModelColumn; + p->textEditableColumn = textEditableModelColumn; + if (params != NULL) + p->textParams = *params; + else + p->textParams = defaultTextColumnOptionalParams; + p->checkboxModelColumn = checkboxModelColumn; + p->checkboxEditableColumn = checkboxEditableModelColumn; +} + +void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) +{ + // TODO +} + +void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModelColumn, int buttonClickableModelColumn) +{ + // TODO } void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) @@ -254,4 +322,3 @@ uiTable *uiNewTable(uiTableModel *model) logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()"); return t; } - From 71a310909d59e9e10487256609759717240666e6 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 8 Jun 2018 21:45:30 -0400 Subject: [PATCH 056/166] Oops --- windows/table.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 63288773..6c4b198f 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -198,9 +198,7 @@ static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height) uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); *width = x; *height = y; -}' - -$ TODO =================== +} static struct columnParams *appendColumn(uiTable *t, const char *name, int colfmt) { From 69e91a96789d55797336c5ace2cd86da703df767 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 8 Jun 2018 22:05:11 -0400 Subject: [PATCH 057/166] Fixed build errors and some logic errors. Let's hope it works! --- windows/table.cpp | 36 +++++++++++++++++++++--------------- windows/winapi.hpp | 1 + 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 6c4b198f..79c0bdb0 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -6,7 +6,7 @@ struct uiTableModel { }; static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { - .ColorModelColumn = -1, + /*TODO.ColorModelColumn = */-1, }; struct columnParams { @@ -17,7 +17,7 @@ struct columnParams { int imageModelColumn; int checkboxModelColumn; - int checkboxEditableModelColumn; + int checkboxEditableColumn; int progressBarModelColumn; @@ -29,7 +29,7 @@ struct uiTable { uiWindowsControl c; uiTableModel *model; HWND hwnd; - std::vector *columns; + std::vector *columns; WPARAM nColumns; // MSDN says we have to keep LVN_GETDISPINFO strings we allocate around at least until "two additional LVN_GETDISPINFO messages have been sent". // we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue @@ -113,20 +113,20 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) uiTableData *data; WCHAR *wstr; - wstr = t->dipsinfoString->front(); + wstr = t->dispinfoStrings->front(); if (wstr != NULL) uiprivFree(wstr); - t->dispinfoString->pop(); + t->dispinfoStrings->pop(); p = (*(t->columns))[nm->item.iSubItem]; // TODO is this condition ever not going to be true for libui? if ((nm->item.mask & LVIF_TEXT) != 0) if (p->textModelColumn != -1) { - data = (*(t->model->mh->Value))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); wstr = toUTF16(uiTableDataString(data)); uiFreeTableData(data); nm->item.pszText = wstr; - t->dispinfoString->push(wstr); + t->dispinfoStrings->push(wstr); } return 0; @@ -135,7 +135,6 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) { uiTable *t = uiTable(c); - uiTableModelHandler *mh = t->model->mh; switch (nmhdr->code) { case LVN_GETDISPINFO: @@ -155,7 +154,7 @@ static void uiTableDestroy(uiControl *c) uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); uiWindowsEnsureDestroyWindow(t->hwnd); // detach table from model - for (it = model->tables.begin(); it != model->tables.end(); it++) { + for (it = model->tables->begin(); it != model->tables->end(); it++) { if (*it == t) { model->tables->erase(it); break; @@ -252,8 +251,8 @@ void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelCo p = appendColumn(t, name, LVCFMT_LEFT | LVCFMT_IMAGE); p->textModelColumn = textModelColumn; p->textEditableColumn = textEditableModelColumn; - if (params != NULL) - p->textParams = *params; + if (textParams != NULL) + p->textParams = *textParams; else p->textParams = defaultTextColumnOptionalParams; p->imageModelColumn = imageModelColumn; @@ -271,8 +270,8 @@ void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxM p = appendColumn(t, name, LVCFMT_LEFT); p->textModelColumn = textModelColumn; p->textEditableColumn = textEditableModelColumn; - if (params != NULL) - p->textParams = *params; + if (textParams != NULL) + p->textParams = *textParams; else p->textParams = defaultTextColumnOptionalParams; p->checkboxModelColumn = checkboxModelColumn; @@ -301,14 +300,14 @@ uiTable *uiNewTable(uiTableModel *model) uiWindowsNewControl(uiTable, t); - t->columns = new std::vector; + t->columns = new std::vector; t->model = model; t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, WC_LISTVIEW, L"", LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL | WS_TABSTOP | WS_HSCROLL | WS_VSCROLL, hInstance, NULL, TRUE); - model->tables.push_back(t); + model->tables->push_back(t); uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t)); // TODO: try LVS_EX_AUTOSIZECOLUMNS @@ -318,5 +317,12 @@ uiTable *uiNewTable(uiTableModel *model) n = (*(model->mh->NumRows))(model->mh, model); if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0) logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()"); + + t->dispinfoStrings = new std::queue; + // this encodes the idea that two LVN_GETDISPINFOs must complete before we can free a string: the first real one is for the fourth call to free + t->dispinfoStrings->push(NULL); + t->dispinfoStrings->push(NULL); + t->dispinfoStrings->push(NULL); + return t; } diff --git a/windows/winapi.hpp b/windows/winapi.hpp index 4f24f607..ef595bba 100644 --- a/windows/winapi.hpp +++ b/windows/winapi.hpp @@ -57,4 +57,5 @@ #include #include #include +#include #endif From 06a8044c2c35639d1e39fd355b424880e404ff6b Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 8 Jun 2018 22:11:46 -0400 Subject: [PATCH 058/166] Oops, forgot to keep the string queue full. It works! Now to switch to custom draw. --- windows/table.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/windows/table.cpp b/windows/table.cpp index 79c0bdb0..e60ad3c5 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -112,6 +112,7 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) struct columnParams *p; uiTableData *data; WCHAR *wstr; + bool queueUpdated = false; wstr = t->dispinfoStrings->front(); if (wstr != NULL) @@ -127,8 +128,12 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) uiFreeTableData(data); nm->item.pszText = wstr; t->dispinfoStrings->push(wstr); + queueUpdated = true; } + // we don't want to pop from an empty queue, so if nothing updated the queue (no info was filled in above), just push NULL + if (!queueUpdated) + t->dispinfoStrings->push(NULL); return 0; } From c6c4dbd58083de07602ad91840af5765e0738c85 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 9 Jun 2018 10:40:42 -0400 Subject: [PATCH 059/166] Started handling colors in the Windows table code. --- windows/table.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index e60ad3c5..c311e7b3 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -34,6 +34,7 @@ struct uiTable { // MSDN says we have to keep LVN_GETDISPINFO strings we allocate around at least until "two additional LVN_GETDISPINFO messages have been sent". // we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue std::queue *dispinfoStrings; + int backgroundColumn; }; uiTableModel *uiNewTableModel(uiTableModelHandler *mh) @@ -109,7 +110,7 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) { - struct columnParams *p; + static struct columnParams *p; uiTableData *data; WCHAR *wstr; bool queueUpdated = false; @@ -137,6 +138,67 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) return 0; } +static COLORREF blend(COLORREF base, double r, double g, double b, double a) +{ + double br, bg, bb; + + // TODO find a better fix than this; is listview already alphablending? + if (base != 0xFF000000) { + br = ((double) GetRValue(base)) / 255.0; + bg = ((double) GetGValue(base)) / 255.0; + bb = ((double) GetBValue(base)) / 255.0; + } else { + // TODO find the right color here + br = 1.0; + bg = 1.0; + bb = 1.0; + } + + br = (r * a) + (br * (1.0 - a)); + bg = (g * a) + (bg * (1.0 - a)); + bb = (b * a) + (bb * (1.0 - a)); + return RGB((BYTE) (br * 255), + (BYTE) (bg * 255), + (BYTE) (bb * 255)); +} + +static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) +{ + struct columnParams *p; + uiTableData *data; + double r, g, b, a; + LRESULT ret; + + switch (nm->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW; + case CDDS_ITEMPREPAINT: + if (t->backgroundColumn != -1) { + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, t->backgroundColumn); + if (data != NULL) { + uiTableDataColor(data, &r, &g, &b, &a); + uiFreeTableData(data); + nm->clrTextBk = blend(nm->clrTextBk, r, g, b, a); + } + } + return CDRF_NEWFONT | CDRF_NOTIFYSUBITEMDRAW; + case CDDS_SUBITEM | CDDS_ITEMPREPAINT: + p = (*(t->columns))[nm->iSubItem]; + if (p->textParams.ColorModelColumn != -1) { + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, p->textParams.ColorModelColumn); + if (data != NULL) { + uiTableDataColor(data, &r, &g, &b, &a); + uiFreeTableData(data); + // TODO the fallback here isn't correct + nm->clrText = blend(nm->clrText, r, g, b, a); + } + } + // TODO this keeps the color for the rest of the row + return CDRF_NEWFONT; + } + return CDRF_DODEFAULT; +} + static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) { uiTable *t = uiTable(c); @@ -145,6 +207,9 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) case LVN_GETDISPINFO: *lResult = onLVN_GETDISPINFO(t, (NMLVDISPINFOW *) nmhdr); return TRUE; + case NM_CUSTOMDRAW: + *lResult = onNM_CUSTOMDRAW(t, (NMLVCUSTOMDRAW *) nmhdr); + return TRUE; } return FALSE; } @@ -295,7 +360,9 @@ void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModel void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) { - // not implemented + // TODO make the names consistent + t->backgroundColumn = modelColumn; + // TODO redraw? } uiTable *uiNewTable(uiTableModel *model) @@ -329,5 +396,7 @@ uiTable *uiNewTable(uiTableModel *model) t->dispinfoStrings->push(NULL); t->dispinfoStrings->push(NULL); + t->backgroundColumn = -1; + return t; } From 1c9f9627c0bab2fc11531f15c477e0f1d6809c3a Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 9 Jun 2018 12:15:50 -0400 Subject: [PATCH 060/166] Started image support for Windows tables. --- windows/table.cpp | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index c311e7b3..e0fafc51 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -35,6 +35,9 @@ struct uiTable { // we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue std::queue *dispinfoStrings; int backgroundColumn; + + // custom draw state + COLORREF clrItemText; }; uiTableModel *uiNewTableModel(uiTableModelHandler *mh) @@ -132,6 +135,10 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) queueUpdated = true; } + if ((nm->item.mask & LVIF_IMAGE) != 0) + if (p->imageModelColumn != -1) + nm->item.iImage = 1; + // we don't want to pop from an empty queue, so if nothing updated the queue (no info was filled in above), just push NULL if (!queueUpdated) t->dispinfoStrings->push(NULL); @@ -142,17 +149,14 @@ static COLORREF blend(COLORREF base, double r, double g, double b, double a) { double br, bg, bb; - // TODO find a better fix than this; is listview already alphablending? - if (base != 0xFF000000) { - br = ((double) GetRValue(base)) / 255.0; - bg = ((double) GetGValue(base)) / 255.0; - bb = ((double) GetBValue(base)) / 255.0; - } else { - // TODO find the right color here - br = 1.0; - bg = 1.0; - bb = 1.0; - } + // TODO find a better fix than this + // TODO s listview already alphablending? + // TODO find the right color here + if (base == 0xFF000000) + base = GetSysColor(COLOR_WINDOW); + br = ((double) GetRValue(base)) / 255.0; + bg = ((double) GetGValue(base)) / 255.0; + bb = ((double) GetBValue(base)) / 255.0; br = (r * a) + (br * (1.0 - a)); bg = (g * a) + (bg * (1.0 - a)); @@ -181,19 +185,20 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) nm->clrTextBk = blend(nm->clrTextBk, r, g, b, a); } } + t->clrItemText = nm->clrText; return CDRF_NEWFONT | CDRF_NOTIFYSUBITEMDRAW; case CDDS_SUBITEM | CDDS_ITEMPREPAINT: p = (*(t->columns))[nm->iSubItem]; + // we need this as previous subitems will persist their colors + nm->clrText = t->clrItemText; if (p->textParams.ColorModelColumn != -1) { data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, p->textParams.ColorModelColumn); if (data != NULL) { uiTableDataColor(data, &r, &g, &b, &a); uiFreeTableData(data); - // TODO the fallback here isn't correct - nm->clrText = blend(nm->clrText, r, g, b, a); + nm->clrText = blend(nm->clrTextBk, r, g, b, a); } } - // TODO this keeps the color for the rest of the row return CDRF_NEWFONT; } return CDRF_DODEFAULT; @@ -318,7 +323,7 @@ void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelCo { struct columnParams *p; - p = appendColumn(t, name, LVCFMT_LEFT | LVCFMT_IMAGE); + p = appendColumn(t, name, LVCFMT_LEFT); p->textModelColumn = textModelColumn; p->textEditableColumn = textEditableModelColumn; if (textParams != NULL) @@ -384,8 +389,8 @@ uiTable *uiNewTable(uiTableModel *model) // TODO: try LVS_EX_AUTOSIZECOLUMNS SendMessageW(t->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, - (WPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP), - (LPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP)); + (WPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_SUBITEMIMAGES), + (LPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_SUBITEMIMAGES)); n = (*(model->mh->NumRows))(model->mh, model); if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0) logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()"); From ff6468565587ceb88905d453e657a09c13a96cf9 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 9 Jun 2018 13:57:43 -0400 Subject: [PATCH 061/166] More work. List View is starting to show its dumbness again. --- windows/table.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index e0fafc51..9ef253bb 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -37,6 +37,8 @@ struct uiTable { int backgroundColumn; // custom draw state + HIMAGELIST dummyLarge; + HIMAGELIST dummySmall; COLORREF clrItemText; }; @@ -137,7 +139,7 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) if ((nm->item.mask & LVIF_IMAGE) != 0) if (p->imageModelColumn != -1) - nm->item.iImage = 1; + nm->item.iImage = 0; // we don't want to pop from an empty queue, so if nothing updated the queue (no info was filled in above), just push NULL if (!queueUpdated) @@ -152,7 +154,7 @@ static COLORREF blend(COLORREF base, double r, double g, double b, double a) // TODO find a better fix than this // TODO s listview already alphablending? // TODO find the right color here - if (base == 0xFF000000) + if (base == CLR_DEFAULT) base = GetSysColor(COLOR_WINDOW); br = ((double) GetRValue(base)) / 255.0; bg = ((double) GetGValue(base)) / 255.0; @@ -199,6 +201,7 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) nm->clrText = blend(nm->clrTextBk, r, g, b, a); } } + // TODO draw background on image columns if needed return CDRF_NEWFONT; } return CDRF_DODEFAULT; @@ -247,6 +250,7 @@ static void uiTableDestroy(uiControl *c) for (auto col : *(t->columns)) uiprivFree(col); delete t->columns; + // t->dummyLarge and t->dummySmall will be automatically destroyed uiFreeControl(uiControl(t)); } @@ -403,5 +407,23 @@ uiTable *uiNewTable(uiTableModel *model) t->backgroundColumn = -1; + // TODO update these when the DPI changes + t->dummyLarge = ImageList_Create( + GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), + ILC_COLOR32, + 1, 0); + if (t->dummyLarge == NULL) + logLastError(L"error calling ImageList_Create() for dummy large image list in uiNewTable()"); + // TODO will this return NULL here because it's an initial state? + SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_NORMAL, (LPARAM) (t->dummyLarge)); + t->dummySmall = ImageList_Create( + GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), + ILC_COLOR32, + 1, 0); + if (t->dummySmall == NULL) + logLastError(L"error calling ImageList_Create() for dummy small image list in uiNewTable()"); + // TODO will this return NULL here because it's an initial state? + SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->dummySmall)); + return t; } From 3aee505f4e38e37ff25d69bd5c1be5d89caf053d Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 9 Jun 2018 14:32:04 -0400 Subject: [PATCH 062/166] Weirdness workarounds. --- windows/table.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/windows/table.cpp b/windows/table.cpp index 9ef253bb..37b5b700 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -141,6 +141,14 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) if (p->imageModelColumn != -1) nm->item.iImage = 0; + // having an image list always leaves space for an image on the main item :| + // other places on the internet imply that you should be able to do this but that it shouldn't work + // but it works perfectly (and pixel-perfectly too) for me, so... + if (nm->item.iSubItem == 0 && p->imageModelColumn == -1) { + nm->item.mask |= LVIF_INDENT; + nm->item.iIndent = -1; + } + // we don't want to pop from an empty queue, so if nothing updated the queue (no info was filled in above), just push NULL if (!queueUpdated) t->dispinfoStrings->push(NULL); From a858300f257959793df085cd80569f466635a336 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 9 Jun 2018 19:24:36 -0400 Subject: [PATCH 063/166] Filled in image.cpp. Also switched on WIC since we'll need it for uiArea, though uiTable will still need classic GDI. Now let's integrate this into uiTable. --- windows/CMakeLists.txt | 2 +- windows/image.cpp | 137 +++++++++++++++++++++++++++++++++++-- windows/init.cpp | 5 ++ windows/table.cpp | 2 + windows/uipriv_windows.hpp | 7 ++ windows/winapi.hpp | 1 + 6 files changed, 148 insertions(+), 6 deletions(-) diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index b871b529..31ba2c93 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -78,7 +78,7 @@ set(_LIBUI_INCLUDEDIRS _LIBUI_INCLUDEDIRS PARENT_SCOPE) # TODO prune this list set(_LIBUI_LIBS - user32 kernel32 gdi32 comctl32 uxtheme msimg32 comdlg32 d2d1 dwrite ole32 oleaut32 oleacc uuid + user32 kernel32 gdi32 comctl32 uxtheme msimg32 comdlg32 d2d1 dwrite ole32 oleaut32 oleacc uuid windowscodecs PARENT_SCOPE) if(NOT MSVC) diff --git a/windows/image.cpp b/windows/image.cpp index e3b78475..a2fb2853 100644 --- a/windows/image.cpp +++ b/windows/image.cpp @@ -1,12 +1,23 @@ #include "uipriv_windows.hpp" -// stubbed out windows image list implementation. -// Required for uiTable control, but windows implemenation -// doesn't currently have image support. + +IWICImagingFactory *uiprivWICFactory = NULL; + +HRESULT uiprivInitImage(void) +{ + return CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, + IID_IWICImagingFactory, (void **) (&uiprivWICFactory)); +} + +void uiprivUninitImage(void) +{ + uiprivWICFactory->Release(); + uiprivWICFactory = NULL; +} struct uiImage { double width; double height; - // HIMAGELIST images; + std::vector *bitmaps; }; uiImage *uiNewImage(double width, double height) @@ -16,16 +27,132 @@ uiImage *uiNewImage(double width, double height) i = uiprivNew(uiImage); i->width = width; i->height = height; + i->bitmaps = new std::vector; return i; } void uiFreeImage(uiImage *i) { + for (IWICBitmap *b : *(i->bitmaps)) + b->Release(); + delete i->bitmaps; uiprivFree(i); } void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride) { - // not implemented + IWICBitmap *b; + HRESULT hr; + + hr = uiprivWICFactory->CreateBitmapFromMemory(pixelWidth, pixelHeight, + GUID_WICPixelFormat32bppRGBA, pixelStride, + pixelStride * pixelHeight, (BYTE *) pixels, + &b); + if (hr != S_OK) + logHRESULT(L"error calling CreateBitmapFromMemory() in uiImageAppend()", hr); + i->bitmaps->push_back(b); } +struct matcher { + IWICBitmap *best; + int distX; + int distY; + int targetX; + int targetY; + bool foundLarger; +}; + +// TODO is this the right algorithm? +static void match(IWICBitmap *b, struct matcher *m) +{ + UINT ux, uy; + int x, y; + int x2, y2; + HRESULT hr; + + hr = b->GetSize(&ux, &uy); + if (hr != S_OK) + logHRESULT(L"error calling GetSize() in match()", hr); + x = ux; + y = uy; + if (m->best == NULL) + goto writeMatch; + + if (x < m->targetX && y < m->targetY) + if (m->foundLarger) + // always prefer larger ones + return; + if (x >= m->targetX && y >= m->targetY && !m->foundLarger) + // we set foundLarger below + goto writeMatch; + +// TODO +#define abs(x) ((x) < 0 ? -(x) : (x)) + x2 = abs(m->targetX - x); + y2 = abs(m->targetY - y); + if (x2 < m->distX && y2 < m->distY) + goto writeMatch; + + // TODO weight one dimension? threshhold? + return; + +writeMatch: + // must set this here too; otherwise the first image will never have ths set + if (x >= m->targetX && y >= m->targetY && !m->foundLarger) + m->foundLarger = true; + m->best = b; + m->distX = abs(m->targetX - x); + m->distY = abs(m->targetY - y); +} + +IWICBitmap *uiprivImageAppropriateForDC(uiImage *i, HDC dc) +{ + struct matcher m; + + m.best = NULL; + m.distX = INT_MAX; + m.distY = INT_MAX; + m.targetX = i->width * GetDeviceCaps(dc, LOGPIXELSX); + m.targetY = i->height * GetDeviceCaps(dc, LOGPIXELSY); + m.foundLarger = false; + for (IWICBitmap *b : *(i->bitmaps)) + match(b, &m); + return m.best; +} + +// TODO see if we can really pass NULL to CreateDIBSection()'s HDC parameter, and if so, use HBITMAPs before WIC maybe? +HBITMAP uiprivWICToGDI(IWICBitmap *b, HDC dc) +{ + BITMAPINFO bmi; + UINT width, height; + HBITMAP hb; + VOID *bits; + BITMAP bmp; + HRESULT hr; + + ZeroMemory(&bmi, sizeof (BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + hr = b->GetSize(&width, &height); + if (hr != S_OK) + logHRESULT(L"error calling GetSize() in uiprivWICToGDI()", hr); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -((int) height); + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + hb = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, + &bits, NULL, 0); + if (hb == NULL) + logLastError(L"error calling CreateDIBSection() in uiprivWICToGDI()"); + + // now we need to figure out the stride of the image data GDI gave us + // TODO find out if CreateDIBSection fills that in bmi for us + if (GetObject(hb, sizeof (BITMAP), &bmp) == 0) + logLastError(L"error calling GetObject() in uiprivWICToGDI()"); + hr = b->CopyPixels(NULL, bmp.bmWidthBytes, + bmp.bmWidthBytes * bmp.bmHeight, (BYTE *) bits); + if (hr != S_OK) + logHRESULT(L"error calling CopyPixels() in uiprivWICToGDI()", hr); + + return hb; +} diff --git a/windows/init.cpp b/windows/init.cpp index 24831143..2fb52c0f 100644 --- a/windows/init.cpp +++ b/windows/init.cpp @@ -131,11 +131,16 @@ const char *uiInit(uiInitOptions *o) if (registerD2DScratchClass(hDefaultIcon, hDefaultCursor) == 0) return ieLastErr("initializing D2D scratch window class"); + hr = uiprivInitImage(); + if (hr != S_OK) + return ieHRESULT("initializing WIC", hr); + return NULL; } void uiUninit(void) { + uiprivUninitImage(); uninitMenus(); unregisterD2DScratchClass(); unregisterMessageFilter(); diff --git a/windows/table.cpp b/windows/table.cpp index 37b5b700..e2fa606c 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -416,6 +416,8 @@ uiTable *uiNewTable(uiTableModel *model) t->backgroundColumn = -1; // TODO update these when the DPI changes + // TODO handle errors + // TODO try adding a real transparent image t->dummyLarge = ImageList_Create( GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), ILC_COLOR32, diff --git a/windows/uipriv_windows.hpp b/windows/uipriv_windows.hpp index 77982322..aab50aa7 100644 --- a/windows/uipriv_windows.hpp +++ b/windows/uipriv_windows.hpp @@ -163,3 +163,10 @@ extern D2D1_SIZE_F realGetSize(ID2D1RenderTarget *rt); // draw.cpp extern ID2D1DCRenderTarget *makeHDCRenderTarget(HDC dc, RECT *r); + +// image.cpp +extern IWICImagingFactory *uiprivWICFactory; +extern HRESULT uiprivInitImage(void); +extern void uiprivUninitImage(void); +extern IWICBitmap *uiprivImageAppropriateForDC(uiImage *i, HDC dc); +extern HBITMAP uiprivWICToGDI(IWICBitmap *b, HDC dc); diff --git a/windows/winapi.hpp b/windows/winapi.hpp index ef595bba..f1306db7 100644 --- a/windows/winapi.hpp +++ b/windows/winapi.hpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include From b43fb4b247c03379bb3b84eb67fc7c86bd944450 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 9 Jun 2018 20:35:05 -0400 Subject: [PATCH 064/166] And added images to uiTable. Right now it's loading the 32x32 images into the table instead of the 16x16 ones, but hey, it works! --- windows/table.cpp | 60 ++++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index e2fa606c..47cedb63 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -31,14 +31,17 @@ struct uiTable { HWND hwnd; std::vector *columns; WPARAM nColumns; + int backgroundColumn; + + // owner data state // MSDN says we have to keep LVN_GETDISPINFO strings we allocate around at least until "two additional LVN_GETDISPINFO messages have been sent". // we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue std::queue *dispinfoStrings; - int backgroundColumn; + // likewise here, though the docs aren't as clear + HIMAGELIST smallImages; + int smallIndex; // custom draw state - HIMAGELIST dummyLarge; - HIMAGELIST dummySmall; COLORREF clrItemText; }; @@ -118,6 +121,9 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) static struct columnParams *p; uiTableData *data; WCHAR *wstr; + HDC dc; + IWICBitmap *wb; + HBITMAP b; bool queueUpdated = false; wstr = t->dispinfoStrings->front(); @@ -138,8 +144,28 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) } if ((nm->item.mask & LVIF_IMAGE) != 0) - if (p->imageModelColumn != -1) - nm->item.iImage = 0; + if (p->imageModelColumn != -1) { + dc = GetDC(t->hwnd); + if (dc == NULL) + logLastError(L"error getting DC for uiTable in onLVN_GETDISPINFO()"); + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->imageModelColumn); + wb = uiprivImageAppropriateForDC(uiTableDataImage(data), dc); + uiFreeTableData(data); + b = uiprivWICToGDI(wb, dc); + if (ReleaseDC(t->hwnd, dc) == 0) + logLastError(L"error calling ReleaseDC() in onLVN_GETDISPINFO()"); + if (ImageList_GetImageCount(t->smallImages) <= t->smallIndex) { + if (ImageList_Add(t->smallImages, b, NULL) == -1) + logLastError(L"error calling ImageList_Add() in onLVN_GETDISPINFO()"); + } else + if (ImageList_Replace(t->smallImages, t->smallIndex, b, NULL) == 0) + logLastError(L"error calling ImageList_Replace() in onLVN_GETDISPINFO()"); + // TODO error check + DeleteObject(b); + nm->item.iImage = t->smallIndex; + t->smallIndex++; + t->smallIndex %= 3; // TODO don't hardcode this + } // having an image list always leaves space for an image on the main item :| // other places on the internet imply that you should be able to do this but that it shouldn't work @@ -258,7 +284,7 @@ static void uiTableDestroy(uiControl *c) for (auto col : *(t->columns)) uiprivFree(col); delete t->columns; - // t->dummyLarge and t->dummySmall will be automatically destroyed + // t->smallImages will be automatically destroyed uiFreeControl(uiControl(t)); } @@ -407,33 +433,25 @@ uiTable *uiNewTable(uiTableModel *model) if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0) logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()"); + t->backgroundColumn = -1; + t->dispinfoStrings = new std::queue; // this encodes the idea that two LVN_GETDISPINFOs must complete before we can free a string: the first real one is for the fourth call to free t->dispinfoStrings->push(NULL); t->dispinfoStrings->push(NULL); t->dispinfoStrings->push(NULL); - t->backgroundColumn = -1; - // TODO update these when the DPI changes // TODO handle errors // TODO try adding a real transparent image - t->dummyLarge = ImageList_Create( - GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), - ILC_COLOR32, - 1, 0); - if (t->dummyLarge == NULL) - logLastError(L"error calling ImageList_Create() for dummy large image list in uiNewTable()"); - // TODO will this return NULL here because it's an initial state? - SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_NORMAL, (LPARAM) (t->dummyLarge)); - t->dummySmall = ImageList_Create( + t->smallImages = ImageList_Create( GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32, - 1, 0); - if (t->dummySmall == NULL) - logLastError(L"error calling ImageList_Create() for dummy small image list in uiNewTable()"); + 0, 3); + if (t->smallImages == NULL) + logLastError(L"error calling ImageList_Create() in uiNewTable()"); // TODO will this return NULL here because it's an initial state? - SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->dummySmall)); + SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages)); return t; } From 0f59bf7399b6c377d6add7bce382d16b975d4c69 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 9 Jun 2018 20:42:13 -0400 Subject: [PATCH 065/166] More TODOs. --- windows/image.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/image.cpp b/windows/image.cpp index a2fb2853..a85207f5 100644 --- a/windows/image.cpp +++ b/windows/image.cpp @@ -121,6 +121,7 @@ IWICBitmap *uiprivImageAppropriateForDC(uiImage *i, HDC dc) } // TODO see if we can really pass NULL to CreateDIBSection()'s HDC parameter, and if so, use HBITMAPs before WIC maybe? +// TODO this needs to actually scale down to fit if the image size isn't perfectly equal to a requested size I need to pass as a parameter here... HBITMAP uiprivWICToGDI(IWICBitmap *b, HDC dc) { BITMAPINFO bmi; From 4c5f0961feaded4b80868c9a94231c85d67034ca Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 9 Jun 2018 23:02:37 -0400 Subject: [PATCH 066/166] Started an implementation of checkboxes in table.cpp. List View simply does not seem to support state images in subitems, so we'll have to improvise. Hopefully this won't screw accessibility. --- windows/table.cpp | 120 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 8 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 47cedb63..96e9011b 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -38,11 +38,15 @@ struct uiTable { // we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue std::queue *dispinfoStrings; // likewise here, though the docs aren't as clear + // TODO make sure what we're doing is even allowed HIMAGELIST smallImages; int smallIndex; // custom draw state COLORREF clrItemText; + + // checkbox + HIMAGELIST checkboxImages; }; uiTableModel *uiNewTableModel(uiTableModelHandler *mh) @@ -124,6 +128,7 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) HDC dc; IWICBitmap *wb; HBITMAP b; + int checked; bool queueUpdated = false; wstr = t->dispinfoStrings->front(); @@ -132,7 +137,7 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) t->dispinfoStrings->pop(); p = (*(t->columns))[nm->item.iSubItem]; - // TODO is this condition ever not going to be true for libui? +nm->item.pszText=L"abcdefg"; if ((nm->item.mask & LVIF_TEXT) != 0) if (p->textModelColumn != -1) { data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); @@ -175,6 +180,21 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) nm->item.iIndent = -1; } + if ((nm->item.mask & LVIF_STATE) != 0) + if (p->checkboxModelColumn != -1) { + // TODO handle enabled + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); + checked = uiTableDataInt(data) != 0; + uiFreeTableData(data); + nm->item.state = INDEXTOSTATEIMAGEMASK(1); + if (checked) + nm->item.state = INDEXTOSTATEIMAGEMASK(2); + nm->item.stateMask = LVIS_STATEIMAGEMASK; + } else { + nm->item.state = INDEXTOSTATEIMAGEMASK(0); + nm->item.stateMask = LVIS_STATEIMAGEMASK; + } + // we don't want to pop from an empty queue, so if nothing updated the queue (no info was filled in above), just push NULL if (!queueUpdated) t->dispinfoStrings->push(NULL); @@ -285,6 +305,7 @@ static void uiTableDestroy(uiControl *c) uiprivFree(col); delete t->columns; // t->smallImages will be automatically destroyed + // TODO will t->checkboxImages? uiFreeControl(uiControl(t)); } @@ -331,8 +352,11 @@ static struct columnParams *appendColumn(uiTable *t, const char *name, int colfm p = uiprivNew(struct columnParams); p->textModelColumn = -1; + p->textEditableColumn = -1; + p->textParams = defaultTextColumnOptionalParams; p->imageModelColumn = -1; p->checkboxModelColumn = -1; + p->checkboxEditableColumn = -1; p->progressBarModelColumn = -1; p->buttonModelColumn = -1; t->columns->push_back(p); @@ -348,8 +372,6 @@ void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, p->textEditableColumn = textEditableModelColumn; if (params != NULL) p->textParams = *params; - else - p->textParams = defaultTextColumnOptionalParams; } void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) @@ -366,14 +388,16 @@ void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelCo p->textEditableColumn = textEditableModelColumn; if (textParams != NULL) p->textParams = *textParams; - else - p->textParams = defaultTextColumnOptionalParams; p->imageModelColumn = imageModelColumn; } void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) { - // TODO + struct columnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->checkboxModelColumn = checkboxModelColumn; + p->checkboxEditableColumn = checkboxEditableModelColumn; } void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) @@ -385,8 +409,6 @@ void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxM p->textEditableColumn = textEditableModelColumn; if (textParams != NULL) p->textParams = *textParams; - else - p->textParams = defaultTextColumnOptionalParams; p->checkboxModelColumn = checkboxModelColumn; p->checkboxEditableColumn = checkboxEditableModelColumn; } @@ -408,6 +430,83 @@ void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) // TODO redraw? } +// see https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485 +static UINT unthemedStates[] = { + 0, + DFCS_CHECKED, + DFCS_INACTIVE, + DFCS_CHECKED | DFCS_INACTIVE, +}; + +// TODO call this whenever either theme, system colors (maybe? TODO), or DPI change +static void mkCheckboxesUnthemed(uiTable *t, int cx, int cy) +{ + HDC dc; + BITMAPINFO bmi; + HBITMAP b; + VOID *bits; + HDC cdc; + HBITMAP prevBitmap; + RECT r; + int i, n; + + // + 1 because we can't actually use image index 0 — that index is used to mean "no state image" in LVITEMW + n = ARRAYSIZE(unthemedStates) + 1; + + dc = GetDC(t->hwnd); + if (dc == NULL) + logLastError(L"error calling GetDC() in mkCheckboxesUnthemed()"); + ZeroMemory(&bmi, sizeof (BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = cx * n; + bmi.bmiHeader.biHeight = cy; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + b = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, + &bits, NULL, 0); + if (b == NULL) + logLastError(L"error calling CreateDIBSection() in mkCheckboxesUnthemed()"); + + cdc = CreateCompatibleDC(dc); + if (cdc == NULL) + logLastError(L"error calling CreateCompatibleDC() in mkCheckboxesUnthemed()"); + // TODO error check + prevBitmap = (HBITMAP) SelectObject(cdc, b); + + // note we start at cx to start at index 1 + r.left = cx; + r.top = 0; + r.right = cx * 2; + r.bottom = cy; + for (i = 0; i < ARRAYSIZE(unthemedStates); i++) { + if (DrawFrameControl(cdc, &r, + DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | unthemedStates[i]) == 0) + logLastError(L"error calling DrawFrameControl() in mkCheckboxesUnthemed()"); + r.left += cx; + r.right += cx; + } + + // TODO error check + SelectObject(cdc, prevBitmap); + if (DeleteDC(cdc) == 0) + logLastError(L"error calling DeleteDC() in mkCheckboxesUnthemed()"); + if (ReleaseDC(t->hwnd, dc) == 0) + logLastError(L"error calling ReleaseDC() in mkCheckboxesUnthemed()"); + + t->checkboxImages = ImageList_Create(cx, cy, ILC_COLOR32, + n, n); + if (t->checkboxImages == NULL) + logLastError(L"error calling ImageList_Create() in mkCheckboxesUnthemed()"); + if (ImageList_Add(t->checkboxImages, b, NULL) == -1) + logLastError(L"error calling ImageList_Add() in mkCheckboxesUnthemed()"); + // TODO will this return NULL here because it's an initial state? + SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_STATE, (LPARAM) (t->checkboxImages)); + + // TODO error check + DeleteObject(b); +} + uiTable *uiNewTable(uiTableModel *model) { uiTable *t; @@ -453,5 +552,10 @@ uiTable *uiNewTable(uiTableModel *model) // TODO will this return NULL here because it's an initial state? SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages)); + mkCheckboxesUnthemed(t, 16, 16); + // and we need to enable LVN_GETDISPINFO for states + if (SendMessageW(t->hwnd, LVM_SETCALLBACKMASK, LVIS_STATEIMAGEMASK, 0) == FALSE) + logLastError(L"error calling LVM_SETCALLBACKMASK in uiTableAdd()"); + return t; } From 405a6defd257c0690e96792d5edd5534b8b75723 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 9 Jun 2018 23:25:16 -0400 Subject: [PATCH 067/166] Started using normal images for checkboxes in Windows uiTables. It doesn't quite work yet. --- windows/table.cpp | 75 ++++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 47 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 96e9011b..49eb9d79 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -9,6 +9,9 @@ static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { /*TODO.ColorModelColumn = */-1, }; +#define nCheckboxImages 4 +#define nLVN_GETDISPINFOSkip 3 + struct columnParams { int textModelColumn; int textEditableColumn; @@ -159,17 +162,13 @@ nm->item.pszText=L"abcdefg"; b = uiprivWICToGDI(wb, dc); if (ReleaseDC(t->hwnd, dc) == 0) logLastError(L"error calling ReleaseDC() in onLVN_GETDISPINFO()"); - if (ImageList_GetImageCount(t->smallImages) <= t->smallIndex) { - if (ImageList_Add(t->smallImages, b, NULL) == -1) - logLastError(L"error calling ImageList_Add() in onLVN_GETDISPINFO()"); - } else - if (ImageList_Replace(t->smallImages, t->smallIndex, b, NULL) == 0) - logLastError(L"error calling ImageList_Replace() in onLVN_GETDISPINFO()"); + if (ImageList_Replace(t->smallImages, t->smallIndex + nCheckboxImages, b, NULL) == 0) + logLastError(L"error calling ImageList_Replace() in onLVN_GETDISPINFO()"); // TODO error check DeleteObject(b); - nm->item.iImage = t->smallIndex; + nm->item.iImage = t->smallIndex + nCheckboxImages; t->smallIndex++; - t->smallIndex %= 3; // TODO don't hardcode this + t->smallIndex %= nLVN_GETDISPINFOSkip; } // having an image list always leaves space for an image on the main item :| @@ -180,20 +179,16 @@ nm->item.pszText=L"abcdefg"; nm->item.iIndent = -1; } - if ((nm->item.mask & LVIF_STATE) != 0) - if (p->checkboxModelColumn != -1) { - // TODO handle enabled - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); - checked = uiTableDataInt(data) != 0; - uiFreeTableData(data); - nm->item.state = INDEXTOSTATEIMAGEMASK(1); - if (checked) - nm->item.state = INDEXTOSTATEIMAGEMASK(2); - nm->item.stateMask = LVIS_STATEIMAGEMASK; - } else { - nm->item.state = INDEXTOSTATEIMAGEMASK(0); - nm->item.stateMask = LVIS_STATEIMAGEMASK; - } + if (p->checkboxModelColumn != -1) { + // TODO handle enabled + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); + checked = uiTableDataInt(data) != 0; + uiFreeTableData(data); + nm->item.iImage = 0; + if (checked) + nm->item.iImage = 1; + nm->item.mask |= LVIF_IMAGE; + } // we don't want to pop from an empty queue, so if nothing updated the queue (no info was filled in above), just push NULL if (!queueUpdated) @@ -448,17 +443,14 @@ static void mkCheckboxesUnthemed(uiTable *t, int cx, int cy) HDC cdc; HBITMAP prevBitmap; RECT r; - int i, n; - - // + 1 because we can't actually use image index 0 — that index is used to mean "no state image" in LVITEMW - n = ARRAYSIZE(unthemedStates) + 1; + int i; dc = GetDC(t->hwnd); if (dc == NULL) logLastError(L"error calling GetDC() in mkCheckboxesUnthemed()"); ZeroMemory(&bmi, sizeof (BITMAPINFO)); bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); - bmi.bmiHeader.biWidth = cx * n; + bmi.bmiHeader.biWidth = cx * nCheckboxImages; bmi.bmiHeader.biHeight = cy; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; @@ -474,12 +466,11 @@ static void mkCheckboxesUnthemed(uiTable *t, int cx, int cy) // TODO error check prevBitmap = (HBITMAP) SelectObject(cdc, b); - // note we start at cx to start at index 1 - r.left = cx; + r.left = 0; r.top = 0; - r.right = cx * 2; + r.right = cx; r.bottom = cy; - for (i = 0; i < ARRAYSIZE(unthemedStates); i++) { + for (i = 0; i < nCheckboxImages; i++) { if (DrawFrameControl(cdc, &r, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | unthemedStates[i]) == 0) logLastError(L"error calling DrawFrameControl() in mkCheckboxesUnthemed()"); @@ -494,14 +485,8 @@ static void mkCheckboxesUnthemed(uiTable *t, int cx, int cy) if (ReleaseDC(t->hwnd, dc) == 0) logLastError(L"error calling ReleaseDC() in mkCheckboxesUnthemed()"); - t->checkboxImages = ImageList_Create(cx, cy, ILC_COLOR32, - n, n); - if (t->checkboxImages == NULL) - logLastError(L"error calling ImageList_Create() in mkCheckboxesUnthemed()"); - if (ImageList_Add(t->checkboxImages, b, NULL) == -1) - logLastError(L"error calling ImageList_Add() in mkCheckboxesUnthemed()"); - // TODO will this return NULL here because it's an initial state? - SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_STATE, (LPARAM) (t->checkboxImages)); + if (ImageList_Replace(t->smallImages, 0, b, NULL) == 0) + logLastError(L"error calling ImageList_Replace() in mkCheckboxesUnthemed()"); // TODO error check DeleteObject(b); @@ -511,6 +496,7 @@ uiTable *uiNewTable(uiTableModel *model) { uiTable *t; int n; + int i; uiWindowsNewControl(uiTable, t); @@ -536,26 +522,21 @@ uiTable *uiNewTable(uiTableModel *model) t->dispinfoStrings = new std::queue; // this encodes the idea that two LVN_GETDISPINFOs must complete before we can free a string: the first real one is for the fourth call to free - t->dispinfoStrings->push(NULL); - t->dispinfoStrings->push(NULL); - t->dispinfoStrings->push(NULL); + for (i = 0; i < nLVN_GETDISPINFOSkip; i++) + t->dispinfoStrings->push(NULL); // TODO update these when the DPI changes // TODO handle errors - // TODO try adding a real transparent image t->smallImages = ImageList_Create( GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32, - 0, 3); + nCheckboxImages + nLVN_GETDISPINFOSkip, nCheckboxImages + nLVN_GETDISPINFOSkip); if (t->smallImages == NULL) logLastError(L"error calling ImageList_Create() in uiNewTable()"); // TODO will this return NULL here because it's an initial state? SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages)); mkCheckboxesUnthemed(t, 16, 16); - // and we need to enable LVN_GETDISPINFO for states - if (SendMessageW(t->hwnd, LVM_SETCALLBACKMASK, LVIS_STATEIMAGEMASK, 0) == FALSE) - logLastError(L"error calling LVM_SETCALLBACKMASK in uiTableAdd()"); return t; } From 8d43b55ff8bbf168b3cc5e3d15e039e84c171959 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 9 Jun 2018 23:55:43 -0400 Subject: [PATCH 068/166] Removed some now-unused stuff. --- windows/table.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 49eb9d79..ab68374b 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -47,9 +47,6 @@ struct uiTable { // custom draw state COLORREF clrItemText; - - // checkbox - HIMAGELIST checkboxImages; }; uiTableModel *uiNewTableModel(uiTableModelHandler *mh) @@ -300,7 +297,6 @@ static void uiTableDestroy(uiControl *c) uiprivFree(col); delete t->columns; // t->smallImages will be automatically destroyed - // TODO will t->checkboxImages? uiFreeControl(uiControl(t)); } From a3feb425a1e05404f7006b702b07be0bdb63723e Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 10 Jun 2018 00:49:44 -0400 Subject: [PATCH 069/166] Started splitting image and checkbox stuff into its own file. Also started adopting HRESULT returns everywhere, because why not make the conversion to it later slightly easier by starting now. --- windows/CMakeLists.txt | 1 + windows/table.cpp | 108 ++++++---------------------------------- windows/table.hpp | 47 +++++++++++++++++ windows/tableimages.cpp | 89 +++++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 93 deletions(-) create mode 100644 windows/table.hpp create mode 100644 windows/tableimages.cpp diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 31ba2c93..4cee5d68 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -51,6 +51,7 @@ list(APPEND _LIBUI_SOURCES windows/stddialogs.cpp windows/tab.cpp windows/table.cpp + windows/tableimages.cpp windows/tabpage.cpp windows/text.cpp windows/utf16.cpp diff --git a/windows/table.cpp b/windows/table.cpp index ab68374b..d0f2210f 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -1,54 +1,10 @@ #include "uipriv_windows.hpp" - -struct uiTableModel { - uiTableModelHandler *mh; - std::vector *tables; -}; +#include "table.hpp" static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { /*TODO.ColorModelColumn = */-1, }; -#define nCheckboxImages 4 -#define nLVN_GETDISPINFOSkip 3 - -struct columnParams { - int textModelColumn; - int textEditableColumn; - uiTableTextColumnOptionalParams textParams; - - int imageModelColumn; - - int checkboxModelColumn; - int checkboxEditableColumn; - - int progressBarModelColumn; - - int buttonModelColumn; - int buttonClickableModelColumn; -}; - -struct uiTable { - uiWindowsControl c; - uiTableModel *model; - HWND hwnd; - std::vector *columns; - WPARAM nColumns; - int backgroundColumn; - - // owner data state - // MSDN says we have to keep LVN_GETDISPINFO strings we allocate around at least until "two additional LVN_GETDISPINFO messages have been sent". - // we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue - std::queue *dispinfoStrings; - // likewise here, though the docs aren't as clear - // TODO make sure what we're doing is even allowed - HIMAGELIST smallImages; - int smallIndex; - - // custom draw state - COLORREF clrItemText; -}; - uiTableModel *uiNewTableModel(uiTableModelHandler *mh) { uiTableModel *m; @@ -122,7 +78,7 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) { - static struct columnParams *p; + static uiprivTableColumnParams *p; uiTableData *data; WCHAR *wstr; HDC dc; @@ -148,43 +104,9 @@ nm->item.pszText=L"abcdefg"; queueUpdated = true; } - if ((nm->item.mask & LVIF_IMAGE) != 0) - if (p->imageModelColumn != -1) { - dc = GetDC(t->hwnd); - if (dc == NULL) - logLastError(L"error getting DC for uiTable in onLVN_GETDISPINFO()"); - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->imageModelColumn); - wb = uiprivImageAppropriateForDC(uiTableDataImage(data), dc); - uiFreeTableData(data); - b = uiprivWICToGDI(wb, dc); - if (ReleaseDC(t->hwnd, dc) == 0) - logLastError(L"error calling ReleaseDC() in onLVN_GETDISPINFO()"); - if (ImageList_Replace(t->smallImages, t->smallIndex + nCheckboxImages, b, NULL) == 0) - logLastError(L"error calling ImageList_Replace() in onLVN_GETDISPINFO()"); - // TODO error check - DeleteObject(b); - nm->item.iImage = t->smallIndex + nCheckboxImages; - t->smallIndex++; - t->smallIndex %= nLVN_GETDISPINFOSkip; - } - - // having an image list always leaves space for an image on the main item :| - // other places on the internet imply that you should be able to do this but that it shouldn't work - // but it works perfectly (and pixel-perfectly too) for me, so... - if (nm->item.iSubItem == 0 && p->imageModelColumn == -1) { - nm->item.mask |= LVIF_INDENT; - nm->item.iIndent = -1; - } - - if (p->checkboxModelColumn != -1) { - // TODO handle enabled - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); - checked = uiTableDataInt(data) != 0; - uiFreeTableData(data); - nm->item.iImage = 0; - if (checked) - nm->item.iImage = 1; - nm->item.mask |= LVIF_IMAGE; + hr = uiprivLVN_GETDISPINFOImagesCheckboxes(t, nm, p); + if (hr != S_OK) { + // TODO } // we don't want to pop from an empty queue, so if nothing updated the queue (no info was filled in above), just push NULL @@ -216,7 +138,7 @@ static COLORREF blend(COLORREF base, double r, double g, double b, double a) static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) { - struct columnParams *p; + uiprivTableColumnParams *p; uiTableData *data; double r, g, b, a; LRESULT ret; @@ -324,11 +246,11 @@ static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height) *height = y; } -static struct columnParams *appendColumn(uiTable *t, const char *name, int colfmt) +static uiprivTableColumnParams *appendColumn(uiTable *t, const char *name, int colfmt) { WCHAR *wstr; LVCOLUMNW lvc; - struct columnParams *p; + uiprivTableColumnParams *p; ZeroMemory(&lvc, sizeof (LVCOLUMNW)); lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; @@ -341,7 +263,7 @@ static struct columnParams *appendColumn(uiTable *t, const char *name, int colfm uiprivFree(wstr); t->nColumns++; - p = uiprivNew(struct columnParams); + p = uiprivNew(uiprivTableColumnParams); p->textModelColumn = -1; p->textEditableColumn = -1; p->textParams = defaultTextColumnOptionalParams; @@ -356,7 +278,7 @@ static struct columnParams *appendColumn(uiTable *t, const char *name, int colfm void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *params) { - struct columnParams *p; + uiprivTableColumnParams *p; p = appendColumn(t, name, LVCFMT_LEFT); p->textModelColumn = textModelColumn; @@ -372,7 +294,7 @@ void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) { - struct columnParams *p; + uiprivTableColumnParams *p; p = appendColumn(t, name, LVCFMT_LEFT); p->textModelColumn = textModelColumn; @@ -384,7 +306,7 @@ void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelCo void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) { - struct columnParams *p; + uiprivTableColumnParams *p; p = appendColumn(t, name, LVCFMT_LEFT); p->checkboxModelColumn = checkboxModelColumn; @@ -496,7 +418,7 @@ uiTable *uiNewTable(uiTableModel *model) uiWindowsNewControl(uiTable, t); - t->columns = new std::vector; + t->columns = new std::vector; t->model = model; t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, WC_LISTVIEW, L"", @@ -518,7 +440,7 @@ uiTable *uiNewTable(uiTableModel *model) t->dispinfoStrings = new std::queue; // this encodes the idea that two LVN_GETDISPINFOs must complete before we can free a string: the first real one is for the fourth call to free - for (i = 0; i < nLVN_GETDISPINFOSkip; i++) + for (i = 0; i < uiprivNumLVN_GETDISPINFOSkip; i++) t->dispinfoStrings->push(NULL); // TODO update these when the DPI changes @@ -526,7 +448,7 @@ uiTable *uiNewTable(uiTableModel *model) t->smallImages = ImageList_Create( GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32, - nCheckboxImages + nLVN_GETDISPINFOSkip, nCheckboxImages + nLVN_GETDISPINFOSkip); + nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip, nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip); if (t->smallImages == NULL) logLastError(L"error calling ImageList_Create() in uiNewTable()"); // TODO will this return NULL here because it's an initial state? diff --git a/windows/table.hpp b/windows/table.hpp new file mode 100644 index 00000000..51994bb5 --- /dev/null +++ b/windows/table.hpp @@ -0,0 +1,47 @@ +// 10 june 2018 + +// table.cpp +#define uiprivNumLVN_GETDISPINFOSkip 3 +struct uiTableModel { + uiTableModelHandler *mh; + std::vector *tables; +}; +typedef struct uiprivTableColumnParams uiprivTableColumnParams; +struct uiprivTableColumnParams { + int textModelColumn; + int textEditableColumn; + uiTableTextColumnOptionalParams textParams; + + int imageModelColumn; + + int checkboxModelColumn; + int checkboxEditableColumn; + + int progressBarModelColumn; + + int buttonModelColumn; + int buttonClickableModelColumn; +}; +struct uiTable { + uiWindowsControl c; + uiTableModel *model; + HWND hwnd; + std::vector *columns; + WPARAM nColumns; + int backgroundColumn; + + // owner data state + // MSDN says we have to keep LVN_GETDISPINFO strings we allocate around at least until "two additional LVN_GETDISPINFO messages have been sent". + // we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue + std::queue *dispinfoStrings; + // likewise here, though the docs aren't as clear + // TODO make sure what we're doing is even allowed + HIMAGELIST smallImages; + int smallIndex; + + // custom draw state + COLORREF clrItemText; +}; + +// tableimage.cpp +extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp new file mode 100644 index 00000000..47d392c6 --- /dev/null +++ b/windows/tableimages.cpp @@ -0,0 +1,89 @@ +// 10 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +/* +This file handles both images and checkboxes in tables. + +For images, we'll do a similar thing to what text columns do: cycle out images from the small image list every few LVN_GETDISPINFO notifications. + +For checkboxes, the native list view checkbox functionality uses state images, but those are only supported on the main item, not on subitems. So instead, we'll do them on normal images instead. +TODO will this affect accessibility? + +We'll use the small image list. For this, the first few items will be reserved for checkboxes, and the last few for cell images. +*/ + +#define nCheckboxImages 4 + +static HRESULT setCellImage(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p, uiTableData *data) +{ + int index; + HDC dc; + IWICImage *wb; + HBITMAP b; + + index = t->smallIndex + nCheckboxImages; + t->smallIndex++; + t->smallIndex %= uiprivNumLVN_GETDISPINFOSkip; + nm->item.iImage = index; + + dc = GetDC(t->hwnd); + if (dc == NULL) { + logLastError(L"GetDC()"); + return E_FAIL; + } + + wb = uiprivImageAppropriateForDC(uiTableDataImage(data), dc); + b = uiprivWICToGDI(wb, dc); + if (ImageList_Replace(t->smallImages, index, b, NULL) == 0) { + logLastError(L"ImageList_Replace()"); + return E_FAIL; + } + + if (ReleaseDC(t->hwnd, dc) == 0) { + logLastError(L"ReleaseDC()"); + return E_FAIL; + } + + return S_OK; +} + +HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) +{ + uiTableData *data; + HRESULT hr; + + if ((nm->item.mask & LVIF_IMAGE) == 0) + return S_OK; // nothing to do here + + if (p->imageModelColumn != -1) { + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->imageModelColumn); + hr = setCellImage(t, nm, p, data); + uiFreeTableData(data); + return hr; + } + +#if 0 + if (p->checkboxModelColumn != -1) { + // TODO handle enabled + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); + checked = uiTableDataInt(data) != 0; + uiFreeTableData(data); + nm->item.iImage = 0; + if (checked) + nm->item.iImage = 1; + nm->item.mask |= LVIF_IMAGE; + } +#endif + + // if we got here, there's no image in this cell + nm->item.iImage = 0; + // having an image list always leaves space for an image on the main item :| + // other places on the internet imply that you should be able to do this but that it shouldn't work + // but it works perfectly (and pixel-perfectly too) for me, so... + if (nm->item.iSubItem == 0) { + nm->item.mask |= LVIF_INDENT; + nm->item.iIndent = -1; + } + return S_OK; +} From c22f643df709b02b36e8f88d865707211b7ea05e Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 10 Jun 2018 10:43:29 -0400 Subject: [PATCH 070/166] More image and checkbox fixups. Next: themed checkboxes. --- windows/table.cpp | 87 ++--------------------- windows/table.hpp | 6 +- windows/tableimages.cpp | 153 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 156 insertions(+), 90 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index d0f2210f..f6fe1c94 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -86,6 +86,7 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) HBITMAP b; int checked; bool queueUpdated = false; + HRESULT hr; wstr = t->dispinfoStrings->front(); if (wstr != NULL) @@ -315,7 +316,7 @@ void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModel void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) { - struct columnParams *p; + uiprivTableColumnParams *p; p = appendColumn(t, name, LVCFMT_LEFT); p->textModelColumn = textModelColumn; @@ -343,78 +344,12 @@ void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) // TODO redraw? } -// see https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485 -static UINT unthemedStates[] = { - 0, - DFCS_CHECKED, - DFCS_INACTIVE, - DFCS_CHECKED | DFCS_INACTIVE, -}; - -// TODO call this whenever either theme, system colors (maybe? TODO), or DPI change -static void mkCheckboxesUnthemed(uiTable *t, int cx, int cy) -{ - HDC dc; - BITMAPINFO bmi; - HBITMAP b; - VOID *bits; - HDC cdc; - HBITMAP prevBitmap; - RECT r; - int i; - - dc = GetDC(t->hwnd); - if (dc == NULL) - logLastError(L"error calling GetDC() in mkCheckboxesUnthemed()"); - ZeroMemory(&bmi, sizeof (BITMAPINFO)); - bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); - bmi.bmiHeader.biWidth = cx * nCheckboxImages; - bmi.bmiHeader.biHeight = cy; - bmi.bmiHeader.biPlanes = 1; - bmi.bmiHeader.biBitCount = 32; - bmi.bmiHeader.biCompression = BI_RGB; - b = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, - &bits, NULL, 0); - if (b == NULL) - logLastError(L"error calling CreateDIBSection() in mkCheckboxesUnthemed()"); - - cdc = CreateCompatibleDC(dc); - if (cdc == NULL) - logLastError(L"error calling CreateCompatibleDC() in mkCheckboxesUnthemed()"); - // TODO error check - prevBitmap = (HBITMAP) SelectObject(cdc, b); - - r.left = 0; - r.top = 0; - r.right = cx; - r.bottom = cy; - for (i = 0; i < nCheckboxImages; i++) { - if (DrawFrameControl(cdc, &r, - DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | unthemedStates[i]) == 0) - logLastError(L"error calling DrawFrameControl() in mkCheckboxesUnthemed()"); - r.left += cx; - r.right += cx; - } - - // TODO error check - SelectObject(cdc, prevBitmap); - if (DeleteDC(cdc) == 0) - logLastError(L"error calling DeleteDC() in mkCheckboxesUnthemed()"); - if (ReleaseDC(t->hwnd, dc) == 0) - logLastError(L"error calling ReleaseDC() in mkCheckboxesUnthemed()"); - - if (ImageList_Replace(t->smallImages, 0, b, NULL) == 0) - logLastError(L"error calling ImageList_Replace() in mkCheckboxesUnthemed()"); - - // TODO error check - DeleteObject(b); -} - uiTable *uiNewTable(uiTableModel *model) { uiTable *t; int n; int i; + HRESULT hr; uiWindowsNewControl(uiTable, t); @@ -443,18 +378,10 @@ uiTable *uiNewTable(uiTableModel *model) for (i = 0; i < uiprivNumLVN_GETDISPINFOSkip; i++) t->dispinfoStrings->push(NULL); - // TODO update these when the DPI changes - // TODO handle errors - t->smallImages = ImageList_Create( - GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), - ILC_COLOR32, - nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip, nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip); - if (t->smallImages == NULL) - logLastError(L"error calling ImageList_Create() in uiNewTable()"); - // TODO will this return NULL here because it's an initial state? - SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages)); - - mkCheckboxesUnthemed(t, 16, 16); + hr = uiprivTableSetupImagesCheckboxes(t); + if (hr != S_OK) { + // TODO + } return t; } diff --git a/windows/table.hpp b/windows/table.hpp index 51994bb5..98ede2f1 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -34,7 +34,8 @@ struct uiTable { // MSDN says we have to keep LVN_GETDISPINFO strings we allocate around at least until "two additional LVN_GETDISPINFO messages have been sent". // we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue std::queue *dispinfoStrings; - // likewise here, though the docs aren't as clear + + // tableimages.cpp // TODO make sure what we're doing is even allowed HIMAGELIST smallImages; int smallIndex; @@ -43,5 +44,6 @@ struct uiTable { COLORREF clrItemText; }; -// tableimage.cpp +// tableimages.cpp extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); +extern HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t); diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index 47d392c6..8142aec7 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -13,13 +13,17 @@ TODO will this affect accessibility? We'll use the small image list. For this, the first few items will be reserved for checkboxes, and the last few for cell images. */ +// checkboxes TODOs: +// - see if we need to get rid of the extra margin in subitems +// - see if we need to get rid of the glow effect + #define nCheckboxImages 4 static HRESULT setCellImage(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p, uiTableData *data) { int index; HDC dc; - IWICImage *wb; + IWICBitmap *wb; HBITMAP b; index = t->smallIndex + nCheckboxImages; @@ -35,10 +39,17 @@ static HRESULT setCellImage(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnPara wb = uiprivImageAppropriateForDC(uiTableDataImage(data), dc); b = uiprivWICToGDI(wb, dc); - if (ImageList_Replace(t->smallImages, index, b, NULL) == 0) { - logLastError(L"ImageList_Replace()"); - return E_FAIL; - } + // TODO rewrite this condition to make more sense; possibly swap the if and else blocks too + if (ImageList_GetImageCount(t->smallImages) > index) { + if (ImageList_Replace(t->smallImages, index, b, NULL) == 0) { + logLastError(L"ImageList_Replace()"); + return E_FAIL; + } + } else + if (ImageList_Add(t->smallImages, b, NULL) == -1) { + logLastError(L"ImageList_Add()"); + return E_FAIL; + } if (ReleaseDC(t->hwnd, dc) == 0) { logLastError(L"ReleaseDC()"); @@ -54,6 +65,7 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip HRESULT hr; if ((nm->item.mask & LVIF_IMAGE) == 0) + // TODO we actually need to do the first column fix here too... return S_OK; // nothing to do here if (p->imageModelColumn != -1) { @@ -63,8 +75,8 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip return hr; } -#if 0 if (p->checkboxModelColumn != -1) { +#if 0 // TODO handle enabled data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); checked = uiTableDataInt(data) != 0; @@ -73,17 +85,142 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip if (checked) nm->item.iImage = 1; nm->item.mask |= LVIF_IMAGE; - } #endif + nm->item.mask |= LVIF_IMAGE; + nm->item.iImage = 1; + return S_OK; + } // if we got here, there's no image in this cell - nm->item.iImage = 0; + nm->item.mask &= ~LVIF_IMAGE; // having an image list always leaves space for an image on the main item :| // other places on the internet imply that you should be able to do this but that it shouldn't work // but it works perfectly (and pixel-perfectly too) for me, so... + // TODO it doesn't work anymore... if (nm->item.iSubItem == 0) { nm->item.mask |= LVIF_INDENT; nm->item.iIndent = -1; } return S_OK; } + +// see https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485 +static UINT unthemedStates[] = { + 0, + DFCS_CHECKED, + DFCS_INACTIVE, + DFCS_CHECKED | DFCS_INACTIVE, +}; + +// TODO call this whenever either theme, system colors (maybe? TODO), or DPI change +// TODO properly clean up on failure +static HRESULT mkCheckboxesUnthemed(uiTable *t, int cx, int cy) +{ + HDC dc; + BITMAPINFO bmi; + HBITMAP b; + VOID *bits; + HDC cdc; + HBITMAP prevBitmap; + RECT r; + int i; + + dc = GetDC(t->hwnd); + if (dc == NULL) { + logLastError(L"GetDC()"); + return E_FAIL; + } + ZeroMemory(&bmi, sizeof (BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = cx * nCheckboxImages; + bmi.bmiHeader.biHeight = cy; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + b = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, + &bits, NULL, 0); + if (b == NULL) { + logLastError(L"CreateDIBSection()"); + return E_FAIL; + } + + cdc = CreateCompatibleDC(dc); + if (cdc == NULL) { + logLastError(L"CreateCompatibleDC()"); + return E_FAIL; + } + prevBitmap = (HBITMAP) SelectObject(cdc, b); + if (prevBitmap == NULL) { + logLastError(L"SelectObject() b"); + return E_FAIL; + } + + // the actual list view LVS_EX_CHECKBOXES code does this to ensure the entire image is valid, not just the parts that are drawn after resizing + // TODO find a better, alpha-friendly way to do this + r.left = 0; + r.top = 0; + r.right = cx * nCheckboxImages; + r.bottom = cy; + FillRect(cdc, &r, GetSysColorBrush(COLOR_WINDOW)); + + r.left = 0; + r.top = 0; + r.right = cx; + r.bottom = cy; + // this is what the actual list view LVS_EX_CHECKBOXES code does to more correctly size the checkboxes + // TODO check errors + InflateRect(&r, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE)); + r.right++; + r.bottom++; + for (i = 0; i < nCheckboxImages; i++) { + if (DrawFrameControl(cdc, &r, + DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | unthemedStates[i]) == 0) { + logLastError(L"DrawFrameControl()"); + return E_FAIL; + } + r.left += cx; + r.right += cx; + } + + if (SelectObject(cdc, prevBitmap) != ((HGDIOBJ) b)) { + logLastError(L"SelectObject() prev"); + return E_FAIL; + } + if (DeleteDC(cdc) == 0) { + logLastError(L"DeleteDC()"); + return E_FAIL; + } + if (ReleaseDC(t->hwnd, dc) == 0) { + logLastError(L"ReleaseDC()"); + return E_FAIL; + } + + if (ImageList_Add(t->smallImages, b, NULL) == -1) { + logLastError(L"ImageList_Add()"); + return E_FAIL; + } + + // TODO error check + DeleteObject(b); + return S_OK; +} + +// TODO run again when the DPI changes +HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t) +{ + int cx, cy; + + cx = GetSystemMetrics(SM_CXSMICON); + cy = GetSystemMetrics(SM_CYSMICON); + // TODO handle errors + t->smallImages = ImageList_Create(cx, cy, + ILC_COLOR32, + nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip, nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip); + if (t->smallImages == NULL) { + logLastError(L"ImageList_Create()"); + return E_FAIL; + } + // TODO will this return NULL here because it's an initial state? + SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages)); + return mkCheckboxesUnthemed(t, cx, cy); +} From 8dd9f08ba42238b0d00f9581b9a89b15196e5e31 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 10 Jun 2018 10:45:50 -0400 Subject: [PATCH 071/166] Actually one more quick test fix to cycle through all the images before continuing. Okay, NOW for themed checkboxes. --- windows/tableimages.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index 8142aec7..8c3377a8 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -87,7 +87,7 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip nm->item.mask |= LVIF_IMAGE; #endif nm->item.mask |= LVIF_IMAGE; - nm->item.iImage = 1; + nm->item.iImage = nm->item.iItem % 4; return S_OK; } From 5a5f9ba9accc76c4804137b1f437f6dca03f0e15 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 10 Jun 2018 13:15:21 -0400 Subject: [PATCH 072/166] And added themed checkboxes. --- windows/tableimages.cpp | 145 +++++++++++++++++++++++++++++----------- windows/winapi.hpp | 2 + 2 files changed, 108 insertions(+), 39 deletions(-) diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index 8c3377a8..ac096d66 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -104,7 +104,10 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip return S_OK; } -// see https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485 +// references for checkbox drawing: +// - https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485 +// - https://blogs.msdn.microsoft.com/oldnewthing/20171201-00/?p=97505 + static UINT unthemedStates[] = { 0, DFCS_CHECKED, @@ -112,11 +115,16 @@ static UINT unthemedStates[] = { DFCS_CHECKED | DFCS_INACTIVE, }; -// TODO call this whenever either theme, system colors (maybe? TODO), or DPI change +static int themedStates[] = { + CBS_UNCHECKEDNORMAL, + CBS_CHECKEDNORMAL, + CBS_UNCHECKEDDISABLED, + CBS_CHECKEDDISABLED, +}; + // TODO properly clean up on failure -static HRESULT mkCheckboxesUnthemed(uiTable *t, int cx, int cy) +static HRESULT mkCheckboxes(uiTable *t, HTHEME theme, HDC dc, int cxList, int cyList, int cxCheck, int cyCheck) { - HDC dc; BITMAPINFO bmi; HBITMAP b; VOID *bits; @@ -124,16 +132,12 @@ static HRESULT mkCheckboxesUnthemed(uiTable *t, int cx, int cy) HBITMAP prevBitmap; RECT r; int i; + HRESULT hr; - dc = GetDC(t->hwnd); - if (dc == NULL) { - logLastError(L"GetDC()"); - return E_FAIL; - } ZeroMemory(&bmi, sizeof (BITMAPINFO)); bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); - bmi.bmiHeader.biWidth = cx * nCheckboxImages; - bmi.bmiHeader.biHeight = cy; + bmi.bmiHeader.biWidth = cxList * nCheckboxImages; + bmi.bmiHeader.biHeight = cyList; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; @@ -157,29 +161,54 @@ static HRESULT mkCheckboxesUnthemed(uiTable *t, int cx, int cy) // the actual list view LVS_EX_CHECKBOXES code does this to ensure the entire image is valid, not just the parts that are drawn after resizing // TODO find a better, alpha-friendly way to do this - r.left = 0; - r.top = 0; - r.right = cx * nCheckboxImages; - r.bottom = cy; - FillRect(cdc, &r, GetSysColorBrush(COLOR_WINDOW)); + // note that the actual list view does this only if unthemed, but it can get away with that since its image lists only contain checkmarks + // ours don't, so we have to compromise until the above TODO is resolved so we don't draw alpha stuff on top of garbage + if (theme == NULL || cxList != cxCheck || cyList != cyCheck) { + r.left = 0; + r.top = 0; + r.right = cxList * nCheckboxImages; + r.bottom = cyList; + FillRect(cdc, &r, GetSysColorBrush(COLOR_WINDOW)); + } r.left = 0; r.top = 0; - r.right = cx; - r.bottom = cy; - // this is what the actual list view LVS_EX_CHECKBOXES code does to more correctly size the checkboxes - // TODO check errors - InflateRect(&r, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE)); - r.right++; - r.bottom++; - for (i = 0; i < nCheckboxImages; i++) { - if (DrawFrameControl(cdc, &r, - DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | unthemedStates[i]) == 0) { - logLastError(L"DrawFrameControl()"); - return E_FAIL; + r.right = cxCheck; + r.bottom = cyCheck; + if (theme != NULL) { + // because we're not making an image list exactly the correct size, we'll need to manually position the checkbox correctly + // let's just center it for now + // TODO make sure this is correct... + r.left = (cxList - cxCheck) / 2; + r.top = (cyList - cyCheck) / 2; + r.right += r.left; + r.bottom += r.top; + for (i = 0; i < nCheckboxImages; i++) { + hr = DrawThemeBackground(theme, cdc, + BP_CHECKBOX, themedStates[i], + &r, NULL); + if (hr != S_OK) { + logHRESULT(L"DrawThemeBackground()", hr); + return hr; + } + r.left += cxList; + r.right += cxList; + } + } else { + // this is what the actual list view LVS_EX_CHECKBOXES code does to more correctly size the checkboxes + // TODO check errors + InflateRect(&r, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE)); + r.right++; + r.bottom++; + for (i = 0; i < nCheckboxImages; i++) { + if (DrawFrameControl(cdc, &r, + DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | unthemedStates[i]) == 0) { + logLastError(L"DrawFrameControl()"); + return E_FAIL; + } + r.left += cxList; + r.right += cxList; } - r.left += cx; - r.right += cx; } if (SelectObject(cdc, prevBitmap) != ((HGDIOBJ) b)) { @@ -190,10 +219,6 @@ static HRESULT mkCheckboxesUnthemed(uiTable *t, int cx, int cy) logLastError(L"DeleteDC()"); return E_FAIL; } - if (ReleaseDC(t->hwnd, dc) == 0) { - logLastError(L"ReleaseDC()"); - return E_FAIL; - } if (ImageList_Add(t->smallImages, b, NULL) == -1) { logLastError(L"ImageList_Add()"); @@ -208,12 +233,41 @@ static HRESULT mkCheckboxesUnthemed(uiTable *t, int cx, int cy) // TODO run again when the DPI changes HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t) { - int cx, cy; + HDC dc; + int cxList, cyList; + HTHEME theme; + SIZE sizeCheck; + HRESULT hr; + + dc = GetDC(t->hwnd); + if (dc == NULL) { + logLastError(L"GetDC()"); + return E_FAIL; + } + + cxList = GetSystemMetrics(SM_CXSMICON); + cyList = GetSystemMetrics(SM_CYSMICON); + sizeCheck.cx = cxList; + sizeCheck.cy = cyList; + theme = OpenThemeData(t->hwnd, L"button"); + if (theme != NULL) { + hr = GetThemePartSize(theme, dc, + BP_CHECKBOX, CBS_UNCHECKEDNORMAL, + NULL, TS_DRAW, &sizeCheck); + if (hr != S_OK) { + logHRESULT(L"GetThemePartSize()", hr); + return hr; // TODO fall back? + } + // make sure these checkmarks fit + // unthemed checkmarks will by the code above be smaller than cxList/cyList here + if (cxList < sizeCheck.cx) + cxList = sizeCheck.cx; + if (cyList < sizeCheck.cy) + cyList = sizeCheck.cy; + } - cx = GetSystemMetrics(SM_CXSMICON); - cy = GetSystemMetrics(SM_CYSMICON); // TODO handle errors - t->smallImages = ImageList_Create(cx, cy, + t->smallImages = ImageList_Create(cxList, cyList, ILC_COLOR32, nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip, nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip); if (t->smallImages == NULL) { @@ -222,5 +276,18 @@ HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t) } // TODO will this return NULL here because it's an initial state? SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages)); - return mkCheckboxesUnthemed(t, cx, cy); + hr = mkCheckboxes(t, theme, dc, cxList, cyList, sizeCheck.cx, sizeCheck.cy); + if (hr != S_OK) + return hr; + + hr = CloseThemeData(theme); + if (hr != S_OK) { + logHRESULT(L"CloseThemeData()", hr); + return hr; + } + if (ReleaseDC(t->hwnd, dc) == 0) { + logLastError(L"ReleaseDC()"); + return E_FAIL; + } + return S_OK; } diff --git a/windows/winapi.hpp b/windows/winapi.hpp index f1306db7..297726e3 100644 --- a/windows/winapi.hpp +++ b/windows/winapi.hpp @@ -37,6 +37,8 @@ #ifndef RC_INVOKED #include #include +#include +#include #include #include #include From 94a33978949b0c080e8d6f4455b9523e35dbceae Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 10 Jun 2018 17:38:51 -0400 Subject: [PATCH 073/166] Tried to resolve checkboxes appearing selected. It doesn't work fully yet, so the actual drawing that makes it work is disabled for now. But the handler for NM_CUSTOMDRAW now has a hook to become cleaner in the future. --- windows/table.cpp | 20 ++++++++--- windows/table.hpp | 1 + windows/tableimages.cpp | 73 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index f6fe1c94..d66f9170 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -143,10 +143,12 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) uiTableData *data; double r, g, b, a; LRESULT ret; + HRESULT hr; switch (nm->nmcd.dwDrawStage) { case CDDS_PREPAINT: - return CDRF_NOTIFYITEMDRAW; + ret = CDRF_NOTIFYITEMDRAW; + break; case CDDS_ITEMPREPAINT: if (t->backgroundColumn != -1) { data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, t->backgroundColumn); @@ -157,7 +159,8 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) } } t->clrItemText = nm->clrText; - return CDRF_NEWFONT | CDRF_NOTIFYSUBITEMDRAW; + ret = CDRF_NEWFONT | CDRF_NOTIFYSUBITEMDRAW; + break; case CDDS_SUBITEM | CDDS_ITEMPREPAINT: p = (*(t->columns))[nm->iSubItem]; // we need this as previous subitems will persist their colors @@ -171,9 +174,17 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) } } // TODO draw background on image columns if needed - return CDRF_NEWFONT; + ret = CDRF_NEWFONT; + break; + default: + ret = CDRF_DODEFAULT; } - return CDRF_DODEFAULT; + + hr = uiprivNM_CUSTOMDRAWImagesCheckboxes(t, nm, &ret); + if (hr != S_OK) { + // TODO + } + return ret; } static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) @@ -364,6 +375,7 @@ uiTable *uiNewTable(uiTableModel *model) uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t)); // TODO: try LVS_EX_AUTOSIZECOLUMNS + // TODO check error SendMessageW(t->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, (WPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_SUBITEMIMAGES), (LPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_SUBITEMIMAGES)); diff --git a/windows/table.hpp b/windows/table.hpp index 98ede2f1..0be01d8f 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -46,4 +46,5 @@ struct uiTable { // tableimages.cpp extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); +extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult); extern HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t); diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index ac096d66..2544e1e7 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -15,7 +15,6 @@ We'll use the small image list. For this, the first few items will be reserved f // checkboxes TODOs: // - see if we need to get rid of the extra margin in subitems -// - see if we need to get rid of the glow effect #define nCheckboxImages 4 @@ -59,6 +58,36 @@ static HRESULT setCellImage(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnPara return S_OK; } +#define stateUnchecked 0 +#define stateChecked 1 +#define stateDisabled 2 + +static int checkboxIndex(uiTableModel *m, int row, int checkboxModelColumn, int checkboxEditableColumn) +{ + uiTableData *data; + int ret; + + ret = stateUnchecked; + data = (*(m->mh->CellValue))(m->mh, m, row, checkboxModelColumn); + if (uiTableDataInt(data) != 0) + ret = stateChecked; + uiFreeTableData(data); + + switch (checkboxEditableColumn) { + case uiTableModelColumnNeverEditable: + ret += stateDisabled; + break; + case uiTableModelColumnAlwaysEditable: + break; + default: + data = (*(m->mh->CellValue))(m->mh, m, row, checkboxEditableColumn); + if (uiTableDataInt(data) != 0) + ret += stateDisabled; + } + + return ret; +} + HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) { uiTableData *data; @@ -104,6 +133,48 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip return S_OK; } +// in order to properly look like checkboxes, we need to exclude them from being colored in by the selection rect +// however, there seems to be no way to do this natively, so we have to draw over ourselves (TODO?) +// hopefully the performance won't be too bad +// see also https://www.codeproject.com/Articles/79/Neat-Stuff-to-Do-in-List-Controls-Using-Custom-Dra +HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult) +{ + uiprivTableColumnParams *p; + int index; + RECT r; + + if (nm->nmcd.dwDrawStage == (CDDS_SUBITEM | CDDS_ITEMPREPAINT)) { + *lResult |= CDRF_NOTIFYPOSTPAINT; + return S_OK; + } + if (nm->nmcd.dwDrawStage != (CDDS_SUBITEM | CDDS_ITEMPOSTPAINT)) + return S_OK; + + // only draw over checkboxes + p = (*(t->columns))[nm->iSubItem]; + if (p->checkboxModelColumn == -1) + return S_OK; + + index = checkboxIndex(t->model, nm->nmcd.dwItemSpec, + p->checkboxModelColumn, p->checkboxEditableColumn); + ZeroMemory(&r, sizeof (RECT)); + r.left = LVIR_ICON; + r.top = nm->iSubItem; + if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { + logLastError(L"LVM_GETSUBITEMRECT"); + return E_FAIL; + } +#if 0 + // TODO this is offset by one pixel on my system and everything I've found indicates this should not be happening??? + if (ImageList_Draw(t->smallImages, index, nm->nmcd.hdc, + r.left, r.top, ILD_NORMAL) == 0) { + logLastError(L"ImageList_Draw()"); + return E_FAIL; + } +#endif + return S_OK; +} + // references for checkbox drawing: // - https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485 // - https://blogs.msdn.microsoft.com/oldnewthing/20171201-00/?p=97505 From 2a2990f19cb966e4b26803b25aa2b7fa6d8957c4 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 10 Jun 2018 19:07:34 -0400 Subject: [PATCH 074/166] I have no clue anymore. Tempted to undo checkbox stuff entirely for now. --- windows/table.cpp | 15 +++++++++++++++ windows/tableimages.cpp | 2 ++ 2 files changed, 17 insertions(+) diff --git a/windows/table.cpp b/windows/table.cpp index d66f9170..12deab16 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -163,6 +163,7 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) break; case CDDS_SUBITEM | CDDS_ITEMPREPAINT: p = (*(t->columns))[nm->iSubItem]; + // TODO none of this runs on the first item // we need this as previous subitems will persist their colors nm->clrText = t->clrItemText; if (p->textParams.ColorModelColumn != -1) { @@ -173,6 +174,20 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) nm->clrText = blend(nm->clrTextBk, r, g, b, a); } } +if (nm->iSubItem != 0) { +HWND header; +LRESULT margin; +header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, NULL, NULL); +margin = SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); +//nm->rcText.left -= margin; +printf("%d %d %d %d\n", +(int)(nm->nmcd.rc.left), +(int)(nm->nmcd.rc.top), +(int)(nm->nmcd.rc.right), +(int)(nm->nmcd.rc.bottom)); +FillRect(nm->nmcd.hdc, &(nm->nmcd.rc), GetSysColorBrush(COLOR_ACTIVECAPTION)); +return CDRF_SKIPDEFAULT; +} // TODO draw background on image columns if needed ret = CDRF_NEWFONT; break; diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index 2544e1e7..b8db37af 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -15,6 +15,8 @@ We'll use the small image list. For this, the first few items will be reserved f // checkboxes TODOs: // - see if we need to get rid of the extra margin in subitems +// - get rid of the extra bitmap margin space before text +// - get rid of extra whitespace before text on subitems #define nCheckboxImages 4 From cd2a6f7c29447e55563a9e287a4d4b2e962558ad Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 10 Jun 2018 19:54:04 -0400 Subject: [PATCH 075/166] Fixed the checkbox y-offset issue. There are other issues, but this is more hopeful already... --- windows/table.cpp | 14 -------------- windows/tableimages.cpp | 17 ++++++++++++++--- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 12deab16..66de886e 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -174,20 +174,6 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) nm->clrText = blend(nm->clrTextBk, r, g, b, a); } } -if (nm->iSubItem != 0) { -HWND header; -LRESULT margin; -header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, NULL, NULL); -margin = SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); -//nm->rcText.left -= margin; -printf("%d %d %d %d\n", -(int)(nm->nmcd.rc.left), -(int)(nm->nmcd.rc.top), -(int)(nm->nmcd.rc.right), -(int)(nm->nmcd.rc.bottom)); -FillRect(nm->nmcd.hdc, &(nm->nmcd.rc), GetSysColorBrush(COLOR_ACTIVECAPTION)); -return CDRF_SKIPDEFAULT; -} // TODO draw background on image columns if needed ret = CDRF_NEWFONT; break; diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index b8db37af..0923f409 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -144,6 +144,8 @@ HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRES uiprivTableColumnParams *p; int index; RECT r; + RECT cellRect; + LONG yoff; if (nm->nmcd.dwDrawStage == (CDDS_SUBITEM | CDDS_ITEMPREPAINT)) { *lResult |= CDRF_NOTIFYPOSTPAINT; @@ -166,14 +168,23 @@ HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRES logLastError(L"LVM_GETSUBITEMRECT"); return E_FAIL; } -#if 0 - // TODO this is offset by one pixel on my system and everything I've found indicates this should not be happening??? + // the real listview also does this :| + ZeroMemory(&cellRect, sizeof (RECT)); + r.left = LVIR_BOUNDS; + r.top = nm->iSubItem; + if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&cellRect)) == 0) { + logLastError(L"LVM_GETSUBITEMRECT cell"); + return E_FAIL; + } + yoff = ((cellRect.bottom - cellRect.top) - (r.bottom - r.top)) / 2; + r.top += yoff; + r.bottom += yoff; +if ((nm->nmcd.dwItemSpec%2)==0) if (ImageList_Draw(t->smallImages, index, nm->nmcd.hdc, r.left, r.top, ILD_NORMAL) == 0) { logLastError(L"ImageList_Draw()"); return E_FAIL; } -#endif return S_OK; } From dfb3bd39f1360ed6d0f399b307dd46109744a3e3 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 10 Jun 2018 20:18:07 -0400 Subject: [PATCH 076/166] Some more TODO work. I might as well try custom drawing text now. --- windows/tableimages.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index 0923f409..a42307b1 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -16,7 +16,7 @@ We'll use the small image list. For this, the first few items will be reserved f // checkboxes TODOs: // - see if we need to get rid of the extra margin in subitems // - get rid of the extra bitmap margin space before text -// - get rid of extra whitespace before text on subitems +// - get rid of extra whitespace before text on subitems (this might not be necessary if we can fill the background of images AND this amount is the same as on the first column; it is a hardcoded 2 logical units in the real list view code) #define nCheckboxImages 4 From 0c6e7add012d365de455ace73f34e15b5510c0d1 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 10 Jun 2018 23:03:54 -0400 Subject: [PATCH 077/166] Experimented with custom-drawing the text, this time in a nicer place thatn the default. Okay, this isn't too bad, especially now that it seems everything is vertically centered... --- windows/table.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/windows/table.cpp b/windows/table.cpp index 66de886e..bfb9751e 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -177,6 +177,18 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) // TODO draw background on image columns if needed ret = CDRF_NEWFONT; break; +case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT: +if(nm->iSubItem == 1) { +RECT r, r2; +r.left = LVIR_LABEL; +r.top = 1; +SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM)(&r)); +r2.left = LVIR_ICON; +r2.top = 1; +SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM)(&r2)); +r.left = r2.right + 2; +DrawTextW(nm->nmcd.hdc, L"Part", -1, +&r, DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL);} default: ret = CDRF_DODEFAULT; } From c843f1e62d3febe39d24952fcf5ebaafb970b855 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 11 Jun 2018 08:01:18 -0400 Subject: [PATCH 078/166] More TODOs. --- windows/table.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/windows/table.cpp b/windows/table.cpp index bfb9751e..32c60244 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -253,7 +253,8 @@ uiWindowsControlAllDefaultsExceptDestroy(uiTable) // "columns widths that avoid truncated data x an integral number of items" // Don't think that'll cut it when some cells have overlong data (eg // stupidly long URLs). So for now, just hardcode a minimum. -// TODO: Investigate using LVM_GETHEADER/HDM_LAYOUT here... +// TODO Investigate using LVM_GETHEADER/HDM_LAYOUT here +// TODO investigate using LVM_APPROXIMATEVIEWRECT here #define tableMinWidth 107 /* in line with other controls */ #define tableMinHeight (14 * 3) /* header + 2 lines (roughly) */ From 8f0019af10765ada02eb354a1e8a8fef175d4105 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 11 Jun 2018 22:22:54 -0400 Subject: [PATCH 079/166] Tried to combine all the Windows table metrics stuff. This broke things, so we'll have to go back to the drawing board here. --- wintablemetrics | 153 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 wintablemetrics diff --git a/wintablemetrics b/wintablemetrics new file mode 100644 index 00000000..5c00e252 --- /dev/null +++ b/wintablemetrics @@ -0,0 +1,153 @@ +diff --git a/windows/table.cpp b/windows/table.cpp +index 32c60244..d33cb22f 100644 +--- a/windows/table.cpp ++++ b/windows/table.cpp +@@ -137,11 +137,71 @@ static COLORREF blend(COLORREF base, double r, double g, double b, double a) + (BYTE) (bb * 255)); + } + ++static HRESULT fillSubitemDrawParams(HWND hwnd, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp) ++{ ++ RECT r; ++ HRESULT hr; ++ ++ // note that we can't just copy nm->nmcd.rc into p->bounds because that is only defined during prepaint stages ++ ++ if (nm->iSubItem == 0) {return S_OK; ++ ZeroMemory(&r, sizeof (RECT)); ++ r.left = LVIR_BOUNDS; ++ if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { ++ logLastError(L"LVM_GETITEMRECT LVIR_BOUNDS"); ++ return E_FAIL; ++ } ++ dp->bounds = r; ++ ZeroMemory(&r, sizeof (RECT)); ++ r.left = LVIR_ICON; ++ if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { ++ logLastError(L"LVM_GETITEMRECT LVIR_ICON"); ++ return E_FAIL; ++ } ++ dp->icon = r; ++ ZeroMemory(&r, sizeof (RECT)); ++ r.left = LVIR_LABEL; ++ if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { ++ logLastError(L"LVM_GETITEMRECT LVIR_LABEL"); ++ return E_FAIL; ++ } ++ dp->label = r; ++ return S_OK; ++ } ++ ++ ZeroMemory(&r, sizeof (RECT)); ++ r.left = LVIR_BOUNDS; ++ r.top = nm->iSubItem; ++ if (SendMessageW(hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { ++ logLastError(L"LVM_GETSUBITEMRECT LVIR_BOUNDS"); ++ return E_FAIL; ++ } ++ dp->bounds = r; ++ ZeroMemory(&r, sizeof (RECT)); ++ r.left = LVIR_ICON; ++ r.top = nm->iSubItem; ++ if (SendMessageW(hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { ++ logLastError(L"LVM_GETSUBITEMRECT LVIR_ICON"); ++ return E_FAIL; ++ } ++ dp->icon = r; ++ // LVIR_LABEL is treated as LVIR_BOUNDS for LVM_GETSUBITEMRECT, but it doesn't matter because the label rect is uses isn't what we want anyway ++ // there's a hardocded 2-logical unit gap between the icon and text for subitems, AND the text starts being drawn (in the background) one bitmap margin to the right of that ++ // with normal items, there's no gap, and only the 2-logical unit gap after the background starts (TODO confirm this part) ++ // let's copy that to look nicer, even if it's not "accurate" ++ // TODO check against accessibility ++ dp->label = dp->bounds; ++ // because we want the 2 extra logical units to be included with the background, we don't include them here ++ dp->label.left = dp->icon.right; ++ return S_OK; ++} ++ + static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) + { + uiprivTableColumnParams *p; + uiTableData *data; + double r, g, b, a; ++ uiprivSubitemDrawParams dp; + LRESULT ret; + HRESULT hr; + +@@ -193,7 +253,12 @@ DrawTextW(nm->nmcd.hdc, L"Part", -1, + ret = CDRF_DODEFAULT; + } + +- hr = uiprivNM_CUSTOMDRAWImagesCheckboxes(t, nm, &ret); ++ ZeroMemory(&dp, sizeof (uiprivSubitemDrawParams)); ++ hr = fillSubitemDrawParams(t->hwnd, nm, &dp); ++ if (hr != S_OK) { ++ // TODO ++ } ++ hr = uiprivNM_CUSTOMDRAWImagesCheckboxes(t, nm, &dp, &ret); + if (hr != S_OK) { + // TODO + } +diff --git a/windows/table.hpp b/windows/table.hpp +index 0be01d8f..2f4f6936 100644 +--- a/windows/table.hpp ++++ b/windows/table.hpp +@@ -43,8 +43,14 @@ struct uiTable { + // custom draw state + COLORREF clrItemText; + }; ++typedef struct uiprivSubitemDrawParams uiprivSubitemDrawParams; ++struct uiprivSubitemDrawParams { ++ RECT bounds; ++ RECT icon; ++ RECT label; ++}; + + // tableimages.cpp + extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); +-extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult); ++extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp, LRESULT *lResult); + extern HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t); +diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp +index a42307b1..14bae8c8 100644 +--- a/windows/tableimages.cpp ++++ b/windows/tableimages.cpp +@@ -139,12 +139,11 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip + // however, there seems to be no way to do this natively, so we have to draw over ourselves (TODO?) + // hopefully the performance won't be too bad + // see also https://www.codeproject.com/Articles/79/Neat-Stuff-to-Do-in-List-Controls-Using-Custom-Dra +-HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult) ++HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp, LRESULT *lResult) + { + uiprivTableColumnParams *p; + int index; + RECT r; +- RECT cellRect; + LONG yoff; + + if (nm->nmcd.dwDrawStage == (CDDS_SUBITEM | CDDS_ITEMPREPAINT)) { +@@ -161,22 +160,9 @@ HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRES + + index = checkboxIndex(t->model, nm->nmcd.dwItemSpec, + p->checkboxModelColumn, p->checkboxEditableColumn); +- ZeroMemory(&r, sizeof (RECT)); +- r.left = LVIR_ICON; +- r.top = nm->iSubItem; +- if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { +- logLastError(L"LVM_GETSUBITEMRECT"); +- return E_FAIL; +- } ++ r = dp->icon; + // the real listview also does this :| +- ZeroMemory(&cellRect, sizeof (RECT)); +- r.left = LVIR_BOUNDS; +- r.top = nm->iSubItem; +- if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&cellRect)) == 0) { +- logLastError(L"LVM_GETSUBITEMRECT cell"); +- return E_FAIL; +- } +- yoff = ((cellRect.bottom - cellRect.top) - (r.bottom - r.top)) / 2; ++ yoff = ((dp->bounds.bottom - dp->bounds.top) - (r.bottom - r.top)) / 2; + r.top += yoff; + r.bottom += yoff; + if ((nm->nmcd.dwItemSpec%2)==0) From 0f89418a9559c86135e21bf40fa29808248a2aa9 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 12 Jun 2018 01:40:26 -0400 Subject: [PATCH 080/166] Fixed a typo in tableimages.cpp. Okay, so the code I had before worked purely by accident, and this code doesn't. Wonderful... --- windows/tableimages.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index a42307b1..6bb168b8 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -170,8 +170,8 @@ HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRES } // the real listview also does this :| ZeroMemory(&cellRect, sizeof (RECT)); - r.left = LVIR_BOUNDS; - r.top = nm->iSubItem; + cellRect.left = LVIR_BOUNDS; + cellRect.top = nm->iSubItem; if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&cellRect)) == 0) { logLastError(L"LVM_GETSUBITEMRECT cell"); return E_FAIL; From 59d8e81b854b1b185e83d1d016b09637aa02f7ac Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 12 Jun 2018 01:54:21 -0400 Subject: [PATCH 081/166] There, found the reason that code doesn't work (LVIF_ICON was as tall as LVIF_BOUNDS) and fixed it (use the actual icon size for vertical centering) --- windows/tableimages.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index 6bb168b8..6c6f2df7 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -144,7 +144,7 @@ HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRES uiprivTableColumnParams *p; int index; RECT r; - RECT cellRect; + int cxIcon, cyIcon; LONG yoff; if (nm->nmcd.dwDrawStage == (CDDS_SUBITEM | CDDS_ITEMPREPAINT)) { @@ -169,14 +169,11 @@ HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRES return E_FAIL; } // the real listview also does this :| - ZeroMemory(&cellRect, sizeof (RECT)); - cellRect.left = LVIR_BOUNDS; - cellRect.top = nm->iSubItem; - if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&cellRect)) == 0) { + if (ImageList_GetIconSize(t->smallImages, &cxIcon, &cyIcon) == 0) { logLastError(L"LVM_GETSUBITEMRECT cell"); return E_FAIL; } - yoff = ((cellRect.bottom - cellRect.top) - (r.bottom - r.top)) / 2; + yoff = ((r.bottom - r.top) - cyIcon) / 2; r.top += yoff; r.bottom += yoff; if ((nm->nmcd.dwItemSpec%2)==0) From e52373c59a88737c558690f161ab1e8b2cade0ed Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 12 Jun 2018 07:58:27 -0400 Subject: [PATCH 082/166] Reintegrated wintablemetrics, properly this time (for the most part). It works. Now we can start switching to custom-drawing everything. --- windows/table.cpp | 69 +++++++++++++++++- windows/table.hpp | 10 ++- windows/tableimages.cpp | 10 +-- wintablemetrics | 153 ---------------------------------------- 4 files changed, 78 insertions(+), 164 deletions(-) delete mode 100644 wintablemetrics diff --git a/windows/table.cpp b/windows/table.cpp index 32c60244..9689a023 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -137,11 +137,72 @@ static COLORREF blend(COLORREF base, double r, double g, double b, double a) (BYTE) (bb * 255)); } +static HRESULT fillSubitemDrawParams(HWND hwnd, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp) +{ + RECT r; + HRESULT hr; + + // note that we can't just copy nm->nmcd.rc into p->bounds because that is only defined during prepaint stages + + // TODO this corrupts memory + if (nm->iSubItem == 0) {return S_OK; + ZeroMemory(&r, sizeof (RECT)); + r.left = LVIR_BOUNDS; + if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { + logLastError(L"LVM_GETITEMRECT LVIR_BOUNDS"); + return E_FAIL; + } + dp->bounds = r; + ZeroMemory(&r, sizeof (RECT)); + r.left = LVIR_ICON; + if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { + logLastError(L"LVM_GETITEMRECT LVIR_ICON"); + return E_FAIL; + } + dp->icon = r; + ZeroMemory(&r, sizeof (RECT)); + r.left = LVIR_LABEL; + if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { + logLastError(L"LVM_GETITEMRECT LVIR_LABEL"); + return E_FAIL; + } + dp->label = r; + return S_OK; + } + + ZeroMemory(&r, sizeof (RECT)); + r.left = LVIR_BOUNDS; + r.top = nm->iSubItem; + if (SendMessageW(hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { + logLastError(L"LVM_GETSUBITEMRECT LVIR_BOUNDS"); + return E_FAIL; + } + dp->bounds = r; + ZeroMemory(&r, sizeof (RECT)); + r.left = LVIR_ICON; + r.top = nm->iSubItem; + if (SendMessageW(hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { + logLastError(L"LVM_GETSUBITEMRECT LVIR_ICON"); + return E_FAIL; + } + dp->icon = r; + // LVIR_LABEL is treated as LVIR_BOUNDS for LVM_GETSUBITEMRECT, but it doesn't matter because the label rect is uses isn't what we want anyway + // there's a hardocded 2-logical unit gap between the icon and text for subitems, AND the text starts being drawn (in the background) one bitmap margin to the right of that + // with normal items, there's no gap, and only the 2-logical unit gap after the background starts (TODO confirm this part) + // let's copy that to look nicer, even if it's not "accurate" + // TODO check against accessibility + dp->label = dp->bounds; + // because we want the 2 extra logical units to be included with the background, we don't include them here + dp->label.left = dp->icon.right; + return S_OK; +} + static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) { uiprivTableColumnParams *p; uiTableData *data; double r, g, b, a; + uiprivSubitemDrawParams dp; LRESULT ret; HRESULT hr; @@ -193,7 +254,13 @@ DrawTextW(nm->nmcd.hdc, L"Part", -1, ret = CDRF_DODEFAULT; } - hr = uiprivNM_CUSTOMDRAWImagesCheckboxes(t, nm, &ret); +if ((nm->nmcd.dwDrawStage & CDDS_SUBITEM) == 0)return ret; + ZeroMemory(&dp, sizeof (uiprivSubitemDrawParams)); + hr = fillSubitemDrawParams(t->hwnd, nm, &dp); + if (hr != S_OK) { + // TODO + } + hr = uiprivNM_CUSTOMDRAWImagesCheckboxes(t, nm, &dp, &ret); if (hr != S_OK) { // TODO } diff --git a/windows/table.hpp b/windows/table.hpp index 0be01d8f..4820eaad 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -43,8 +43,14 @@ struct uiTable { // custom draw state COLORREF clrItemText; }; - +typedef struct uiprivSubitemDrawParams uiprivSubitemDrawParams; +struct uiprivSubitemDrawParams { + RECT bounds; + RECT icon; + RECT label; +}; + // tableimages.cpp extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); -extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult); +extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp, LRESULT *lResult); extern HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t); diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index 6c6f2df7..8bc563ac 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -139,7 +139,7 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip // however, there seems to be no way to do this natively, so we have to draw over ourselves (TODO?) // hopefully the performance won't be too bad // see also https://www.codeproject.com/Articles/79/Neat-Stuff-to-Do-in-List-Controls-Using-Custom-Dra -HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult) +HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp, LRESULT *lResult) { uiprivTableColumnParams *p; int index; @@ -161,13 +161,7 @@ HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRES index = checkboxIndex(t->model, nm->nmcd.dwItemSpec, p->checkboxModelColumn, p->checkboxEditableColumn); - ZeroMemory(&r, sizeof (RECT)); - r.left = LVIR_ICON; - r.top = nm->iSubItem; - if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { - logLastError(L"LVM_GETSUBITEMRECT"); - return E_FAIL; - } + r = dp->icon; // the real listview also does this :| if (ImageList_GetIconSize(t->smallImages, &cxIcon, &cyIcon) == 0) { logLastError(L"LVM_GETSUBITEMRECT cell"); diff --git a/wintablemetrics b/wintablemetrics deleted file mode 100644 index 5c00e252..00000000 --- a/wintablemetrics +++ /dev/null @@ -1,153 +0,0 @@ -diff --git a/windows/table.cpp b/windows/table.cpp -index 32c60244..d33cb22f 100644 ---- a/windows/table.cpp -+++ b/windows/table.cpp -@@ -137,11 +137,71 @@ static COLORREF blend(COLORREF base, double r, double g, double b, double a) - (BYTE) (bb * 255)); - } - -+static HRESULT fillSubitemDrawParams(HWND hwnd, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp) -+{ -+ RECT r; -+ HRESULT hr; -+ -+ // note that we can't just copy nm->nmcd.rc into p->bounds because that is only defined during prepaint stages -+ -+ if (nm->iSubItem == 0) {return S_OK; -+ ZeroMemory(&r, sizeof (RECT)); -+ r.left = LVIR_BOUNDS; -+ if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { -+ logLastError(L"LVM_GETITEMRECT LVIR_BOUNDS"); -+ return E_FAIL; -+ } -+ dp->bounds = r; -+ ZeroMemory(&r, sizeof (RECT)); -+ r.left = LVIR_ICON; -+ if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { -+ logLastError(L"LVM_GETITEMRECT LVIR_ICON"); -+ return E_FAIL; -+ } -+ dp->icon = r; -+ ZeroMemory(&r, sizeof (RECT)); -+ r.left = LVIR_LABEL; -+ if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { -+ logLastError(L"LVM_GETITEMRECT LVIR_LABEL"); -+ return E_FAIL; -+ } -+ dp->label = r; -+ return S_OK; -+ } -+ -+ ZeroMemory(&r, sizeof (RECT)); -+ r.left = LVIR_BOUNDS; -+ r.top = nm->iSubItem; -+ if (SendMessageW(hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { -+ logLastError(L"LVM_GETSUBITEMRECT LVIR_BOUNDS"); -+ return E_FAIL; -+ } -+ dp->bounds = r; -+ ZeroMemory(&r, sizeof (RECT)); -+ r.left = LVIR_ICON; -+ r.top = nm->iSubItem; -+ if (SendMessageW(hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { -+ logLastError(L"LVM_GETSUBITEMRECT LVIR_ICON"); -+ return E_FAIL; -+ } -+ dp->icon = r; -+ // LVIR_LABEL is treated as LVIR_BOUNDS for LVM_GETSUBITEMRECT, but it doesn't matter because the label rect is uses isn't what we want anyway -+ // there's a hardocded 2-logical unit gap between the icon and text for subitems, AND the text starts being drawn (in the background) one bitmap margin to the right of that -+ // with normal items, there's no gap, and only the 2-logical unit gap after the background starts (TODO confirm this part) -+ // let's copy that to look nicer, even if it's not "accurate" -+ // TODO check against accessibility -+ dp->label = dp->bounds; -+ // because we want the 2 extra logical units to be included with the background, we don't include them here -+ dp->label.left = dp->icon.right; -+ return S_OK; -+} -+ - static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) - { - uiprivTableColumnParams *p; - uiTableData *data; - double r, g, b, a; -+ uiprivSubitemDrawParams dp; - LRESULT ret; - HRESULT hr; - -@@ -193,7 +253,12 @@ DrawTextW(nm->nmcd.hdc, L"Part", -1, - ret = CDRF_DODEFAULT; - } - -- hr = uiprivNM_CUSTOMDRAWImagesCheckboxes(t, nm, &ret); -+ ZeroMemory(&dp, sizeof (uiprivSubitemDrawParams)); -+ hr = fillSubitemDrawParams(t->hwnd, nm, &dp); -+ if (hr != S_OK) { -+ // TODO -+ } -+ hr = uiprivNM_CUSTOMDRAWImagesCheckboxes(t, nm, &dp, &ret); - if (hr != S_OK) { - // TODO - } -diff --git a/windows/table.hpp b/windows/table.hpp -index 0be01d8f..2f4f6936 100644 ---- a/windows/table.hpp -+++ b/windows/table.hpp -@@ -43,8 +43,14 @@ struct uiTable { - // custom draw state - COLORREF clrItemText; - }; -+typedef struct uiprivSubitemDrawParams uiprivSubitemDrawParams; -+struct uiprivSubitemDrawParams { -+ RECT bounds; -+ RECT icon; -+ RECT label; -+}; - - // tableimages.cpp - extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); --extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult); -+extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp, LRESULT *lResult); - extern HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t); -diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp -index a42307b1..14bae8c8 100644 ---- a/windows/tableimages.cpp -+++ b/windows/tableimages.cpp -@@ -139,12 +139,11 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip - // however, there seems to be no way to do this natively, so we have to draw over ourselves (TODO?) - // hopefully the performance won't be too bad - // see also https://www.codeproject.com/Articles/79/Neat-Stuff-to-Do-in-List-Controls-Using-Custom-Dra --HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult) -+HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp, LRESULT *lResult) - { - uiprivTableColumnParams *p; - int index; - RECT r; -- RECT cellRect; - LONG yoff; - - if (nm->nmcd.dwDrawStage == (CDDS_SUBITEM | CDDS_ITEMPREPAINT)) { -@@ -161,22 +160,9 @@ HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, LRES - - index = checkboxIndex(t->model, nm->nmcd.dwItemSpec, - p->checkboxModelColumn, p->checkboxEditableColumn); -- ZeroMemory(&r, sizeof (RECT)); -- r.left = LVIR_ICON; -- r.top = nm->iSubItem; -- if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { -- logLastError(L"LVM_GETSUBITEMRECT"); -- return E_FAIL; -- } -+ r = dp->icon; - // the real listview also does this :| -- ZeroMemory(&cellRect, sizeof (RECT)); -- r.left = LVIR_BOUNDS; -- r.top = nm->iSubItem; -- if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&cellRect)) == 0) { -- logLastError(L"LVM_GETSUBITEMRECT cell"); -- return E_FAIL; -- } -- yoff = ((cellRect.bottom - cellRect.top) - (r.bottom - r.top)) / 2; -+ yoff = ((dp->bounds.bottom - dp->bounds.top) - (r.bottom - r.top)) / 2; - r.top += yoff; - r.bottom += yoff; - if ((nm->nmcd.dwItemSpec%2)==0) From f28c97d4d62425f744328272c08293139bcd3f98 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 12 Jun 2018 08:17:31 -0400 Subject: [PATCH 083/166] Switched checkboxes from postpaint to prepaint and turned off drawing everything while we slowly transition everything over. --- windows/table.cpp | 4 ++-- windows/table.hpp | 2 +- windows/tableimages.cpp | 13 ++++--------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 9689a023..7a16e02c 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -236,7 +236,7 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) } } // TODO draw background on image columns if needed - ret = CDRF_NEWFONT; + ret = CDRF_SKIPDEFAULT | CDRF_NEWFONT; break; case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT: if(nm->iSubItem == 1) { @@ -260,7 +260,7 @@ if ((nm->nmcd.dwDrawStage & CDDS_SUBITEM) == 0)return ret; if (hr != S_OK) { // TODO } - hr = uiprivNM_CUSTOMDRAWImagesCheckboxes(t, nm, &dp, &ret); + hr = uiprivNM_CUSTOMDRAWImagesCheckboxes(t, nm, &dp); if (hr != S_OK) { // TODO } diff --git a/windows/table.hpp b/windows/table.hpp index 4820eaad..514af352 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -52,5 +52,5 @@ struct uiprivSubitemDrawParams { // tableimages.cpp extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); -extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp, LRESULT *lResult); +extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp); extern HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t); diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index 8bc563ac..278a88a5 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -136,10 +136,9 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip } // in order to properly look like checkboxes, we need to exclude them from being colored in by the selection rect -// however, there seems to be no way to do this natively, so we have to draw over ourselves (TODO?) -// hopefully the performance won't be too bad -// see also https://www.codeproject.com/Articles/79/Neat-Stuff-to-Do-in-List-Controls-Using-Custom-Dra -HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp, LRESULT *lResult) +// however, there seems to be no way to do this natively, so we have to draw the icons ourselves +// see also https://www.codeproject.com/Articles/79/Neat-Stuff-to-Do-in-List-Controls-Using-Custom-Dra (and while this uses postpaint to draw over the existing icon, we are drawing everything ourselves, so we only draw once) +HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp) { uiprivTableColumnParams *p; int index; @@ -147,11 +146,7 @@ HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uipr int cxIcon, cyIcon; LONG yoff; - if (nm->nmcd.dwDrawStage == (CDDS_SUBITEM | CDDS_ITEMPREPAINT)) { - *lResult |= CDRF_NOTIFYPOSTPAINT; - return S_OK; - } - if (nm->nmcd.dwDrawStage != (CDDS_SUBITEM | CDDS_ITEMPOSTPAINT)) + if (nm->nmcd.dwDrawStage != (CDDS_SUBITEM | CDDS_ITEMPREPAINT)) return S_OK; // only draw over checkboxes From f216af94e747467db4bd856142702dcdcad97083 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 12 Jun 2018 08:20:15 -0400 Subject: [PATCH 084/166] Made our new custom draw code only run on item prepaint. --- windows/table.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 7a16e02c..6d5da0d7 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -208,8 +208,7 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) switch (nm->nmcd.dwDrawStage) { case CDDS_PREPAINT: - ret = CDRF_NOTIFYITEMDRAW; - break; + return CDRF_NOTIFYITEMDRAW; case CDDS_ITEMPREPAINT: if (t->backgroundColumn != -1) { data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, t->backgroundColumn); @@ -251,7 +250,7 @@ r.left = r2.right + 2; DrawTextW(nm->nmcd.hdc, L"Part", -1, &r, DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL);} default: - ret = CDRF_DODEFAULT; + return CDRF_DODEFAULT; } if ((nm->nmcd.dwDrawStage & CDDS_SUBITEM) == 0)return ret; From c4251894b5870498d6596d28d88d5c86353c7233 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 12 Jun 2018 20:15:59 -0400 Subject: [PATCH 085/166] Flipped the memory corruption back on so we can debug it. --- windows/table.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 6d5da0d7..c573f97c 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -145,7 +145,7 @@ static HRESULT fillSubitemDrawParams(HWND hwnd, NMLVCUSTOMDRAW *nm, uiprivSubite // note that we can't just copy nm->nmcd.rc into p->bounds because that is only defined during prepaint stages // TODO this corrupts memory - if (nm->iSubItem == 0) {return S_OK; + if (nm->iSubItem == 0) { ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_BOUNDS; if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { @@ -219,7 +219,7 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) } } t->clrItemText = nm->clrText; - ret = CDRF_NEWFONT | CDRF_NOTIFYSUBITEMDRAW; + ret = CDRF_NOTIFYSUBITEMDRAW; break; case CDDS_SUBITEM | CDDS_ITEMPREPAINT: p = (*(t->columns))[nm->iSubItem]; @@ -235,10 +235,10 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) } } // TODO draw background on image columns if needed - ret = CDRF_SKIPDEFAULT | CDRF_NEWFONT; + ret = /*CDRF_SKIPDEFAULT | */CDRF_NEWFONT; break; -case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT: -if(nm->iSubItem == 1) { +//case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT: +if(0){//nm->iSubItem == 1) { RECT r, r2; r.left = LVIR_LABEL; r.top = 1; @@ -253,7 +253,6 @@ DrawTextW(nm->nmcd.hdc, L"Part", -1, return CDRF_DODEFAULT; } -if ((nm->nmcd.dwDrawStage & CDDS_SUBITEM) == 0)return ret; ZeroMemory(&dp, sizeof (uiprivSubitemDrawParams)); hr = fillSubitemDrawParams(t->hwnd, nm, &dp); if (hr != S_OK) { From c01b010fd76421f7f498870e44d32f5a1c17027d Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 13 Jun 2018 08:30:00 -0400 Subject: [PATCH 086/166] Fixed memory corruption issues. See code for details. --- windows/table.cpp | 37 ++++++------------------------------- windows/table.hpp | 5 ----- windows/winapi.hpp | 1 - 3 files changed, 6 insertions(+), 37 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index c573f97c..26b8b406 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -81,28 +81,20 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) static uiprivTableColumnParams *p; uiTableData *data; WCHAR *wstr; - HDC dc; - IWICBitmap *wb; - HBITMAP b; - int checked; - bool queueUpdated = false; HRESULT hr; - wstr = t->dispinfoStrings->front(); - if (wstr != NULL) - uiprivFree(wstr); - t->dispinfoStrings->pop(); - p = (*(t->columns))[nm->item.iSubItem]; -nm->item.pszText=L"abcdefg"; if ((nm->item.mask & LVIF_TEXT) != 0) if (p->textModelColumn != -1) { data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); wstr = toUTF16(uiTableDataString(data)); uiFreeTableData(data); - nm->item.pszText = wstr; - t->dispinfoStrings->push(wstr); - queueUpdated = true; + // we could just make pszText into a freshly allocated conversion and avoid the limitation of cchTextMax + // but then we would have to keep things around for some amount of time (some pages on MSDN say 2 additional LVN_GETDISPINFO messages) + // and in practice, anything that results in extra LVN_GETDISPINFO messages (such as fillSubitemDrawParams() below) will break this counting + // TODO make it so we don't have to make a copy; instead we can convert directly into pszText (this will also avoid the risk of having a dangling surrogate pair at the end) + wcscpy_s(nm->item.pszText, nm->item.cchTextMax, wstr); + uiprivFree(wstr); } hr = uiprivLVN_GETDISPINFOImagesCheckboxes(t, nm, p); @@ -110,9 +102,6 @@ nm->item.pszText=L"abcdefg"; // TODO } - // we don't want to pop from an empty queue, so if nothing updated the queue (no info was filled in above), just push NULL - if (!queueUpdated) - t->dispinfoStrings->push(NULL); return 0; } @@ -285,7 +274,6 @@ static void uiTableDestroy(uiControl *c) uiTable *t = uiTable(c); uiTableModel *model = t->model; std::vector::iterator it; - WCHAR *wstr; uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); uiWindowsEnsureDestroyWindow(t->hwnd); @@ -296,14 +284,6 @@ static void uiTableDestroy(uiControl *c) break; } } - // empty the string queue - while (t->dispinfoStrings->size() != 0) { - wstr = t->dispinfoStrings->front(); - if (wstr != NULL) - uiprivFree(wstr); - t->dispinfoStrings->pop(); - } - delete t->dispinfoStrings; // free the columns for (auto col : *(t->columns)) uiprivFree(col); @@ -464,11 +444,6 @@ uiTable *uiNewTable(uiTableModel *model) t->backgroundColumn = -1; - t->dispinfoStrings = new std::queue; - // this encodes the idea that two LVN_GETDISPINFOs must complete before we can free a string: the first real one is for the fourth call to free - for (i = 0; i < uiprivNumLVN_GETDISPINFOSkip; i++) - t->dispinfoStrings->push(NULL); - hr = uiprivTableSetupImagesCheckboxes(t); if (hr != S_OK) { // TODO diff --git a/windows/table.hpp b/windows/table.hpp index 514af352..b1b53c66 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -30,11 +30,6 @@ struct uiTable { WPARAM nColumns; int backgroundColumn; - // owner data state - // MSDN says we have to keep LVN_GETDISPINFO strings we allocate around at least until "two additional LVN_GETDISPINFO messages have been sent". - // we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue - std::queue *dispinfoStrings; - // tableimages.cpp // TODO make sure what we're doing is even allowed HIMAGELIST smallImages; diff --git a/windows/winapi.hpp b/windows/winapi.hpp index 297726e3..19426844 100644 --- a/windows/winapi.hpp +++ b/windows/winapi.hpp @@ -60,5 +60,4 @@ #include #include #include -#include #endif From 15bc55dd5e747cf6d332a98f23f284f0d97e07ff Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 13 Jun 2018 10:09:13 -0400 Subject: [PATCH 087/166] Started work on backgrounds. This isn't ideal quite yet, but. --- windows/table.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 26b8b406..93b3c3e0 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -133,7 +133,6 @@ static HRESULT fillSubitemDrawParams(HWND hwnd, NMLVCUSTOMDRAW *nm, uiprivSubite // note that we can't just copy nm->nmcd.rc into p->bounds because that is only defined during prepaint stages - // TODO this corrupts memory if (nm->iSubItem == 0) { ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_BOUNDS; @@ -207,8 +206,30 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) nm->clrTextBk = blend(nm->clrTextBk, r, g, b, a); } } + { + LRESULT state; + HBRUSH b; + bool freeBrush = false; + + // note: nm->nmcd.uItemState CDIS_SELECTED is unreliable for the listview configuration we have + state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, nm->nmcd.dwItemSpec, LVIS_SELECTED); + if ((state & LVIS_SELECTED) != 0) + b = GetSysColorBrush(COLOR_HIGHLIGHT); + else if (nm->clrTextBk != CLR_DEFAULT) { + b = CreateSolidBrush(nm->clrTextBk); + if (b == NULL) + logLastError(L"CreateSolidBrush()"); + freeBrush = true; + } else + b = GetSysColorBrush(COLOR_WINDOW); + // TODO check error + FillRect(nm->nmcd.hdc, &(nm->nmcd.rc), b); + if (freeBrush) + if (DeleteObject(b) == 0) + logLastError(L"DeleteObject()"); + } t->clrItemText = nm->clrText; - ret = CDRF_NOTIFYSUBITEMDRAW; + ret = CDRF_NEWFONT | CDRF_NOTIFYSUBITEMDRAW; break; case CDDS_SUBITEM | CDDS_ITEMPREPAINT: p = (*(t->columns))[nm->iSubItem]; From caa06886874896503c822673f16407a407c426ae Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 13 Jun 2018 21:06:08 -0400 Subject: [PATCH 088/166] Started drawing uiTable text. We're drawing fully manually from here on out. Also fixed LVIF_INDENT settings and a few other things. --- windows/CMakeLists.txt | 1 + windows/table.cpp | 72 ++++++++++++++++----------------- windows/table.hpp | 8 +++- windows/tableimages.cpp | 20 +++++----- windows/tabletext.cpp | 88 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 139 insertions(+), 50 deletions(-) create mode 100644 windows/tabletext.cpp diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 4cee5d68..a896e700 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -52,6 +52,7 @@ list(APPEND _LIBUI_SOURCES windows/tab.cpp windows/table.cpp windows/tableimages.cpp + windows/tabletext.cpp windows/tabpage.cpp windows/text.cpp windows/utf16.cpp diff --git a/windows/table.cpp b/windows/table.cpp index 93b3c3e0..d3cd97f7 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -79,24 +79,13 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) { static uiprivTableColumnParams *p; - uiTableData *data; - WCHAR *wstr; HRESULT hr; p = (*(t->columns))[nm->item.iSubItem]; - if ((nm->item.mask & LVIF_TEXT) != 0) - if (p->textModelColumn != -1) { - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); - wstr = toUTF16(uiTableDataString(data)); - uiFreeTableData(data); - // we could just make pszText into a freshly allocated conversion and avoid the limitation of cchTextMax - // but then we would have to keep things around for some amount of time (some pages on MSDN say 2 additional LVN_GETDISPINFO messages) - // and in practice, anything that results in extra LVN_GETDISPINFO messages (such as fillSubitemDrawParams() below) will break this counting - // TODO make it so we don't have to make a copy; instead we can convert directly into pszText (this will also avoid the risk of having a dangling surrogate pair at the end) - wcscpy_s(nm->item.pszText, nm->item.cchTextMax, wstr); - uiprivFree(wstr); - } - + hr = uiprivLVN_GETDISPINFOText(t, nm, p); + if (hr != S_OK) { + // TODO + } hr = uiprivLVN_GETDISPINFOImagesCheckboxes(t, nm, p); if (hr != S_OK) { // TODO @@ -126,31 +115,47 @@ static COLORREF blend(COLORREF base, double r, double g, double b, double a) (BYTE) (bb * 255)); } -static HRESULT fillSubitemDrawParams(HWND hwnd, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp) +COLORREF uiprivTableBlendedColorFromModel(uiTable *t, NMLVCUSTOMDRAW *nm, int modelColumn, int fallbackSysColorID) { + uiTableData *data; + double r, g, b, a; + + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, modelColumn); + if (data == NULL) + return GetSysColor(fallbackSysColorID); + uiTableDataColor(data, &r, &g, &b, &a); + uiFreeTableData(data); + return blend(nm->clrTextBk, r, g, b, a); +} + +static HRESULT fillSubitemDrawParams(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp) +{ + LRESULT state; RECT r; HRESULT hr; - // note that we can't just copy nm->nmcd.rc into p->bounds because that is only defined during prepaint stages + // note: nm->nmcd.uItemState CDIS_SELECTED is unreliable for the listview configuration we have + state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, nm->nmcd.dwItemSpec, LVIS_SELECTED); + dp->selected = (state & LVIS_SELECTED) != 0; if (nm->iSubItem == 0) { ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_BOUNDS; - if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { + if (SendMessageW(t->hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { logLastError(L"LVM_GETITEMRECT LVIR_BOUNDS"); return E_FAIL; } dp->bounds = r; ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_ICON; - if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { + if (SendMessageW(t->hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { logLastError(L"LVM_GETITEMRECT LVIR_ICON"); return E_FAIL; } dp->icon = r; ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_LABEL; - if (SendMessageW(hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { + if (SendMessageW(t->hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { logLastError(L"LVM_GETITEMRECT LVIR_LABEL"); return E_FAIL; } @@ -161,7 +166,7 @@ static HRESULT fillSubitemDrawParams(HWND hwnd, NMLVCUSTOMDRAW *nm, uiprivSubite ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_BOUNDS; r.top = nm->iSubItem; - if (SendMessageW(hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { + if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { logLastError(L"LVM_GETSUBITEMRECT LVIR_BOUNDS"); return E_FAIL; } @@ -169,7 +174,7 @@ static HRESULT fillSubitemDrawParams(HWND hwnd, NMLVCUSTOMDRAW *nm, uiprivSubite ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_ICON; r.top = nm->iSubItem; - if (SendMessageW(hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { + if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { logLastError(L"LVM_GETSUBITEMRECT LVIR_ICON"); return E_FAIL; } @@ -229,8 +234,7 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) logLastError(L"DeleteObject()"); } t->clrItemText = nm->clrText; - ret = CDRF_NEWFONT | CDRF_NOTIFYSUBITEMDRAW; - break; + return CDRF_NEWFONT | CDRF_NOTIFYSUBITEMDRAW; case CDDS_SUBITEM | CDDS_ITEMPREPAINT: p = (*(t->columns))[nm->iSubItem]; // TODO none of this runs on the first item @@ -245,26 +249,14 @@ static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) } } // TODO draw background on image columns if needed - ret = /*CDRF_SKIPDEFAULT | */CDRF_NEWFONT; + ret = CDRF_SKIPDEFAULT | CDRF_NEWFONT; break; -//case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT: -if(0){//nm->iSubItem == 1) { -RECT r, r2; -r.left = LVIR_LABEL; -r.top = 1; -SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM)(&r)); -r2.left = LVIR_ICON; -r2.top = 1; -SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM)(&r2)); -r.left = r2.right + 2; -DrawTextW(nm->nmcd.hdc, L"Part", -1, -&r, DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL);} default: return CDRF_DODEFAULT; } ZeroMemory(&dp, sizeof (uiprivSubitemDrawParams)); - hr = fillSubitemDrawParams(t->hwnd, nm, &dp); + hr = fillSubitemDrawParams(t, nm, &dp); if (hr != S_OK) { // TODO } @@ -272,6 +264,10 @@ DrawTextW(nm->nmcd.hdc, L"Part", -1, if (hr != S_OK) { // TODO } + hr = uiprivNM_CUSTOMDRAWText(t, nm, p, &dp); + if (hr != S_OK) { + // TODO + } return ret; } diff --git a/windows/table.hpp b/windows/table.hpp index b1b53c66..8849a833 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -40,11 +40,17 @@ struct uiTable { }; typedef struct uiprivSubitemDrawParams uiprivSubitemDrawParams; struct uiprivSubitemDrawParams { + bool selected; RECT bounds; RECT icon; RECT label; }; - +extern COLORREF uiprivTableBlendedColorFromModel(uiTable *t, NMLVCUSTOMDRAW *nm, int modelColumn, int fallbackSysColorID); + +// tabletext.cpp +extern HRESULT uiprivLVN_GETDISPINFOText(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); +extern HRESULT uiprivNM_CUSTOMDRAWText(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColumnParams *p, uiprivSubitemDrawParams *dp); + // tableimages.cpp extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp); diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index 278a88a5..69a23bb5 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -95,8 +95,14 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip uiTableData *data; HRESULT hr; + if (nm->item.iSubItem == 0 && p->imageModelColumn == -1 && p->checkboxModelColumn == -1) { + // having an image list always leaves space for an image on the main item :| + // other places on the internet imply that you should be able to do this but that it shouldn't work + // but it works perfectly (and pixel-perfectly too) for me, so... + nm->item.mask |= LVIF_INDENT; + nm->item.iIndent = -1; + } if ((nm->item.mask & LVIF_IMAGE) == 0) - // TODO we actually need to do the first column fix here too... return S_OK; // nothing to do here if (p->imageModelColumn != -1) { @@ -122,16 +128,8 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip return S_OK; } - // if we got here, there's no image in this cell - nm->item.mask &= ~LVIF_IMAGE; - // having an image list always leaves space for an image on the main item :| - // other places on the internet imply that you should be able to do this but that it shouldn't work - // but it works perfectly (and pixel-perfectly too) for me, so... - // TODO it doesn't work anymore... - if (nm->item.iSubItem == 0) { - nm->item.mask |= LVIF_INDENT; - nm->item.iIndent = -1; - } + // TODO see if this is correct + nm->item.iImage = -1; return S_OK; } diff --git a/windows/tabletext.cpp b/windows/tabletext.cpp new file mode 100644 index 00000000..9208553d --- /dev/null +++ b/windows/tabletext.cpp @@ -0,0 +1,88 @@ +// 13 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +// This file handles text in tables. + +HRESULT uiprivLVN_GETDISPINFOText(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) +{ + uiTableData *data; + WCHAR *wstr; + HRESULT hr; + + if ((nm->item.mask & LVIF_TEXT) == 0) + return S_OK; + if (p->textModelColumn != -1) + return S_OK; + + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); + wstr = toUTF16(uiTableDataString(data)); + uiFreeTableData(data); + // we could just make pszText into a freshly allocated conversion and avoid the limitation of cchTextMax + // but then we would have to keep things around for some amount of time (some pages on MSDN say 2 additional LVN_GETDISPINFO messages) + // and in practice, anything that results in extra LVN_GETDISPINFO messages (such as fillSubitemDrawParams() below) will break this counting + // TODO make it so we don't have to make a copy; instead we can convert directly into pszText (this will also avoid the risk of having a dangling surrogate pair at the end) + wcsncpy(nm->item.pszText, wstr, nm->item.cchTextMax); + nm->item.pszText[nm->item.cchTextMax - 1] = L'\0'; + uiprivFree(wstr); + return S_OK; +} + +HRESULT uiprivNM_CUSTOMDRAWText(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColumnParams *p, uiprivSubitemDrawParams *dp) +{ + COLORREF color; + COLORREF prev; + int prevMode; + RECT r; + uiTableData *data; + WCHAR *wstr; + + if (p->textModelColumn == -1) + return S_OK; + + if (dp->selected) + color = GetSysColor(COLOR_HIGHLIGHTTEXT); + else if (p->textParams.ColorModelColumn != -1) + color = uiprivTableBlendedColorFromModel(t, nm, p->textParams.ColorModelColumn, COLOR_WINDOWTEXT); + else + color = GetSysColor(COLOR_WINDOWTEXT); + prev = SetTextColor(nm->nmcd.hdc, color); + if (prev == CLR_INVALID) { + logLastError(L"SetTextColor()"); + return E_FAIL; + } + prevMode = SetBkMode(nm->nmcd.hdc, TRANSPARENT); + if (prevMode == 0) { + logLastError(L"SetBkMode()"); + return E_FAIL; + } + + // text is actually drawn two logical units to the right of the beginning of the text rect + // TODO confirm this for the first column on both image and imageless cases + // TODO actually this whole thing is wrong for imageless columns + r = dp->label; + r.left += 2; + + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, p->textModelColumn); + wstr = toUTF16(uiTableDataString(data)); + uiFreeTableData(data); + // these flags are a menagerie of flags from various sources: guessing, the Windows 2000 source leak, various custom draw examples on the web, etc. + // TODO find the real correct flags + if (DrawTextW(nm->nmcd.hdc, wstr, -1, &r, DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL) == 0) { + uiprivFree(wstr); + logLastError(L"DrawTextW()"); + return E_FAIL; + } + uiprivFree(wstr); + + // TODO decide once and for all what to compare to here and with SelectObject() + if (SetBkMode(nm->nmcd.hdc, prevMode) != TRANSPARENT) { + logLastError(L"SetBkMode() prev"); + return E_FAIL; + } + if (SetTextColor(nm->nmcd.hdc, prev) != color) { + logLastError(L"SetTextColor() prev"); + return E_FAIL; + } + return S_OK; +} From c79f9b4ecd40f0588e6bf6b9e9915bc8216fd808 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 13 Jun 2018 21:46:28 -0400 Subject: [PATCH 089/166] Added bitmap margins to the draw parameters. We'll need it later. --- windows/table.cpp | 4 ++++ windows/table.hpp | 1 + 2 files changed, 5 insertions(+) diff --git a/windows/table.cpp b/windows/table.cpp index d3cd97f7..ea3257de 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -131,6 +131,7 @@ COLORREF uiprivTableBlendedColorFromModel(uiTable *t, NMLVCUSTOMDRAW *nm, int mo static HRESULT fillSubitemDrawParams(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp) { LRESULT state; + HWND header; RECT r; HRESULT hr; @@ -138,6 +139,9 @@ static HRESULT fillSubitemDrawParams(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubit state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, nm->nmcd.dwItemSpec, LVIS_SELECTED); dp->selected = (state & LVIS_SELECTED) != 0; + header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); + dp->bitmapMargin = SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); + if (nm->iSubItem == 0) { ZeroMemory(&r, sizeof (RECT)); r.left = LVIR_BOUNDS; diff --git a/windows/table.hpp b/windows/table.hpp index 8849a833..69b453be 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -41,6 +41,7 @@ struct uiTable { typedef struct uiprivSubitemDrawParams uiprivSubitemDrawParams; struct uiprivSubitemDrawParams { bool selected; + LRESULT bitmapMargin; RECT bounds; RECT icon; RECT label; From 75063ec266c0eae3dc49f88a1f2e23ea1ef5269a Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Thu, 14 Jun 2018 20:26:51 -0400 Subject: [PATCH 090/166] Fixed text positioning in non-image columns. This separation probably isn't working... --- windows/tabletext.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/windows/tabletext.cpp b/windows/tabletext.cpp index 9208553d..04a38b5f 100644 --- a/windows/tabletext.cpp +++ b/windows/tabletext.cpp @@ -57,11 +57,14 @@ HRESULT uiprivNM_CUSTOMDRAWText(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColum return E_FAIL; } - // text is actually drawn two logical units to the right of the beginning of the text rect - // TODO confirm this for the first column on both image and imageless cases - // TODO actually this whole thing is wrong for imageless columns r = dp->label; - r.left += 2; + if (p->imageModelColumn != -1 || p->checkboxModelColumn != -1) + // text is actually drawn two logical units to the right of the beginning of the text rect + // TODO confirm this for the first column on both image and imageless cases + // TODO actually this whole thing is wrong for imageless columns + r.left += 2; + else + r.left = dp->bounds.left + dp->bitmapMargin; data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, p->textModelColumn); wstr = toUTF16(uiTableDataString(data)); From fda8f2fbae32645fc52af8319cfea4b93da4f47b Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Thu, 14 Jun 2018 21:31:45 -0400 Subject: [PATCH 091/166] Started a separate file just for drawing tables; integrated text. --- windows/table.cpp | 66 ----------- windows/tabledraw.cpp | 251 ++++++++++++++++++++++++++++++++++++++++++ windows/tabletext.cpp | 62 ----------- 3 files changed, 251 insertions(+), 128 deletions(-) create mode 100644 windows/tabledraw.cpp diff --git a/windows/table.cpp b/windows/table.cpp index ea3257de..19bacd4b 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -128,72 +128,6 @@ COLORREF uiprivTableBlendedColorFromModel(uiTable *t, NMLVCUSTOMDRAW *nm, int mo return blend(nm->clrTextBk, r, g, b, a); } -static HRESULT fillSubitemDrawParams(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp) -{ - LRESULT state; - HWND header; - RECT r; - HRESULT hr; - - // note: nm->nmcd.uItemState CDIS_SELECTED is unreliable for the listview configuration we have - state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, nm->nmcd.dwItemSpec, LVIS_SELECTED); - dp->selected = (state & LVIS_SELECTED) != 0; - - header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); - dp->bitmapMargin = SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); - - if (nm->iSubItem == 0) { - ZeroMemory(&r, sizeof (RECT)); - r.left = LVIR_BOUNDS; - if (SendMessageW(t->hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { - logLastError(L"LVM_GETITEMRECT LVIR_BOUNDS"); - return E_FAIL; - } - dp->bounds = r; - ZeroMemory(&r, sizeof (RECT)); - r.left = LVIR_ICON; - if (SendMessageW(t->hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { - logLastError(L"LVM_GETITEMRECT LVIR_ICON"); - return E_FAIL; - } - dp->icon = r; - ZeroMemory(&r, sizeof (RECT)); - r.left = LVIR_LABEL; - if (SendMessageW(t->hwnd, LVM_GETITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == FALSE) { - logLastError(L"LVM_GETITEMRECT LVIR_LABEL"); - return E_FAIL; - } - dp->label = r; - return S_OK; - } - - ZeroMemory(&r, sizeof (RECT)); - r.left = LVIR_BOUNDS; - r.top = nm->iSubItem; - if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { - logLastError(L"LVM_GETSUBITEMRECT LVIR_BOUNDS"); - return E_FAIL; - } - dp->bounds = r; - ZeroMemory(&r, sizeof (RECT)); - r.left = LVIR_ICON; - r.top = nm->iSubItem; - if (SendMessageW(t->hwnd, LVM_GETSUBITEMRECT, nm->nmcd.dwItemSpec, (LPARAM) (&r)) == 0) { - logLastError(L"LVM_GETSUBITEMRECT LVIR_ICON"); - return E_FAIL; - } - dp->icon = r; - // LVIR_LABEL is treated as LVIR_BOUNDS for LVM_GETSUBITEMRECT, but it doesn't matter because the label rect is uses isn't what we want anyway - // there's a hardocded 2-logical unit gap between the icon and text for subitems, AND the text starts being drawn (in the background) one bitmap margin to the right of that - // with normal items, there's no gap, and only the 2-logical unit gap after the background starts (TODO confirm this part) - // let's copy that to look nicer, even if it's not "accurate" - // TODO check against accessibility - dp->label = dp->bounds; - // because we want the 2 extra logical units to be included with the background, we don't include them here - dp->label.left = dp->icon.right; - return S_OK; -} - static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) { uiprivTableColumnParams *p; diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp new file mode 100644 index 00000000..3036ac30 --- /dev/null +++ b/windows/tabledraw.cpp @@ -0,0 +1,251 @@ +// 14 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +struct drawState { + uiTable *t; + uiTableModel *m; + uiprivTableColumnParams *p; + + HDC dc; + int iItem; + int iSubItem; + BOOL hasText; + BOOL hasImage; + BOOL selected; + BOOL focused; + + RECT itemBounds; + RECT itemIcon; + RECT itemText; + RECT subitemBounds; + RECT subitemIcon; + RECT subitemText; + + COLORREF bgColor; + HBRUSH bgBrush; + BOOL freeBgBrush; + COLORREF textColor; + HBRUSH textBrush; + BOOL freeTextBrush; + + LRESULT bitmapMargins; + int cxIcon; + int cyIcon; + + RECT realTextRect; + RECT focusRect; +}; + +static HRESULT computeAndDrawTextRect(struct drawState *s) +{ + RECT r; + + r = s->subitemText; + if (!s->hasText && !s->hasImage) + r = s->subitemBounds; + + if (FillRect(s->dc, &r, s->bgBrush) == 0) { + logLastError(L"FillRect()"); + return E_FAIL; + } + UnionRect(&(s->focusRect), &(s->focusRect), &r); + + s->realTextRect = r; + // TODO confirm whether this really happens on column 0 as well + if (s->hasImage && s->iSubItem != 0) + // Normally there's this many hard-coded logical units + // of blank space, followed by the background, followed + // by a bitmap margin's worth of space. This looks bad, + // so we overrule that to start the background immediately + // and the text after the hard-coded amount. + s->realTextRect.left += 2; + else if (s->iSubItem != 0) + // In the case of subitem text without an image, we draw + // text one bitmap margin away from the left edge. + s->realTextRect.left += s->bitmapMargin; + return S_OK; +} + +static HRESULT drawTextPart(struct drawState *s) +{ + COLORREF prevText; + int prevMode; + RECT r; + uiTableData *data; + WCHAR *wstr; + + if (!s->hasText) + return S_OK; + + prevText = SetTextColor(s->dc, s->textColor); + if (prevText == CLR_INVALID) { + logLastError(L"SetTextColor()"); + return E_FAIL; + } + prevMode = SetBkMode(s->dc, TRANSPARENT); + if (prevMode == 0) { + logLastError(L"SetBkMode()"); + return E_FAIL; + } + + data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->textModelColumn); + wstr = toUTF16(uiTableDataString(data)); + uiFreeTableData(data); + // These flags are a menagerie of flags from various sources: + // guessing, the Windows 2000 source leak, various custom + // draw examples on the web, etc. + // TODO find the real correct flags + if (DrawTextW(s->dc, wstr, -1, &(s->realTextRect), DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL) == 0) { + uiprivFree(wstr); + logLastError(L"DrawTextW()"); + return E_FAIL; + } + uiprivFree(wstr); + + // TODO decide once and for all what to compare to here and with SelectObject() + if (SetBkMode(nm->nmcd.hdc, prevMode) != TRANSPARENT) { + logLastError(L"SetBkMode() prev"); + return E_FAIL; + } + if (SetTextColor(nm->nmcd.hdc, prevText) != color) { + logLastError(L"SetTextColor() prev"); + return E_FAIL; + } + return S_OK; +} + +static HRESULT freeDrawState(struct drawState *s) +{ + HRESULT hr, hrret; + + hrret = S_OK; + if (p->freeTextBrush) { + if (DeleteObject(p->textBrush) == 0) { + logLastError(L"DeleteObject()"); + hrret = E_FAIL; + // continue cleaning up anyway + } + p->freeTextBrush = NO; + } + if (p->freeBgBrush) { + if (DeleteObject(p->bgBrush) == 0) { + logLastError(L"DeleteObject()"); + hrret = E_FAIL; + // continue cleaning up anyway + } + p->freeBgBrush = NO; + } + return hrret; +} + +static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG left, LONG top, LRESULT bad, RECT *r) +{ + if (hr != S_OK) + return hr; + ZeroMemory(r, sizeof (RECT)); + r->left = left; + r->top = top; + if (SendMessageW(t->hwnd, uMsg, wParam, (LPARAM) r) == bad) { + logLastError(L"itemRect() message"); + return E_FAIL; + } + return S_OK; +} + +static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColumnParams *p) +{ + LRESULT state; + HWND header; + HRESULT hr; + + ZeroMemory(s, sizeof (struct drawState)); + s->t = t; + s->m = t->model; + s->p = p; + + s->dc = nm->nmcd.hdc; + s->iItem = nm->nmcd.dwItemSpec; + s->iSubItem = nm->iSubItem; + s->hasText = p->textModelColumn != -1; + s->hasImage = (p->imageModelColumn != -1) || (p->checkboxModelColumn != -1); + // nm->nmcd.uItemState CDIS_SELECTED is unreliable for the + // listview configuration we have, so we must do this. + state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, nm->nmcd.dwItemSpec, LVIS_SELECTED); + dp->selected = (state & LVIS_SELECTED) != 0; + dp->focused = (nm->nmcd.uiTemState & CDIS_FOCUSED) != 0; + + hr = itemRect(S_OK, t, LVM_GETITEMRECT, LVIR_BOUNDS, + 0, &(s->itemBounds)); + hr = itemRect(hr, t, LVM_GETITEMRECT, LVIR_ICON, + 0, &(s->itemIcon)); + hr = itemRect(hr, t, LVM_GETITEMRECT, LVIR_LABEL, + 0, &(s->itemLabel)); + hr = itemRect(hr, t, LVM_GETSUBITEMRECT, LVIR_BOUNDS, + s->iSubItem, &(s->subitemBounds)); + hr = itemRect(hr, t, LVM_GETSUBITEMRECT, LVIR_ICON, + s->iSubItem, &(s->subitemIcon)); + if (hr != S_OK) + goto fail; + // LVM_GETSUBITEMRECT treats LVIR_LABEL as the same as + // LVIR_BOUNDS, so we can't use that directly. Instead, let's + // assume the text is immediately after the icon. The correct + // rect will be determined by computeAndDrawTextRect() + // above. + s->subitemLabel = s->subitemBounds; + s->subitemLabel.left = s->subitemIcon.right; + + if (s->selected) { + s->bgColor = GetSysColor(COLOR_HIGHLIGHT); + s->bgBrush = GetSysColorBrush(COLOR_HIGHLIGHT); + s->textColor = GetSysColor(COLOR_HIGHLIGHTTEXT); + s->textBrush = GetSysColorBrush(COLOR_HIGHLIGHTTEXT); + } else { + uiTableData *data; + + s->bgColor = GetSysColor(COLOR_WINDOW); + s->bgBrush = GetSysColorBrush(COLOR_WINDOW); + if (t->backgroundColumn != -1) { + data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, t->backgroundColumn); + if (data != NULL) { + uiTableDataColor(data, &r, &g, &b, &a); + uiFreeTableData(data); + s->bgColor = blend(s->bgColor, r, g, b, a); + s->bgBrush = CreateSolidBrush(s->bgBrush); + if (s->bgBrush == NULL) { + logLastError(L"CreateSolidBrush()"); + hr = E_FAIL; + goto fail; + } + s->freeBgBrush = TRUE; + } + } + s->textColor = GetSysColor(COLOR_WINDOWTEXT); + s->textBrush = GetSysColorBrush(COLOR_WINDOWTEXT); + if (p->textParams.ColorModelColumn != -1) { + data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, p->textParams.ColorModelColumn); + if (data != NULL) { + uiTableDataColor(data, &r, &g, &b, &a); + uiFreeTableData(data); + s->textColor = blend(s->bgColor, r, g, b, a); + s->textBrush = CreateSolidBrush(s->textColor); + if (s->textBrush == NULL) { + logLastError(L"CreateSolidBrush()"); + hr = E_FAIL; + goto fail; + } + s->freeTextBrush = TRUE; + } + } + } + + header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); + s->bitmapMargin = SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); + // TODO + + return S_OK; +fail: + // ignore the error; we need to return the one we got above + freeDrawState(s); + return hr; +} diff --git a/windows/tabletext.cpp b/windows/tabletext.cpp index 04a38b5f..d1feb336 100644 --- a/windows/tabletext.cpp +++ b/windows/tabletext.cpp @@ -27,65 +27,3 @@ HRESULT uiprivLVN_GETDISPINFOText(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColu uiprivFree(wstr); return S_OK; } - -HRESULT uiprivNM_CUSTOMDRAWText(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColumnParams *p, uiprivSubitemDrawParams *dp) -{ - COLORREF color; - COLORREF prev; - int prevMode; - RECT r; - uiTableData *data; - WCHAR *wstr; - - if (p->textModelColumn == -1) - return S_OK; - - if (dp->selected) - color = GetSysColor(COLOR_HIGHLIGHTTEXT); - else if (p->textParams.ColorModelColumn != -1) - color = uiprivTableBlendedColorFromModel(t, nm, p->textParams.ColorModelColumn, COLOR_WINDOWTEXT); - else - color = GetSysColor(COLOR_WINDOWTEXT); - prev = SetTextColor(nm->nmcd.hdc, color); - if (prev == CLR_INVALID) { - logLastError(L"SetTextColor()"); - return E_FAIL; - } - prevMode = SetBkMode(nm->nmcd.hdc, TRANSPARENT); - if (prevMode == 0) { - logLastError(L"SetBkMode()"); - return E_FAIL; - } - - r = dp->label; - if (p->imageModelColumn != -1 || p->checkboxModelColumn != -1) - // text is actually drawn two logical units to the right of the beginning of the text rect - // TODO confirm this for the first column on both image and imageless cases - // TODO actually this whole thing is wrong for imageless columns - r.left += 2; - else - r.left = dp->bounds.left + dp->bitmapMargin; - - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, p->textModelColumn); - wstr = toUTF16(uiTableDataString(data)); - uiFreeTableData(data); - // these flags are a menagerie of flags from various sources: guessing, the Windows 2000 source leak, various custom draw examples on the web, etc. - // TODO find the real correct flags - if (DrawTextW(nm->nmcd.hdc, wstr, -1, &r, DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL) == 0) { - uiprivFree(wstr); - logLastError(L"DrawTextW()"); - return E_FAIL; - } - uiprivFree(wstr); - - // TODO decide once and for all what to compare to here and with SelectObject() - if (SetBkMode(nm->nmcd.hdc, prevMode) != TRANSPARENT) { - logLastError(L"SetBkMode() prev"); - return E_FAIL; - } - if (SetTextColor(nm->nmcd.hdc, prev) != color) { - logLastError(L"SetTextColor() prev"); - return E_FAIL; - } - return S_OK; -} From e6da33121edeb4d0cf56ad4ea3df3317b59dcbf8 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 15 Jun 2018 10:04:32 -0400 Subject: [PATCH 092/166] And integrated tabledraw.cpp. It works, barring some technical gltiches. It also makes me realize the alpha blending issue was my fault... --- windows/CMakeLists.txt | 1 + windows/table.cpp | 123 +++-------------------------------------- windows/table.hpp | 5 +- windows/tabledraw.cpp | 115 +++++++++++++++++++++++++++++--------- 4 files changed, 101 insertions(+), 143 deletions(-) diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index a896e700..d074ca87 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -51,6 +51,7 @@ list(APPEND _LIBUI_SOURCES windows/stddialogs.cpp windows/tab.cpp windows/table.cpp + windows/tabledraw.cpp windows/tableimages.cpp windows/tabletext.cpp windows/tabpage.cpp diff --git a/windows/table.cpp b/windows/table.cpp index 19bacd4b..a766010f 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -78,6 +78,7 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) { + // TODO remove static static uiprivTableColumnParams *p; HRESULT hr; @@ -94,131 +95,21 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) return 0; } -static COLORREF blend(COLORREF base, double r, double g, double b, double a) -{ - double br, bg, bb; - - // TODO find a better fix than this - // TODO s listview already alphablending? - // TODO find the right color here - if (base == CLR_DEFAULT) - base = GetSysColor(COLOR_WINDOW); - br = ((double) GetRValue(base)) / 255.0; - bg = ((double) GetGValue(base)) / 255.0; - bb = ((double) GetBValue(base)) / 255.0; - - br = (r * a) + (br * (1.0 - a)); - bg = (g * a) + (bg * (1.0 - a)); - bb = (b * a) + (bb * (1.0 - a)); - return RGB((BYTE) (br * 255), - (BYTE) (bg * 255), - (BYTE) (bb * 255)); -} - -COLORREF uiprivTableBlendedColorFromModel(uiTable *t, NMLVCUSTOMDRAW *nm, int modelColumn, int fallbackSysColorID) -{ - uiTableData *data; - double r, g, b, a; - - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, modelColumn); - if (data == NULL) - return GetSysColor(fallbackSysColorID); - uiTableDataColor(data, &r, &g, &b, &a); - uiFreeTableData(data); - return blend(nm->clrTextBk, r, g, b, a); -} - -static LRESULT onNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm) -{ - uiprivTableColumnParams *p; - uiTableData *data; - double r, g, b, a; - uiprivSubitemDrawParams dp; - LRESULT ret; - HRESULT hr; - - switch (nm->nmcd.dwDrawStage) { - case CDDS_PREPAINT: - return CDRF_NOTIFYITEMDRAW; - case CDDS_ITEMPREPAINT: - if (t->backgroundColumn != -1) { - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, t->backgroundColumn); - if (data != NULL) { - uiTableDataColor(data, &r, &g, &b, &a); - uiFreeTableData(data); - nm->clrTextBk = blend(nm->clrTextBk, r, g, b, a); - } - } - { - LRESULT state; - HBRUSH b; - bool freeBrush = false; - - // note: nm->nmcd.uItemState CDIS_SELECTED is unreliable for the listview configuration we have - state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, nm->nmcd.dwItemSpec, LVIS_SELECTED); - if ((state & LVIS_SELECTED) != 0) - b = GetSysColorBrush(COLOR_HIGHLIGHT); - else if (nm->clrTextBk != CLR_DEFAULT) { - b = CreateSolidBrush(nm->clrTextBk); - if (b == NULL) - logLastError(L"CreateSolidBrush()"); - freeBrush = true; - } else - b = GetSysColorBrush(COLOR_WINDOW); - // TODO check error - FillRect(nm->nmcd.hdc, &(nm->nmcd.rc), b); - if (freeBrush) - if (DeleteObject(b) == 0) - logLastError(L"DeleteObject()"); - } - t->clrItemText = nm->clrText; - return CDRF_NEWFONT | CDRF_NOTIFYSUBITEMDRAW; - case CDDS_SUBITEM | CDDS_ITEMPREPAINT: - p = (*(t->columns))[nm->iSubItem]; - // TODO none of this runs on the first item - // we need this as previous subitems will persist their colors - nm->clrText = t->clrItemText; - if (p->textParams.ColorModelColumn != -1) { - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->nmcd.dwItemSpec, p->textParams.ColorModelColumn); - if (data != NULL) { - uiTableDataColor(data, &r, &g, &b, &a); - uiFreeTableData(data); - nm->clrText = blend(nm->clrTextBk, r, g, b, a); - } - } - // TODO draw background on image columns if needed - ret = CDRF_SKIPDEFAULT | CDRF_NEWFONT; - break; - default: - return CDRF_DODEFAULT; - } - - ZeroMemory(&dp, sizeof (uiprivSubitemDrawParams)); - hr = fillSubitemDrawParams(t, nm, &dp); - if (hr != S_OK) { - // TODO - } - hr = uiprivNM_CUSTOMDRAWImagesCheckboxes(t, nm, &dp); - if (hr != S_OK) { - // TODO - } - hr = uiprivNM_CUSTOMDRAWText(t, nm, p, &dp); - if (hr != S_OK) { - // TODO - } - return ret; -} - static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) { uiTable *t = uiTable(c); + HRESULT hr; switch (nmhdr->code) { case LVN_GETDISPINFO: *lResult = onLVN_GETDISPINFO(t, (NMLVDISPINFOW *) nmhdr); return TRUE; case NM_CUSTOMDRAW: - *lResult = onNM_CUSTOMDRAW(t, (NMLVCUSTOMDRAW *) nmhdr); + hr = uiprivTableHandleNM_CUSTOMDRAW(t, (NMLVCUSTOMDRAW *) nmhdr, lResult); + if (hr != S_OK) { + // TODO + return FALSE; + } return TRUE; } return FALSE; diff --git a/windows/table.hpp b/windows/table.hpp index 69b453be..3ceeb977 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -46,13 +46,14 @@ struct uiprivSubitemDrawParams { RECT icon; RECT label; }; -extern COLORREF uiprivTableBlendedColorFromModel(uiTable *t, NMLVCUSTOMDRAW *nm, int modelColumn, int fallbackSysColorID); // tabletext.cpp extern HRESULT uiprivLVN_GETDISPINFOText(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); -extern HRESULT uiprivNM_CUSTOMDRAWText(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColumnParams *p, uiprivSubitemDrawParams *dp); // tableimages.cpp extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp); extern HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t); + +// tabledraw.cpp +extern HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult); diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 3036ac30..3afa4370 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -17,10 +17,10 @@ struct drawState { RECT itemBounds; RECT itemIcon; - RECT itemText; + RECT itemLabel; RECT subitemBounds; RECT subitemIcon; - RECT subitemText; + RECT subitemLabel; COLORREF bgColor; HBRUSH bgBrush; @@ -29,7 +29,7 @@ struct drawState { HBRUSH textBrush; BOOL freeTextBrush; - LRESULT bitmapMargins; + LRESULT bitmapMargin; int cxIcon; int cyIcon; @@ -41,7 +41,7 @@ static HRESULT computeAndDrawTextRect(struct drawState *s) { RECT r; - r = s->subitemText; + r = s->subitemLabel; if (!s->hasText && !s->hasImage) r = s->subitemBounds; @@ -104,11 +104,11 @@ static HRESULT drawTextPart(struct drawState *s) uiprivFree(wstr); // TODO decide once and for all what to compare to here and with SelectObject() - if (SetBkMode(nm->nmcd.hdc, prevMode) != TRANSPARENT) { + if (SetBkMode(s->dc, prevMode) != TRANSPARENT) { logLastError(L"SetBkMode() prev"); return E_FAIL; } - if (SetTextColor(nm->nmcd.hdc, prevText) != color) { + if (SetTextColor(s->dc, prevText) != s->textColor) { logLastError(L"SetTextColor() prev"); return E_FAIL; } @@ -120,21 +120,21 @@ static HRESULT freeDrawState(struct drawState *s) HRESULT hr, hrret; hrret = S_OK; - if (p->freeTextBrush) { - if (DeleteObject(p->textBrush) == 0) { + if (s->freeTextBrush) { + if (DeleteObject(s->textBrush) == 0) { logLastError(L"DeleteObject()"); hrret = E_FAIL; // continue cleaning up anyway } - p->freeTextBrush = NO; + s->freeTextBrush = FALSE; } - if (p->freeBgBrush) { - if (DeleteObject(p->bgBrush) == 0) { + if (s->freeBgBrush) { + if (DeleteObject(s->bgBrush) == 0) { logLastError(L"DeleteObject()"); hrret = E_FAIL; // continue cleaning up anyway } - p->freeBgBrush = NO; + s->freeBgBrush = FALSE; } return hrret; } @@ -153,6 +153,22 @@ static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG l return S_OK; } +static COLORREF blend(COLORREF base, double r, double g, double b, double a) +{ + double br, bg, bb; + + br = ((double) GetRValue(base)) / 255.0; + bg = ((double) GetGValue(base)) / 255.0; + bb = ((double) GetBValue(base)) / 255.0; + + br = (r * a) + (br * (1.0 - a)); + bg = (g * a) + (bg * (1.0 - a)); + bb = (b * a) + (bb * (1.0 - a)); + return RGB((BYTE) (br * 255), + (BYTE) (bg * 255), + (BYTE) (bb * 255)); +} + static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColumnParams *p) { LRESULT state; @@ -172,19 +188,20 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm // nm->nmcd.uItemState CDIS_SELECTED is unreliable for the // listview configuration we have, so we must do this. state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, nm->nmcd.dwItemSpec, LVIS_SELECTED); - dp->selected = (state & LVIS_SELECTED) != 0; - dp->focused = (nm->nmcd.uiTemState & CDIS_FOCUSED) != 0; + s->selected = (state & LVIS_SELECTED) != 0; + s->focused = (nm->nmcd.uItemState & CDIS_FOCUS) != 0; - hr = itemRect(S_OK, t, LVM_GETITEMRECT, LVIR_BOUNDS, - 0, &(s->itemBounds)); - hr = itemRect(hr, t, LVM_GETITEMRECT, LVIR_ICON, - 0, &(s->itemIcon)); - hr = itemRect(hr, t, LVM_GETITEMRECT, LVIR_LABEL, - 0, &(s->itemLabel)); - hr = itemRect(hr, t, LVM_GETSUBITEMRECT, LVIR_BOUNDS, - s->iSubItem, &(s->subitemBounds)); - hr = itemRect(hr, t, LVM_GETSUBITEMRECT, LVIR_ICON, - s->iSubItem, &(s->subitemIcon)); + // TODO check LRESULT bad parameters here + hr = itemRect(S_OK, t, LVM_GETITEMRECT, s->iItem, + LVIR_BOUNDS, 0, FALSE, &(s->itemBounds)); + hr = itemRect(hr, t, LVM_GETITEMRECT, s->iItem, + LVIR_ICON, 0, FALSE, &(s->itemIcon)); + hr = itemRect(hr, t, LVM_GETITEMRECT, s->iItem, + LVIR_LABEL, 0, FALSE, &(s->itemLabel)); + hr = itemRect(hr, t, LVM_GETSUBITEMRECT, s->iItem, + LVIR_BOUNDS, s->iSubItem, 0, &(s->subitemBounds)); + hr = itemRect(hr, t, LVM_GETSUBITEMRECT, s->iItem, + LVIR_ICON, s->iSubItem, 0, &(s->subitemIcon)); if (hr != S_OK) goto fail; // LVM_GETSUBITEMRECT treats LVIR_LABEL as the same as @@ -194,6 +211,12 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm // above. s->subitemLabel = s->subitemBounds; s->subitemLabel.left = s->subitemIcon.right; + // And on iSubItem == 0, LVIF_GETSUBITEMRECT still includes + // all the subitems, which we don't want. + if (s->iSubItem == 0) { + s->subitemBounds.right = s->itemLabel.right; + s->subitemLabel.right = s->itemLabel.right; + } if (s->selected) { s->bgColor = GetSysColor(COLOR_HIGHLIGHT); @@ -202,6 +225,7 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm s->textBrush = GetSysColorBrush(COLOR_HIGHLIGHTTEXT); } else { uiTableData *data; + double r, g, b, a; s->bgColor = GetSysColor(COLOR_WINDOW); s->bgBrush = GetSysColorBrush(COLOR_WINDOW); @@ -211,7 +235,7 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm uiTableDataColor(data, &r, &g, &b, &a); uiFreeTableData(data); s->bgColor = blend(s->bgColor, r, g, b, a); - s->bgBrush = CreateSolidBrush(s->bgBrush); + s->bgBrush = CreateSolidBrush(s->bgColor); if (s->bgBrush == NULL) { logLastError(L"CreateSolidBrush()"); hr = E_FAIL; @@ -249,3 +273,44 @@ fail: freeDrawState(s); return hr; } + +HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult) +{ + struct drawState s; + uiprivTableColumnParams *p; + HRESULT hr; + + switch (nm->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + *lResult = CDRF_NOTIFYITEMDRAW; + return S_OK; + case CDDS_ITEMPREPAINT: + *lResult = CDRF_NOTIFYSUBITEMDRAW; + return S_OK; + case CDDS_SUBITEM | CDDS_ITEMPREPAINT: + break; + default: + *lResult = CDRF_DODEFAULT; + return S_OK; + } + + p = (*(t->columns))[nm->iSubItem]; + hr = fillDrawState(&s, t, nm, p); + if (hr != S_OK) + return hr; + hr = computeAndDrawTextRect(&s); + if (hr != S_OK) + goto fail; + hr = drawTextPart(&s); + if (hr != S_OK) + goto fail; + hr = freeDrawState(&s); + if (hr != S_OK) // TODO really error out here? + return hr; + *lResult = CDRF_SKIPDEFAULT; + return S_OK; +fail: + // ignore error here + freeDrawState(&s); + return hr; +} From 32ee36eb22a05acdedd65aa8bf94fad9e381e7a1 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 15 Jun 2018 19:29:01 -0400 Subject: [PATCH 093/166] Fixed some of the technical glitches. --- windows/tabledraw.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 3afa4370..3344f8a2 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -44,6 +44,11 @@ static HRESULT computeAndDrawTextRect(struct drawState *s) r = s->subitemLabel; if (!s->hasText && !s->hasImage) r = s->subitemBounds; + else if (!s->hasImage && s->iSubItem != 0) + // By default, this will include images; we're not drawing + // images, so we will manually draw over the image area. + // There's a second part to this; see below. + r.left = s->subitemBounds.left; if (FillRect(s->dc, &r, s->bgBrush) == 0) { logLastError(L"FillRect()"); From d6cebf4ca02c90a5eaf0bf9d137404fc49b0605f Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 15 Jun 2018 22:28:37 -0400 Subject: [PATCH 094/166] Added scaling to uiImage on Windows. We'll need this for what we're about to do. --- windows/image.cpp | 67 ++++++++++++++++++++++++++++++-------- windows/uipriv_windows.hpp | 2 +- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/windows/image.cpp b/windows/image.cpp index a85207f5..3beedb0e 100644 --- a/windows/image.cpp +++ b/windows/image.cpp @@ -120,10 +120,12 @@ IWICBitmap *uiprivImageAppropriateForDC(uiImage *i, HDC dc) return m.best; } -// TODO see if we can really pass NULL to CreateDIBSection()'s HDC parameter, and if so, use HBITMAPs before WIC maybe? -// TODO this needs to actually scale down to fit if the image size isn't perfectly equal to a requested size I need to pass as a parameter here... -HBITMAP uiprivWICToGDI(IWICBitmap *b, HDC dc) +// TODO this needs to center images if the given size is not the same aspect ratio +HRESULT uiprivWICToGDI(IWICBitmap *b, HDC dc, int width, int height, HBITMAP *hb) { + UINT ux, uy; + int x, y; + IWICImageSource *src; BITMAPINFO bmi; UINT width, height; HBITMAP hb; @@ -131,29 +133,66 @@ HBITMAP uiprivWICToGDI(IWICBitmap *b, HDC dc) BITMAP bmp; HRESULT hr; + hr = b->GetSize(&ux, &uy); + if (hr != S_OK) + return hr; + x = ux; + y = uy; + if (width == 0) + width = x; + if (height == 0) + height = y; + + // special case: don't invoke a scaler if the size is the same + if (width == x && height == y) { + b->AddRef(); // for the Release() later + src = b; + } else { + IWICBitmapScaler *scaler; + + hr = uiprivWICFactory->CreateBitmapScaler(&scaler); + if (hr != S_OK) + return hr; + hr = scaler->Initialize(b, width, height, + // according to https://stackoverflow.com/questions/4250738/is-stretchblt-halftone-bilinear-for-all-scaling, this is what StretchBlt(COLORONCOLOR) does (with COLORONCOLOR being what's supported by AlphaBlend()) + WICBitmapInterpolationModeNearestNeighbor); + if (hr != S_OK) { + scaler->Release(); + return hr; + } + src = scaler; + } + ZeroMemory(&bmi, sizeof (BITMAPINFO)); bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); - hr = b->GetSize(&width, &height); - if (hr != S_OK) - logHRESULT(L"error calling GetSize() in uiprivWICToGDI()", hr); bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -((int) height); bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; - hb = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, + *hb = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0); - if (hb == NULL) - logLastError(L"error calling CreateDIBSection() in uiprivWICToGDI()"); + if (*hb == NULL) { + logLastError(L"CreateDIBSection()"); + hr = E_FAIL; + goto fail; + } // now we need to figure out the stride of the image data GDI gave us - // TODO find out if CreateDIBSection fills that in bmi for us - if (GetObject(hb, sizeof (BITMAP), &bmp) == 0) + // TODO find out if CreateDIBSection() fills that in bmi for us + // TODO fill in the error returns here too + if (GetObject(*hb, sizeof (BITMAP), &bmp) == 0) logLastError(L"error calling GetObject() in uiprivWICToGDI()"); hr = b->CopyPixels(NULL, bmp.bmWidthBytes, bmp.bmWidthBytes * bmp.bmHeight, (BYTE *) bits); - if (hr != S_OK) - logHRESULT(L"error calling CopyPixels() in uiprivWICToGDI()", hr); - return hb; + hr = S_OK; +fail: + if (*hb != NULL && hr != S_OK) { + // don't bother with the error returned here + DeleteObject(*hb); + *hb = NULL; + } + src->Release(); + return hr; } diff --git a/windows/uipriv_windows.hpp b/windows/uipriv_windows.hpp index aab50aa7..a1c2bc29 100644 --- a/windows/uipriv_windows.hpp +++ b/windows/uipriv_windows.hpp @@ -169,4 +169,4 @@ extern IWICImagingFactory *uiprivWICFactory; extern HRESULT uiprivInitImage(void); extern void uiprivUninitImage(void); extern IWICBitmap *uiprivImageAppropriateForDC(uiImage *i, HDC dc); -extern HBITMAP uiprivWICToGDI(IWICBitmap *b, HDC dc); +extern HRESULT uiprivWICToGDI(IWICBitmap *b, HDC dc, int width, int height, HBITMAP *hb); From 43bb983f5bb95db98a1c6bc90124b2b2b722bafe Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 15 Jun 2018 22:50:19 -0400 Subject: [PATCH 095/166] Wrote new (incomplete) image drawing code. Now to build and test. --- windows/table.cpp | 2 +- windows/table.hpp | 2 +- windows/tabledraw.cpp | 129 ++++++++++++++++++++++++++++++++++++++-- windows/tableimages.cpp | 105 ++------------------------------ 4 files changed, 131 insertions(+), 107 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index a766010f..e1eb011d 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -290,7 +290,7 @@ uiTable *uiNewTable(uiTableModel *model) t->backgroundColumn = -1; - hr = uiprivTableSetupImagesCheckboxes(t); + hr = uiprivUpdateImageListSize(t); if (hr != S_OK) { // TODO } diff --git a/windows/table.hpp b/windows/table.hpp index 3ceeb977..056cb884 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -53,7 +53,7 @@ extern HRESULT uiprivLVN_GETDISPINFOText(uiTable *t, NMLVDISPINFOW *nm, uiprivTa // tableimages.cpp extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp); -extern HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t); // tabledraw.cpp extern HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult); +extern HRESULT uiprivUpdateImageListSize(uiTable *t); diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 3344f8a2..cb4a9fce 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -37,7 +37,7 @@ struct drawState { RECT focusRect; }; -static HRESULT computeAndDrawTextRect(struct drawState *s) +static HRESULT computeOtherRectsAndDrawBackgrounds(struct drawState *s) { RECT r; @@ -50,6 +50,11 @@ static HRESULT computeAndDrawTextRect(struct drawState *s) // There's a second part to this; see below. r.left = s->subitemBounds.left; + if (s->hasImage) + if (FillRect(s->dc, &(s->subitemIcon), GetSysColorBrush(COLOR_WINDOW)) == 0) { + logLastError(L"FillRect() icon"); + return E_FAIL; + } if (FillRect(s->dc, &r, s->bgBrush) == 0) { logLastError(L"FillRect()"); return E_FAIL; @@ -72,6 +77,52 @@ static HRESULT computeAndDrawTextRect(struct drawState *s) return S_OK; } +static HRESULT drawImagePart(struct drawState *s) +{ + uiTableData *data; + IWICBitmap *wb; + HBITMAP b; + UINT fStyle; + HRESULT hr; + + if (s->p->imageModelColumn == -1) + return S_OK; + + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->imageModelColumn); + wb = uiprivImageAppropriateForDC(uiTableDataImage(data), s->dc); + uiFreeTableData(data); + + hr = uiprivWICToGDI(wb, s->cxIcon, s->cyIcon, &b); + if (hr != S_OK) + return hr; + // TODO rewrite this condition to make more sense; possibly swap the if and else blocks too + // TODO proper cleanup + if (ImageList_GetImageCount(t->imagelist) > 1) { + if (ImageList_Replace(t->imagelist, 0, b, NULL) == 0) { + logLastError(L"ImageList_Replace()"); + return E_FAIL; + } + } else + if (ImageList_Add(t->imagelist, b, NULL) == -1) { + logLastError(L"ImageList_Add()"); + return E_FAIL; + } + // TODO error check + DeleteObject(b); + + fStyle = ILD_NORMAL; + if (s->selected) + fStyle = ILD_SELECTED; + // TODO copy the centering code from tableimage.cpp + if (ImageList_Draw(t->imagelist, 0, + s->dc, s->subitemIcon.left, s->subitemIcon.top, + fStyle) == 0) { + logLastError(L"ImageList_Draw()"); + return E_FAIL; + } + return S_OK; +} + static HRESULT drawTextPart(struct drawState *s) { COLORREF prevText; @@ -212,8 +263,8 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm // LVM_GETSUBITEMRECT treats LVIR_LABEL as the same as // LVIR_BOUNDS, so we can't use that directly. Instead, let's // assume the text is immediately after the icon. The correct - // rect will be determined by computeAndDrawTextRect() - // above. + // rect will be determined by + // computeOtherRectsAndDrawBackgrounds() above. s->subitemLabel = s->subitemBounds; s->subitemLabel.left = s->subitemIcon.right; // And on iSubItem == 0, LVIF_GETSUBITEMRECT still includes @@ -270,7 +321,11 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); s->bitmapMargin = SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); - // TODO + if (ImageList_GetIconSize(t->imagelist, &(s->cxIcon), &(s->cyIcon)) == 0) { + logLastError(L"ImageList_GetIconSize()"); + hr = E_FAIL; + goto fail; + } return S_OK; fail: @@ -303,7 +358,10 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * hr = fillDrawState(&s, t, nm, p); if (hr != S_OK) return hr; - hr = computeAndDrawTextRect(&s); + hr = computeOtherRectsAndDrawBackgrounds(&s); + if (hr != S_OK) + goto fail; + hr = drawImagePart(&s); if (hr != S_OK) goto fail; hr = drawTextPart(&s); @@ -319,3 +377,64 @@ fail: freeDrawState(&s); return hr; } + +// TODO run again when the DPI or the theme changes +// TODO properly clean things up here +// TODO properly destroy the old lists here too +HRESULT uiprivUpdateImageListSize(uiTable *t) +{ + HDC dc; + int cxList, cyList; + HTHEME theme; + SIZE sizeCheck; + HRESULT hr; + + dc = GetDC(t->hwnd); + if (dc == NULL) { + logLastError(L"GetDC()"); + return E_FAIL; + } + + cxList = GetSystemMetrics(SM_CXSMICON); + cyList = GetSystemMetrics(SM_CYSMICON); + sizeCheck.cx = cxList; + sizeCheck.cy = cyList; + theme = OpenThemeData(t->hwnd, L"button"); + if (theme != NULL) { + hr = GetThemePartSize(theme, dc, + BP_CHECKBOX, CBS_UNCHECKEDNORMAL, + NULL, TS_DRAW, &sizeCheck); + if (hr != S_OK) { + logHRESULT(L"GetThemePartSize()", hr); + return hr; // TODO fall back? + } + // make sure these checkmarks fit + // unthemed checkmarks will by the code above be smaller than cxList/cyList here + if (cxList < sizeCheck.cx) + cxList = sizeCheck.cx; + if (cyList < sizeCheck.cy) + cyList = sizeCheck.cy; + } + + // TODO handle errors + t->imagelist = ImageList_Create(cxList, cyList, + ILC_COLOR32, + 1, 1); + if (t->smallImages == NULL) { + logLastError(L"ImageList_Create()"); + return E_FAIL; + } + // TODO will this return NULL here because it's an initial state? + SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages)); + + hr = CloseThemeData(theme); + if (hr != S_OK) { + logHRESULT(L"CloseThemeData()", hr); + return hr; + } + if (ReleaseDC(t->hwnd, dc) == 0) { + logLastError(L"ReleaseDC()"); + return E_FAIL; + } + return S_OK; +} diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index 69a23bb5..fb97b596 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -21,44 +21,7 @@ We'll use the small image list. For this, the first few items will be reserved f #define nCheckboxImages 4 static HRESULT setCellImage(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p, uiTableData *data) -{ - int index; - HDC dc; - IWICBitmap *wb; - HBITMAP b; - - index = t->smallIndex + nCheckboxImages; - t->smallIndex++; - t->smallIndex %= uiprivNumLVN_GETDISPINFOSkip; - nm->item.iImage = index; - - dc = GetDC(t->hwnd); - if (dc == NULL) { - logLastError(L"GetDC()"); - return E_FAIL; - } - - wb = uiprivImageAppropriateForDC(uiTableDataImage(data), dc); - b = uiprivWICToGDI(wb, dc); - // TODO rewrite this condition to make more sense; possibly swap the if and else blocks too - if (ImageList_GetImageCount(t->smallImages) > index) { - if (ImageList_Replace(t->smallImages, index, b, NULL) == 0) { - logLastError(L"ImageList_Replace()"); - return E_FAIL; - } - } else - if (ImageList_Add(t->smallImages, b, NULL) == -1) { - logLastError(L"ImageList_Add()"); - return E_FAIL; - } - - if (ReleaseDC(t->hwnd, dc) == 0) { - logLastError(L"ReleaseDC()"); - return E_FAIL; - } - - return S_OK; -} +{return E_NOTIMPL;/*TODO*/} #define stateUnchecked 0 #define stateChecked 1 @@ -105,6 +68,10 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip if ((nm->item.mask & LVIF_IMAGE) == 0) return S_OK; // nothing to do here + // TODO + nm->item.iImage = 0; + return S_OK; + if (p->imageModelColumn != -1) { data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->imageModelColumn); hr = setCellImage(t, nm, p, data); @@ -297,65 +264,3 @@ static HRESULT mkCheckboxes(uiTable *t, HTHEME theme, HDC dc, int cxList, int cy DeleteObject(b); return S_OK; } - -// TODO run again when the DPI changes -HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t) -{ - HDC dc; - int cxList, cyList; - HTHEME theme; - SIZE sizeCheck; - HRESULT hr; - - dc = GetDC(t->hwnd); - if (dc == NULL) { - logLastError(L"GetDC()"); - return E_FAIL; - } - - cxList = GetSystemMetrics(SM_CXSMICON); - cyList = GetSystemMetrics(SM_CYSMICON); - sizeCheck.cx = cxList; - sizeCheck.cy = cyList; - theme = OpenThemeData(t->hwnd, L"button"); - if (theme != NULL) { - hr = GetThemePartSize(theme, dc, - BP_CHECKBOX, CBS_UNCHECKEDNORMAL, - NULL, TS_DRAW, &sizeCheck); - if (hr != S_OK) { - logHRESULT(L"GetThemePartSize()", hr); - return hr; // TODO fall back? - } - // make sure these checkmarks fit - // unthemed checkmarks will by the code above be smaller than cxList/cyList here - if (cxList < sizeCheck.cx) - cxList = sizeCheck.cx; - if (cyList < sizeCheck.cy) - cyList = sizeCheck.cy; - } - - // TODO handle errors - t->smallImages = ImageList_Create(cxList, cyList, - ILC_COLOR32, - nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip, nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip); - if (t->smallImages == NULL) { - logLastError(L"ImageList_Create()"); - return E_FAIL; - } - // TODO will this return NULL here because it's an initial state? - SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages)); - hr = mkCheckboxes(t, theme, dc, cxList, cyList, sizeCheck.cx, sizeCheck.cy); - if (hr != S_OK) - return hr; - - hr = CloseThemeData(theme); - if (hr != S_OK) { - logHRESULT(L"CloseThemeData()", hr); - return hr; - } - if (ReleaseDC(t->hwnd, dc) == 0) { - logLastError(L"ReleaseDC()"); - return E_FAIL; - } - return S_OK; -} From f852359acbee65e49427632d2eb94a09e50e55e6 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 15 Jun 2018 23:00:39 -0400 Subject: [PATCH 096/166] Fixed build errors. The image list selection part works, at least!! Let's figure out why nothing else does. --- windows/image.cpp | 4 +--- windows/table.hpp | 10 ++-------- windows/tabledraw.cpp | 16 ++++++++-------- windows/tableimages.cpp | 8 +++++++- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/windows/image.cpp b/windows/image.cpp index 3beedb0e..d94de789 100644 --- a/windows/image.cpp +++ b/windows/image.cpp @@ -125,10 +125,8 @@ HRESULT uiprivWICToGDI(IWICBitmap *b, HDC dc, int width, int height, HBITMAP *hb { UINT ux, uy; int x, y; - IWICImageSource *src; + IWICBitmapSource *src; BITMAPINFO bmi; - UINT width, height; - HBITMAP hb; VOID *bits; BITMAP bmp; HRESULT hr; diff --git a/windows/table.hpp b/windows/table.hpp index 056cb884..4e33d70c 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -29,14 +29,8 @@ struct uiTable { std::vector *columns; WPARAM nColumns; int backgroundColumn; - - // tableimages.cpp - // TODO make sure what we're doing is even allowed - HIMAGELIST smallImages; - int smallIndex; - - // custom draw state - COLORREF clrItemText; + // TODO make sure replacing images while selected in the listview is even allowed + HIMAGELIST imagelist; }; typedef struct uiprivSubitemDrawParams uiprivSubitemDrawParams; struct uiprivSubitemDrawParams { diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index cb4a9fce..62cd3123 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -88,22 +88,22 @@ static HRESULT drawImagePart(struct drawState *s) if (s->p->imageModelColumn == -1) return S_OK; - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->imageModelColumn); + data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->imageModelColumn); wb = uiprivImageAppropriateForDC(uiTableDataImage(data), s->dc); uiFreeTableData(data); - hr = uiprivWICToGDI(wb, s->cxIcon, s->cyIcon, &b); + hr = uiprivWICToGDI(wb, s->dc, s->cxIcon, s->cyIcon, &b); if (hr != S_OK) return hr; // TODO rewrite this condition to make more sense; possibly swap the if and else blocks too // TODO proper cleanup - if (ImageList_GetImageCount(t->imagelist) > 1) { - if (ImageList_Replace(t->imagelist, 0, b, NULL) == 0) { + if (ImageList_GetImageCount(s->t->imagelist) > 1) { + if (ImageList_Replace(s->t->imagelist, 0, b, NULL) == 0) { logLastError(L"ImageList_Replace()"); return E_FAIL; } } else - if (ImageList_Add(t->imagelist, b, NULL) == -1) { + if (ImageList_Add(s->t->imagelist, b, NULL) == -1) { logLastError(L"ImageList_Add()"); return E_FAIL; } @@ -114,7 +114,7 @@ static HRESULT drawImagePart(struct drawState *s) if (s->selected) fStyle = ILD_SELECTED; // TODO copy the centering code from tableimage.cpp - if (ImageList_Draw(t->imagelist, 0, + if (ImageList_Draw(s->t->imagelist, 0, s->dc, s->subitemIcon.left, s->subitemIcon.top, fStyle) == 0) { logLastError(L"ImageList_Draw()"); @@ -420,12 +420,12 @@ HRESULT uiprivUpdateImageListSize(uiTable *t) t->imagelist = ImageList_Create(cxList, cyList, ILC_COLOR32, 1, 1); - if (t->smallImages == NULL) { + if (t->imagelist == NULL) { logLastError(L"ImageList_Create()"); return E_FAIL; } // TODO will this return NULL here because it's an initial state? - SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages)); + SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->imagelist)); hr = CloseThemeData(theme); if (hr != S_OK) { diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index fb97b596..b7fed3bd 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -69,7 +69,9 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip return S_OK; // nothing to do here // TODO - nm->item.iImage = 0; + nm->item.iImage = -1; + if (p->imageModelColumn != -1 || p->checkboxModelColumn != -1) + nm->item.iImage = 0; return S_OK; if (p->imageModelColumn != -1) { @@ -100,6 +102,8 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip return S_OK; } +#if 0 + // in order to properly look like checkboxes, we need to exclude them from being colored in by the selection rect // however, there seems to be no way to do this natively, so we have to draw the icons ourselves // see also https://www.codeproject.com/Articles/79/Neat-Stuff-to-Do-in-List-Controls-Using-Custom-Dra (and while this uses postpaint to draw over the existing icon, we are drawing everything ourselves, so we only draw once) @@ -264,3 +268,5 @@ static HRESULT mkCheckboxes(uiTable *t, HTHEME theme, HDC dc, int cxList, int cy DeleteObject(b); return S_OK; } + +#endif From f1341a04856b81edf8c9a2b2e717c3916e28433b Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 04:26:36 -0400 Subject: [PATCH 097/166] Fixed image drawing. Still need to figure out why it's using the wrong size... --- windows/image.cpp | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/windows/image.cpp b/windows/image.cpp index d94de789..2997b092 100644 --- a/windows/image.cpp +++ b/windows/image.cpp @@ -147,6 +147,8 @@ HRESULT uiprivWICToGDI(IWICBitmap *b, HDC dc, int width, int height, HBITMAP *hb src = b; } else { IWICBitmapScaler *scaler; + WICPixelFormatGUID guid; + IWICFormatConverter *conv; hr = uiprivWICFactory->CreateBitmapScaler(&scaler); if (hr != S_OK) @@ -158,7 +160,35 @@ HRESULT uiprivWICToGDI(IWICBitmap *b, HDC dc, int width, int height, HBITMAP *hb scaler->Release(); return hr; } - src = scaler; + + // But we are not done yet! IWICBitmapScaler can use an + // entirely different pixel format than what we gave it, + // and by extension, what GDI wants. See also: + // - https://stackoverflow.com/questions/28323228/iwicbitmapscaler-doesnt-work-for-96bpprgbfloat-format + // - https://github.com/Microsoft/DirectXTex/blob/0d94e9469bc3e6080a71145f35efa559f8f2e522/DirectXTex/DirectXTexResize.cpp#L83 + hr = scaler->GetPixelFormat(&guid); + if (hr != S_OK) { + scaler->Release(); + return hr; + } + if (IsEqualGUID(guid, GUID_WICPixelFormat32bppRGBA)) + src = scaler; + else { + hr = uiprivWICFactory->CreateFormatConverter(&conv); + if (hr != S_OK) { + scaler->Release(); + return hr; + } + hr = conv->Initialize(scaler, GUID_WICPixelFormat32bppRGBA, + // TODO is the dither type correct in all cases? + WICBitmapDitherTypeNone, NULL, 0, WICBitmapPaletteTypeMedianCut); + scaler->Release(); + if (hr != S_OK) { + conv->Release(); + return hr; + } + src = conv; + } } ZeroMemory(&bmi, sizeof (BITMAPINFO)); @@ -181,10 +211,9 @@ HRESULT uiprivWICToGDI(IWICBitmap *b, HDC dc, int width, int height, HBITMAP *hb // TODO fill in the error returns here too if (GetObject(*hb, sizeof (BITMAP), &bmp) == 0) logLastError(L"error calling GetObject() in uiprivWICToGDI()"); - hr = b->CopyPixels(NULL, bmp.bmWidthBytes, + hr = src->CopyPixels(NULL, bmp.bmWidthBytes, bmp.bmWidthBytes * bmp.bmHeight, (BYTE *) bits); - hr = S_OK; fail: if (*hb != NULL && hr != S_OK) { // don't bother with the error returned here From f92c83992e61e69d29c2bd58eb8cc65ebe5a1e54 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 08:52:55 -0400 Subject: [PATCH 098/166] Fixed image matching. --- windows/image.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/windows/image.cpp b/windows/image.cpp index 2997b092..0058aa13 100644 --- a/windows/image.cpp +++ b/windows/image.cpp @@ -112,8 +112,9 @@ IWICBitmap *uiprivImageAppropriateForDC(uiImage *i, HDC dc) m.best = NULL; m.distX = INT_MAX; m.distY = INT_MAX; - m.targetX = i->width * GetDeviceCaps(dc, LOGPIXELSX); - m.targetY = i->height * GetDeviceCaps(dc, LOGPIXELSY); + // TODO explain this + m.targetX = MulDiv(i->width, GetDeviceCaps(dc, LOGPIXELSX), 96); + m.targetY = MulDiv(i->height, GetDeviceCaps(dc, LOGPIXELSY), 96); m.foundLarger = false; for (IWICBitmap *b : *(i->bitmaps)) match(b, &m); From 4bfd950caa2265feb682b96d764dc89ce30b9629 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 10:22:41 -0400 Subject: [PATCH 099/166] Centered the table image. --- windows/tabledraw.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 62cd3123..6887bb54 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -77,11 +77,21 @@ static HRESULT computeOtherRectsAndDrawBackgrounds(struct drawState *s) return S_OK; } +static void centerImageRect(RECT *image, RECT *space) +{ + LONG yoff; + + yoff = ((space->bottom - space->top) - (image->bottom - image->top)) / 2; + image->top += yoff; + image->bottom += yoff; +} + static HRESULT drawImagePart(struct drawState *s) { uiTableData *data; IWICBitmap *wb; HBITMAP b; + RECT r; UINT fStyle; HRESULT hr; @@ -110,13 +120,15 @@ static HRESULT drawImagePart(struct drawState *s) // TODO error check DeleteObject(b); + r = s->subitemIcon; + r.right = r.left + s->cxIcon; + r.bottom = r.top + s->cyIcon; + centerImageRect(&r, &(s->subitemIcon)); fStyle = ILD_NORMAL; if (s->selected) fStyle = ILD_SELECTED; - // TODO copy the centering code from tableimage.cpp if (ImageList_Draw(s->t->imagelist, 0, - s->dc, s->subitemIcon.left, s->subitemIcon.top, - fStyle) == 0) { + s->dc, r.left, r.top, fStyle) == 0) { logLastError(L"ImageList_Draw()"); return E_FAIL; } From b9289c93a6dd7ec3aade7641adafc02ce1d515e6 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 11:59:17 -0400 Subject: [PATCH 100/166] And drew checkboxes. We can FINALLY move on to other data types! ...almost. First we have to consolidate LVN_GETDISPINFO handlers. --- windows/tabledraw.cpp | 142 +++++++++++++++++++++++++-- windows/tableimages.cpp | 208 +--------------------------------------- 2 files changed, 138 insertions(+), 212 deletions(-) diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 6887bb54..ad8b4b46 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -79,10 +79,22 @@ static HRESULT computeOtherRectsAndDrawBackgrounds(struct drawState *s) static void centerImageRect(RECT *image, RECT *space) { - LONG yoff; + LONG xoff, yoff; + // first make sure both have the same upper-left + xoff = image->left - space->left; + yoff = image->top - space->top; + image->left -= xoff; + image->top -= yoff; + image->right -= xoff; + image->bottom -= yoff; + + // now center + xoff = ((space->right - space->left) - (image->right - image->left)) / 2; yoff = ((space->bottom - space->top) - (image->bottom - image->top)) / 2; + image->left += xoff; image->top += yoff; + image->right += xoff; image->bottom += yoff; } @@ -135,6 +147,121 @@ static HRESULT drawImagePart(struct drawState *s) return S_OK; } +// references for checkbox drawing: +// - https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485 +// - https://blogs.msdn.microsoft.com/oldnewthing/20171201-00/?p=97505 + +static HRESULT drawUnthemedCheckbox(struct drawState *s, int checked, int enabled) +{ + RECT r; + UINT state; + + r = s->subitemIcon; + // this is what the actual list view LVS_EX_CHECKBOXES code does to size the checkboxes + // TODO reverify the initial size + r.right = r.left + GetSystemMetrics(SM_CXSMICON); + r.bottom = r.top + GetSystemMetrics(SM_CYSMICON); + if (InflateRect(&r, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE)) == 0) { + logLastError(L"InflateRect()"); + return E_FAIL; + } + r.right++; + r.bottom++; + + centerImageRect(&r, &(s->subitemIcon)); + state = DFCS_BUTTONCHECK | DFCS_FLAT; + if (checked) + state |= DFCS_CHECKED; + if (!enabled) + state |= DFCS_INACTIVE; + if (DrawFrameControl(s->dc, &r, DFC_BUTTON, state) == 0) { + logLastError(L"DrawFrameControl()"); + return E_FAIL; + } + return S_OK; +} + +static HRESULT drawThemedCheckbox(struct drawState *s, HTHEME theme, int checked, int enabled) +{ + RECT r; + SIZE size; + int state; + HRESULT hr; + + hr = GetThemePartSize(theme, s->dc, + BP_CHECKBOX, CBS_UNCHECKEDNORMAL, + NULL, TS_DRAW, &size); + if (hr != S_OK) { + logHRESULT(L"GetThemePartSize()", hr); + return hr; // TODO fall back? + } + r = s->subitemIcon; + r.right = r.left + size.cx; + r.bottom = r.top + size.cy; + + centerImageRect(&r, &(s->subitemIcon)); + if (!checked && enabled) + state = CBS_UNCHECKEDNORMAL; + else if (checked && enabled) + state = CBS_CHECKEDNORMAL; + else if (!checked && !enabled) + state = CBS_UNCHECKEDDISABLED; + else + state = CBS_CHECKEDDISABLED; + hr = DrawThemeBackground(theme, s->dc, + BP_CHECKBOX, state, + &r, NULL); + if (hr != S_OK) { + logHRESULT(L"DrawThemeBackground()", hr); + return hr; + } + return S_OK; +} + +static HRESULT drawCheckboxPart(struct drawState *s) +{ + uiTableData *data; + int checked, enabled; + HTHEME theme; + HRESULT hr; + + if (s->p->checkboxModelColumn == -1) + return S_OK; + + data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->checkboxModelColumn); + checked = uiTableDataInt(data); + uiFreeTableData(data); + switch (s->p->checkboxEditableColumn) { + case uiTableModelColumnNeverEditable: + enabled = 0; + break; + case uiTableModelColumnAlwaysEditable: + enabled = 1; + break; + default: + data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->checkboxEditableColumn); + enabled = uiTableDataInt(data); + uiFreeTableData(data); + } + + theme = OpenThemeData(s->t->hwnd, L"button"); + if (theme != NULL) { + hr = drawThemedCheckbox(s, theme, checked, enabled); + if (hr != S_OK) + return hr; + hr = CloseThemeData(theme); + if (hr != S_OK) { + logHRESULT(L"CloseThemeData()", hr); + return hr; + } + } else { + hr = drawUnthemedCheckbox(s, checked, enabled); + if (hr != S_OK) + return hr; + } + return S_OK; +} + static HRESULT drawTextPart(struct drawState *s) { COLORREF prevText; @@ -374,6 +501,9 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * if (hr != S_OK) goto fail; hr = drawImagePart(&s); + if (hr != S_OK) + goto fail; + hr = drawCheckboxPart(&s); if (hr != S_OK) goto fail; hr = drawTextPart(&s); @@ -426,6 +556,11 @@ HRESULT uiprivUpdateImageListSize(uiTable *t) cxList = sizeCheck.cx; if (cyList < sizeCheck.cy) cyList = sizeCheck.cy; + hr = CloseThemeData(theme); + if (hr != S_OK) { + logHRESULT(L"CloseThemeData()", hr); + return hr; + } } // TODO handle errors @@ -439,11 +574,6 @@ HRESULT uiprivUpdateImageListSize(uiTable *t) // TODO will this return NULL here because it's an initial state? SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->imagelist)); - hr = CloseThemeData(theme); - if (hr != S_OK) { - logHRESULT(L"CloseThemeData()", hr); - return hr; - } if (ReleaseDC(t->hwnd, dc) == 0) { logLastError(L"ReleaseDC()"); return E_FAIL; diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index b7fed3bd..7f163ec0 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -20,39 +20,6 @@ We'll use the small image list. For this, the first few items will be reserved f #define nCheckboxImages 4 -static HRESULT setCellImage(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p, uiTableData *data) -{return E_NOTIMPL;/*TODO*/} - -#define stateUnchecked 0 -#define stateChecked 1 -#define stateDisabled 2 - -static int checkboxIndex(uiTableModel *m, int row, int checkboxModelColumn, int checkboxEditableColumn) -{ - uiTableData *data; - int ret; - - ret = stateUnchecked; - data = (*(m->mh->CellValue))(m->mh, m, row, checkboxModelColumn); - if (uiTableDataInt(data) != 0) - ret = stateChecked; - uiFreeTableData(data); - - switch (checkboxEditableColumn) { - case uiTableModelColumnNeverEditable: - ret += stateDisabled; - break; - case uiTableModelColumnAlwaysEditable: - break; - default: - data = (*(m->mh->CellValue))(m->mh, m, row, checkboxEditableColumn); - if (uiTableDataInt(data) != 0) - ret += stateDisabled; - } - - return ret; -} - HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) { uiTableData *data; @@ -75,10 +42,8 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip return S_OK; if (p->imageModelColumn != -1) { - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->imageModelColumn); - hr = setCellImage(t, nm, p, data); - uiFreeTableData(data); - return hr; + nm->item.iImage = 0; + return S_OK; } if (p->checkboxModelColumn != -1) { @@ -101,172 +66,3 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip nm->item.iImage = -1; return S_OK; } - -#if 0 - -// in order to properly look like checkboxes, we need to exclude them from being colored in by the selection rect -// however, there seems to be no way to do this natively, so we have to draw the icons ourselves -// see also https://www.codeproject.com/Articles/79/Neat-Stuff-to-Do-in-List-Controls-Using-Custom-Dra (and while this uses postpaint to draw over the existing icon, we are drawing everything ourselves, so we only draw once) -HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp) -{ - uiprivTableColumnParams *p; - int index; - RECT r; - int cxIcon, cyIcon; - LONG yoff; - - if (nm->nmcd.dwDrawStage != (CDDS_SUBITEM | CDDS_ITEMPREPAINT)) - return S_OK; - - // only draw over checkboxes - p = (*(t->columns))[nm->iSubItem]; - if (p->checkboxModelColumn == -1) - return S_OK; - - index = checkboxIndex(t->model, nm->nmcd.dwItemSpec, - p->checkboxModelColumn, p->checkboxEditableColumn); - r = dp->icon; - // the real listview also does this :| - if (ImageList_GetIconSize(t->smallImages, &cxIcon, &cyIcon) == 0) { - logLastError(L"LVM_GETSUBITEMRECT cell"); - return E_FAIL; - } - yoff = ((r.bottom - r.top) - cyIcon) / 2; - r.top += yoff; - r.bottom += yoff; -if ((nm->nmcd.dwItemSpec%2)==0) - if (ImageList_Draw(t->smallImages, index, nm->nmcd.hdc, - r.left, r.top, ILD_NORMAL) == 0) { - logLastError(L"ImageList_Draw()"); - return E_FAIL; - } - return S_OK; -} - -// references for checkbox drawing: -// - https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485 -// - https://blogs.msdn.microsoft.com/oldnewthing/20171201-00/?p=97505 - -static UINT unthemedStates[] = { - 0, - DFCS_CHECKED, - DFCS_INACTIVE, - DFCS_CHECKED | DFCS_INACTIVE, -}; - -static int themedStates[] = { - CBS_UNCHECKEDNORMAL, - CBS_CHECKEDNORMAL, - CBS_UNCHECKEDDISABLED, - CBS_CHECKEDDISABLED, -}; - -// TODO properly clean up on failure -static HRESULT mkCheckboxes(uiTable *t, HTHEME theme, HDC dc, int cxList, int cyList, int cxCheck, int cyCheck) -{ - BITMAPINFO bmi; - HBITMAP b; - VOID *bits; - HDC cdc; - HBITMAP prevBitmap; - RECT r; - int i; - HRESULT hr; - - ZeroMemory(&bmi, sizeof (BITMAPINFO)); - bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); - bmi.bmiHeader.biWidth = cxList * nCheckboxImages; - bmi.bmiHeader.biHeight = cyList; - bmi.bmiHeader.biPlanes = 1; - bmi.bmiHeader.biBitCount = 32; - bmi.bmiHeader.biCompression = BI_RGB; - b = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, - &bits, NULL, 0); - if (b == NULL) { - logLastError(L"CreateDIBSection()"); - return E_FAIL; - } - - cdc = CreateCompatibleDC(dc); - if (cdc == NULL) { - logLastError(L"CreateCompatibleDC()"); - return E_FAIL; - } - prevBitmap = (HBITMAP) SelectObject(cdc, b); - if (prevBitmap == NULL) { - logLastError(L"SelectObject() b"); - return E_FAIL; - } - - // the actual list view LVS_EX_CHECKBOXES code does this to ensure the entire image is valid, not just the parts that are drawn after resizing - // TODO find a better, alpha-friendly way to do this - // note that the actual list view does this only if unthemed, but it can get away with that since its image lists only contain checkmarks - // ours don't, so we have to compromise until the above TODO is resolved so we don't draw alpha stuff on top of garbage - if (theme == NULL || cxList != cxCheck || cyList != cyCheck) { - r.left = 0; - r.top = 0; - r.right = cxList * nCheckboxImages; - r.bottom = cyList; - FillRect(cdc, &r, GetSysColorBrush(COLOR_WINDOW)); - } - - r.left = 0; - r.top = 0; - r.right = cxCheck; - r.bottom = cyCheck; - if (theme != NULL) { - // because we're not making an image list exactly the correct size, we'll need to manually position the checkbox correctly - // let's just center it for now - // TODO make sure this is correct... - r.left = (cxList - cxCheck) / 2; - r.top = (cyList - cyCheck) / 2; - r.right += r.left; - r.bottom += r.top; - for (i = 0; i < nCheckboxImages; i++) { - hr = DrawThemeBackground(theme, cdc, - BP_CHECKBOX, themedStates[i], - &r, NULL); - if (hr != S_OK) { - logHRESULT(L"DrawThemeBackground()", hr); - return hr; - } - r.left += cxList; - r.right += cxList; - } - } else { - // this is what the actual list view LVS_EX_CHECKBOXES code does to more correctly size the checkboxes - // TODO check errors - InflateRect(&r, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE)); - r.right++; - r.bottom++; - for (i = 0; i < nCheckboxImages; i++) { - if (DrawFrameControl(cdc, &r, - DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | unthemedStates[i]) == 0) { - logLastError(L"DrawFrameControl()"); - return E_FAIL; - } - r.left += cxList; - r.right += cxList; - } - } - - if (SelectObject(cdc, prevBitmap) != ((HGDIOBJ) b)) { - logLastError(L"SelectObject() prev"); - return E_FAIL; - } - if (DeleteDC(cdc) == 0) { - logLastError(L"DeleteDC()"); - return E_FAIL; - } - - if (ImageList_Add(t->smallImages, b, NULL) == -1) { - logLastError(L"ImageList_Add()"); - return E_FAIL; - } - - // TODO error check - DeleteObject(b); - return S_OK; -} - -#endif From bcab52131143fc9e4a3a0f788790951ece7f2360 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 12:33:16 -0400 Subject: [PATCH 101/166] And consolidated the LVN_DISPINFO handlers. Everything's a lot cleaner now too, woo! --- windows/CMakeLists.txt | 3 +- windows/table.cpp | 25 +++----------- windows/table.hpp | 16 ++------- windows/tabledispinfo.cpp | 73 +++++++++++++++++++++++++++++++++++++++ windows/tableimages.cpp | 68 ------------------------------------ windows/tabletext.cpp | 29 ---------------- 6 files changed, 81 insertions(+), 133 deletions(-) create mode 100644 windows/tabledispinfo.cpp delete mode 100644 windows/tableimages.cpp delete mode 100644 windows/tabletext.cpp diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index d074ca87..5a15459c 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -51,9 +51,8 @@ list(APPEND _LIBUI_SOURCES windows/stddialogs.cpp windows/tab.cpp windows/table.cpp + windows/tabledispinfo.cpp windows/tabledraw.cpp - windows/tableimages.cpp - windows/tabletext.cpp windows/tabpage.cpp windows/text.cpp windows/utf16.cpp diff --git a/windows/table.cpp b/windows/table.cpp index e1eb011d..89276fee 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -76,25 +76,6 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) } } -static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm) -{ - // TODO remove static - static uiprivTableColumnParams *p; - HRESULT hr; - - p = (*(t->columns))[nm->item.iSubItem]; - hr = uiprivLVN_GETDISPINFOText(t, nm, p); - if (hr != S_OK) { - // TODO - } - hr = uiprivLVN_GETDISPINFOImagesCheckboxes(t, nm, p); - if (hr != S_OK) { - // TODO - } - - return 0; -} - static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) { uiTable *t = uiTable(c); @@ -102,7 +83,11 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) switch (nmhdr->code) { case LVN_GETDISPINFO: - *lResult = onLVN_GETDISPINFO(t, (NMLVDISPINFOW *) nmhdr); + hr = uiprivTableHandleLVN_GETDISPINFO(t, (NMLVDISPINFOW *) nmhdr, lResult); + if (hr != S_OK) { + // TODO + return FALSE; + } return TRUE; case NM_CUSTOMDRAW: hr = uiprivTableHandleNM_CUSTOMDRAW(t, (NMLVCUSTOMDRAW *) nmhdr, lResult); diff --git a/windows/table.hpp b/windows/table.hpp index 4e33d70c..42eb04be 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -32,21 +32,9 @@ struct uiTable { // TODO make sure replacing images while selected in the listview is even allowed HIMAGELIST imagelist; }; -typedef struct uiprivSubitemDrawParams uiprivSubitemDrawParams; -struct uiprivSubitemDrawParams { - bool selected; - LRESULT bitmapMargin; - RECT bounds; - RECT icon; - RECT label; -}; -// tabletext.cpp -extern HRESULT uiprivLVN_GETDISPINFOText(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); - -// tableimages.cpp -extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); -extern HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp); +// tabledispinfo.cpp +extern HRESULT uiprivTableHandleLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm, LRESULT *lResult); // tabledraw.cpp extern HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult); diff --git a/windows/tabledispinfo.cpp b/windows/tabledispinfo.cpp new file mode 100644 index 00000000..87cdd0d1 --- /dev/null +++ b/windows/tabledispinfo.cpp @@ -0,0 +1,73 @@ +// 13 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +static HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) +{ + uiTableData *data; + WCHAR *wstr; + HRESULT hr; + + if ((nm->item.mask & LVIF_TEXT) == 0) + return S_OK; + if (p->textModelColumn != -1) + return S_OK; + + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); + wstr = toUTF16(uiTableDataString(data)); + uiFreeTableData(data); + // We *could* just make pszText into a freshly allocated + // conversion and avoid the limitation of cchTextMax. + // But then, we would have to keep things around for some + // amount of time (some pages on MSDN say 2 additional + // LVN_GETDISPINFO messages). And in practice, anything + // that results in extra LVN_GETDISPINFO messages (such as + // LVN_GETITEMRECT with LVIR_LABEL) will break this + // counting. + // TODO make it so we don't have to make a copy; instead we can convert directly into pszText (this will also avoid the risk of having a dangling surrogate pair at the end) + wcsncpy(nm->item.pszText, wstr, nm->item.cchTextMax); + nm->item.pszText[nm->item.cchTextMax - 1] = L'\0'; + uiprivFree(wstr); + return S_OK; +} + +static HRESULT handleLVIF_IMAGE(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) +{ + uiTableData *data; + HRESULT hr; + + if (nm->item.iSubItem == 0 && p->imageModelColumn == -1 && p->checkboxModelColumn == -1) { + // Having an image list always leaves space for an image + // on the main item :| + // Other places on the internet imply that you should be + // able to do this but that it shouldn't work, but it works + // perfectly (and pixel-perfectly too) for me, so... + nm->item.mask |= LVIF_INDENT; + nm->item.iIndent = -1; + } + if ((nm->item.mask & LVIF_IMAGE) == 0) + return S_OK; // nothing to do here + + // TODO see if the -1 part is correct + // TODO see if we should use state instead of images for checkbox data + nm->item.iImage = -1; + if (p->imageModelColumn != -1 || p->checkboxModelColumn != -1) + nm->item.iImage = 0; + return S_OK; +} + +HRESULT uiprivTableHandleLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm, LRESULT *lResult) +{ + uiprivTableColumnParams *p; + HRESULT hr; + + p = (*(t->columns))[nm->item.iSubItem]; + hr = handleLVIF_TEXT(t, nm, p); + if (hr != S_OK) + return hr; + hr = handleLVIF_IMAGE(t, nm, p); + if (hr != S_OK) + return hr; + *lResult = 0; + return S_OK; +} diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp deleted file mode 100644 index 7f163ec0..00000000 --- a/windows/tableimages.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// 10 june 2018 -#include "uipriv_windows.hpp" -#include "table.hpp" - -/* -This file handles both images and checkboxes in tables. - -For images, we'll do a similar thing to what text columns do: cycle out images from the small image list every few LVN_GETDISPINFO notifications. - -For checkboxes, the native list view checkbox functionality uses state images, but those are only supported on the main item, not on subitems. So instead, we'll do them on normal images instead. -TODO will this affect accessibility? - -We'll use the small image list. For this, the first few items will be reserved for checkboxes, and the last few for cell images. -*/ - -// checkboxes TODOs: -// - see if we need to get rid of the extra margin in subitems -// - get rid of the extra bitmap margin space before text -// - get rid of extra whitespace before text on subitems (this might not be necessary if we can fill the background of images AND this amount is the same as on the first column; it is a hardcoded 2 logical units in the real list view code) - -#define nCheckboxImages 4 - -HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) -{ - uiTableData *data; - HRESULT hr; - - if (nm->item.iSubItem == 0 && p->imageModelColumn == -1 && p->checkboxModelColumn == -1) { - // having an image list always leaves space for an image on the main item :| - // other places on the internet imply that you should be able to do this but that it shouldn't work - // but it works perfectly (and pixel-perfectly too) for me, so... - nm->item.mask |= LVIF_INDENT; - nm->item.iIndent = -1; - } - if ((nm->item.mask & LVIF_IMAGE) == 0) - return S_OK; // nothing to do here - - // TODO - nm->item.iImage = -1; - if (p->imageModelColumn != -1 || p->checkboxModelColumn != -1) - nm->item.iImage = 0; - return S_OK; - - if (p->imageModelColumn != -1) { - nm->item.iImage = 0; - return S_OK; - } - - if (p->checkboxModelColumn != -1) { -#if 0 - // TODO handle enabled - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); - checked = uiTableDataInt(data) != 0; - uiFreeTableData(data); - nm->item.iImage = 0; - if (checked) - nm->item.iImage = 1; - nm->item.mask |= LVIF_IMAGE; -#endif - nm->item.mask |= LVIF_IMAGE; - nm->item.iImage = nm->item.iItem % 4; - return S_OK; - } - - // TODO see if this is correct - nm->item.iImage = -1; - return S_OK; -} diff --git a/windows/tabletext.cpp b/windows/tabletext.cpp deleted file mode 100644 index d1feb336..00000000 --- a/windows/tabletext.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// 13 june 2018 -#include "uipriv_windows.hpp" -#include "table.hpp" - -// This file handles text in tables. - -HRESULT uiprivLVN_GETDISPINFOText(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) -{ - uiTableData *data; - WCHAR *wstr; - HRESULT hr; - - if ((nm->item.mask & LVIF_TEXT) == 0) - return S_OK; - if (p->textModelColumn != -1) - return S_OK; - - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); - wstr = toUTF16(uiTableDataString(data)); - uiFreeTableData(data); - // we could just make pszText into a freshly allocated conversion and avoid the limitation of cchTextMax - // but then we would have to keep things around for some amount of time (some pages on MSDN say 2 additional LVN_GETDISPINFO messages) - // and in practice, anything that results in extra LVN_GETDISPINFO messages (such as fillSubitemDrawParams() below) will break this counting - // TODO make it so we don't have to make a copy; instead we can convert directly into pszText (this will also avoid the risk of having a dangling surrogate pair at the end) - wcsncpy(nm->item.pszText, wstr, nm->item.cchTextMax); - nm->item.pszText[nm->item.cchTextMax - 1] = L'\0'; - uiprivFree(wstr); - return S_OK; -} From d63af885ba6fd36479cc87a8fe1a6c3c1685ba63 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 12:52:16 -0400 Subject: [PATCH 102/166] Oops, that's why tooltips weren't working right: I had inverted a test in tabledispinfo.cpp. Fixed. --- windows/tabledispinfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/tabledispinfo.cpp b/windows/tabledispinfo.cpp index 87cdd0d1..9912bebf 100644 --- a/windows/tabledispinfo.cpp +++ b/windows/tabledispinfo.cpp @@ -10,7 +10,7 @@ static HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnP if ((nm->item.mask & LVIF_TEXT) == 0) return S_OK; - if (p->textModelColumn != -1) + if (p->textModelColumn == -1) return S_OK; data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); From f96c0f410ee8294dd66fa42b924b1458b75c8186 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 13:06:20 -0400 Subject: [PATCH 103/166] Started the implementation of progressbar columns. This handles LVN_GETDISPINFO. --- windows/table.cpp | 5 +++- windows/tabledispinfo.cpp | 53 ++++++++++++++++++++++++++------------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 89276fee..b3409907 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -230,7 +230,10 @@ void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxM void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) { - // TODO + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->progressBarModelColumn = progressModelColumn; } void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModelColumn, int buttonClickableModelColumn) diff --git a/windows/tabledispinfo.cpp b/windows/tabledispinfo.cpp index 9912bebf..2e8a06e2 100644 --- a/windows/tabledispinfo.cpp +++ b/windows/tabledispinfo.cpp @@ -6,28 +6,47 @@ static HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnP { uiTableData *data; WCHAR *wstr; + int progress; HRESULT hr; if ((nm->item.mask & LVIF_TEXT) == 0) return S_OK; - if (p->textModelColumn == -1) - return S_OK; - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); - wstr = toUTF16(uiTableDataString(data)); - uiFreeTableData(data); - // We *could* just make pszText into a freshly allocated - // conversion and avoid the limitation of cchTextMax. - // But then, we would have to keep things around for some - // amount of time (some pages on MSDN say 2 additional - // LVN_GETDISPINFO messages). And in practice, anything - // that results in extra LVN_GETDISPINFO messages (such as - // LVN_GETITEMRECT with LVIR_LABEL) will break this - // counting. - // TODO make it so we don't have to make a copy; instead we can convert directly into pszText (this will also avoid the risk of having a dangling surrogate pair at the end) - wcsncpy(nm->item.pszText, wstr, nm->item.cchTextMax); - nm->item.pszText[nm->item.cchTextMax - 1] = L'\0'; - uiprivFree(wstr); + if (p->textModelColumn != -1) { + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); + wstr = toUTF16(uiTableDataString(data)); + uiFreeTableData(data); + // We *could* just make pszText into a freshly allocated + // conversion and avoid the limitation of cchTextMax. + // But then, we would have to keep things around for some + // amount of time (some pages on MSDN say 2 additional + // LVN_GETDISPINFO messages). And in practice, anything + // that results in extra LVN_GETDISPINFO messages (such + // as LVN_GETITEMRECT with LVIR_LABEL) will break this + // counting. + // TODO make it so we don't have to make a copy; instead we can convert directly into pszText (this will also avoid the risk of having a dangling surrogate pair at the end) + wcsncpy(nm->item.pszText, wstr, nm->item.cchTextMax); + nm->item.pszText[nm->item.cchTextMax - 1] = L'\0'; + uiprivFree(wstr); + return S_OK; + } + + if (p->progressBarModelColumn != -1) { + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->progressBarModelColumn); + progress = uiTableDataInt(data); + uiFreeTableData(data); + + if (progress == -1) { + // TODO either localize this or replace it with something that's language-neutral + // TODO ensure null terminator + wcsncpy(nm->item.pszText, L"Indeterminate", nm->item.cchTextMax); + return S_OK; + } + // TODO ensure null terminator + _snwprintf(nm->item.pszText, nm->item.cchTextMax, L"%d%%", progress); + return S_OK; + } + return S_OK; } From 8769bea3a074aa8484c4d8d235d465a211f53da0 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 13:57:46 -0400 Subject: [PATCH 104/166] Added code for unthemed definite progressbars. --- windows/tabledraw.cpp | 77 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index ad8b4b46..7dbd7177 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -310,6 +310,80 @@ static HRESULT drawTextPart(struct drawState *s) return S_OK; } +// much of this is to imitate what shell32.dll's CDrawProgressBar does +static HRESULT drawProgressBarPart(struct drawState *s) +{ + uiTableData *data; + int progress; + HTHEME theme; + RECT r; + RECT rBorder, rFill; + TEXTMETRICW tm; + int sysColor; + HRESULT hr; + + if (s->p->progressBarModelColumn == -1) + return S_OK; + data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->progressBarModelColumn); + progress = uiTableDataInt(data); + uiFreeTableData(data); + if (progress == -1) + return S_OK; // TODO + + theme = OpenThemeData(s->t->hwnd, L"TODO"); + + if (GetTextMetricsW(s->dc, &tm) == 0) { + logLastError(L"GetTextMetricsW()"); + hr = E_FAIL; + goto fail; + } + r = s->subitemBounds; + // this sets the height of the progressbar and vertically centers it in one fell swoop + r.top += (r.bottom - tm.tmHeight - r.top) / 2; + r.bottom = r.top + tm.tmHeight; + + // TODO check errors + rBorder = r; + InflateRect(&rBorder, -1, -1); + if (theme != NULL) { + // TODO + }/* else */{ + HPEN pen, prevPen; + HBRUSH brush, prevBrush; + + sysColor = COLOR_HIGHLIGHT; + if (s->selected) + sysColor = COLOR_HIGHLIGHTTEXT; + + // TODO check errors everywhere + pen = CreatePen(PS_SOLID, 1, GetSysColor(sysColor)); + prevPen = (HPEN) SelectObject(s->dc, pen); + brush = (HBRUSH) GetStockObject(NULL_BRUSH); + prevBrush = (HBRUSH) SelectObject(s->dc, brush); + Rectangle(s->dc, rBorder.left, rBorder.top, rBorder.right, rBorder.bottom); + SelectObject(s->dc, prevBrush); + SelectObject(s->dc, prevPen); + DeleteObject(pen); + } + + rFill = r; + // TODO check error + InflateRect(&rFill, -1, -1); + rFill.right -= (rFill.right - rFill.left) * (100 - progress) / 100; + if (theme != NULL) { + // TODO + }/* else*/ + // TODO check errors + FillRect(s->dc, &rFill, GetSysColorBrush(sysColor)); + + hr = S_OK; +fail: + // TODO check errors + if (theme != NULL) + CloseThemeData(theme); + return hr; +} + static HRESULT freeDrawState(struct drawState *s) { HRESULT hr, hrret; @@ -507,6 +581,9 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * if (hr != S_OK) goto fail; hr = drawTextPart(&s); + if (hr != S_OK) + goto fail; + hr = drawProgressBarPart(&s); if (hr != S_OK) goto fail; hr = freeDrawState(&s); From c978f6fece2a484d9ea696595999b4e06887e876 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 18:06:44 -0400 Subject: [PATCH 105/166] Started indeterminate progress bars. This is gonna be interesting. --- windows/table.hpp | 1 + windows/tabledraw.cpp | 42 +++++++++++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/windows/table.hpp b/windows/table.hpp index 42eb04be..bab46f0e 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -31,6 +31,7 @@ struct uiTable { int backgroundColumn; // TODO make sure replacing images while selected in the listview is even allowed HIMAGELIST imagelist; + LONG indeterminatePosition; }; // tabledispinfo.cpp diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 7dbd7177..adaf5feb 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -311,13 +311,16 @@ static HRESULT drawTextPart(struct drawState *s) } // much of this is to imitate what shell32.dll's CDrawProgressBar does +#define indeterminateSegments 8 + static HRESULT drawProgressBarPart(struct drawState *s) { uiTableData *data; int progress; HTHEME theme; RECT r; - RECT rBorder, rFill; + RECT rBorder, rFill[2]; + int i, nFill; TEXTMETRICW tm; int sysColor; HRESULT hr; @@ -327,8 +330,6 @@ static HRESULT drawProgressBarPart(struct drawState *s) data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->progressBarModelColumn); progress = uiTableDataInt(data); uiFreeTableData(data); - if (progress == -1) - return S_OK; // TODO theme = OpenThemeData(s->t->hwnd, L"TODO"); @@ -366,16 +367,35 @@ static HRESULT drawProgressBarPart(struct drawState *s) DeleteObject(pen); } - rFill = r; + nFill = 1; + rFill[0] = r; // TODO check error - InflateRect(&rFill, -1, -1); - rFill.right -= (rFill.right - rFill.left) * (100 - progress) / 100; - if (theme != NULL) { - // TODO - }/* else*/ - // TODO check errors - FillRect(s->dc, &rFill, GetSysColorBrush(sysColor)); + InflateRect(&rFill[0], -1, -1); + if (progress != -1) + rFill[0].right -= (rFill[0].right - rFill[0].left) * (100 - progress) / 100; + else { + LONG barWidth; + LONG pieceWidth; + // TODO explain all this + rFill[1] = rFill[0]; // save in case we need it + barWidth = rFill[0].right - rFill[0].left; + pieceWidth = barWidth / indeterminateSegments; + rFill[0].left += s->t->indeterminatePosition % barWidth; + if ((rFill[0].left + pieceWidth) >= rFill[0].right) { + // make this piece wrap back around + nFill++; + rFill[1].right = rFill[1].left + (pieceWidth - (rFill[0].right - rFill[0].left)); + } else + rFill[0].right = rFill[0].left + pieceWidth; + } + for (i = 0; i < nFill; i++) +{ if (theme != NULL) { + // TODO + }/* else*/ + // TODO check errors + FillRect(s->dc, &rFill[i], GetSysColorBrush(sysColor)); +} hr = S_OK; fail: // TODO check errors From c7555dcfd3aa22f7a662d546f58b9cd69685dbe2 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 19:05:36 -0400 Subject: [PATCH 106/166] Started a more flexible indeterminate-state implementation. Now to build and test it. --- windows/table.cpp | 69 ++++++++++++++++++++++++++++++++++++++- windows/table.hpp | 4 ++- windows/tabledispinfo.cpp | 4 +-- windows/tabledraw.cpp | 8 ++--- windows/winapi.hpp | 1 + 5 files changed, 76 insertions(+), 10 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index b3409907..5fcd7c6b 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -76,6 +76,68 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) } } +static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData) +{ + uiTable *t = (uiTable *) dwRefData; + + switch (uMsg) { + case WM_TIMER: + if (wParam != (WPARAM) t) + break; + for (auto &i : t->indeterminatePositions) { + i->second++; + // TODO check errors + SendMessageW(hwnd, LVM_UPDATE, (WPARAM) (i->first.first), 0); + } + return 0; + case WM_NCDESTROY: + if (RemoveWindowSubclass(hwnd, tableSubProc, uIDSubclass) == FALSE) + logLastError(L"RemoveWindowSubclass()"); + // fall through + } + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} + +int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos) +{ + uiTableData *data; + int progress; + std::pair p; + std::map, LONG>::iterator iter; + bool startTimer = false; + bool stopTimer = false; + + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, item, modelColumn); + progress = uiTableModelInt(data); + uiFreeTableData(data); + + p.first = item; + p.second = subitem; + iter = t->indeterminatePositions->find(p); + if (iter == t->indeterminatePositions->end()) { + if (progress == -1) { + startTimer = t->indeterminatePositions->size() == 0; + (*(t->indeterminatePositions))[p] = 0; + if (pos != NULL) + *pos = 0; + } + } else + if (progress != -1) { + t->indeterminatePositions->erase(p); + stopTimer = t->indeterminatePositions->size() == 0; + } else if (pos != NULL) + *pos = iter->second; + + if (startTimer) + // the interval shown here is PBM_SETMARQUEE's default + // TODO should we pass a function here instead? it seems to be called by DispatchMessage(), not DefWindowProc(), but I'm still unsure + if (SetTimer(t->hwnd, (UINT_PTR) (&t), 30, NULL) == 0) + logLastError(L"SetTimer()"); + if (stopTimer) + if (KillTimer(t->hwnd, (UINT_PTR) (&t)) == 0) + logLastError(L"KillTimer()"); +} + static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) { uiTable *t = uiTable(c); @@ -119,7 +181,8 @@ static void uiTableDestroy(uiControl *c) for (auto col : *(t->columns)) uiprivFree(col); delete t->columns; - // t->smallImages will be automatically destroyed + // t->imagelist will be automatically destroyed + delete t->indeterminatePositions; uiFreeControl(uiControl(t)); } @@ -283,5 +346,9 @@ uiTable *uiNewTable(uiTableModel *model) // TODO } + t->indeterminatePositions = new std::map, LONG>; + if (SetWindowSubclass(t->hwnd, tableSubProc, 0, (DWORD_PTR) t) == FALSE) + logLastError(L"SetWindowSubclass()"); + return t; } diff --git a/windows/table.hpp b/windows/table.hpp index bab46f0e..bf55ef6f 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -31,8 +31,10 @@ struct uiTable { int backgroundColumn; // TODO make sure replacing images while selected in the listview is even allowed HIMAGELIST imagelist; - LONG indeterminatePosition; + // TODO document all this + std::map, LONG> *indeterminatePositions; }; +extern int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos); // tabledispinfo.cpp extern HRESULT uiprivTableHandleLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm, LRESULT *lResult); diff --git a/windows/tabledispinfo.cpp b/windows/tabledispinfo.cpp index 2e8a06e2..33559bb3 100644 --- a/windows/tabledispinfo.cpp +++ b/windows/tabledispinfo.cpp @@ -32,9 +32,7 @@ static HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnP } if (p->progressBarModelColumn != -1) { - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->progressBarModelColumn); - progress = uiTableDataInt(data); - uiFreeTableData(data); + progress = uiprivTableProgress(t, nm->item.iItem, p->progressBarModelColumn, NULL); if (progress == -1) { // TODO either localize this or replace it with something that's language-neutral diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index adaf5feb..59b9d90c 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -315,8 +315,8 @@ static HRESULT drawTextPart(struct drawState *s) static HRESULT drawProgressBarPart(struct drawState *s) { - uiTableData *data; int progress; + LONG indeterminatePos; HTHEME theme; RECT r; RECT rBorder, rFill[2]; @@ -327,9 +327,7 @@ static HRESULT drawProgressBarPart(struct drawState *s) if (s->p->progressBarModelColumn == -1) return S_OK; - data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->progressBarModelColumn); - progress = uiTableDataInt(data); - uiFreeTableData(data); + progress = uiprivTableProgress(s->t, s->iItem, s->p->progressBarModelColumn, &indeterminatePos); theme = OpenThemeData(s->t->hwnd, L"TODO"); @@ -381,7 +379,7 @@ static HRESULT drawProgressBarPart(struct drawState *s) rFill[1] = rFill[0]; // save in case we need it barWidth = rFill[0].right - rFill[0].left; pieceWidth = barWidth / indeterminateSegments; - rFill[0].left += s->t->indeterminatePosition % barWidth; + rFill[0].left += indeterminatePos % barWidth; if ((rFill[0].left + pieceWidth) >= rFill[0].right) { // make this piece wrap back around nFill++; diff --git a/windows/winapi.hpp b/windows/winapi.hpp index 19426844..92c08230 100644 --- a/windows/winapi.hpp +++ b/windows/winapi.hpp @@ -60,4 +60,5 @@ #include #include #include +#include #endif From 301376706fff9b9eb020c75d6aeb3972c61a9ff8 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 19:40:06 -0400 Subject: [PATCH 107/166] And made indeterminate progressbars work. Now to theme them. --- windows/table.cpp | 15 +++++++++------ windows/tabledispinfo.cpp | 2 +- windows/tabledraw.cpp | 3 ++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 5fcd7c6b..9dba46b7 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -76,6 +76,7 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) } } +// TODO explain all this static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData) { uiTable *t = (uiTable *) dwRefData; @@ -84,10 +85,11 @@ static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM case WM_TIMER: if (wParam != (WPARAM) t) break; - for (auto &i : t->indeterminatePositions) { - i->second++; + // TODO only increment and update if visible? + for (auto &i : *(t->indeterminatePositions)) { + i.second++; // TODO check errors - SendMessageW(hwnd, LVM_UPDATE, (WPARAM) (i->first.first), 0); + SendMessageW(hwnd, LVM_UPDATE, (WPARAM) (i.first.first), 0); } return 0; case WM_NCDESTROY: @@ -108,7 +110,7 @@ int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG bool stopTimer = false; data = (*(t->model->mh->CellValue))(t->model->mh, t->model, item, modelColumn); - progress = uiTableModelInt(data); + progress = uiTableDataInt(data); uiFreeTableData(data); p.first = item; @@ -131,11 +133,13 @@ int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG if (startTimer) // the interval shown here is PBM_SETMARQUEE's default // TODO should we pass a function here instead? it seems to be called by DispatchMessage(), not DefWindowProc(), but I'm still unsure - if (SetTimer(t->hwnd, (UINT_PTR) (&t), 30, NULL) == 0) + if (SetTimer(t->hwnd, (UINT_PTR) t, 30, NULL) == 0) logLastError(L"SetTimer()"); if (stopTimer) if (KillTimer(t->hwnd, (UINT_PTR) (&t)) == 0) logLastError(L"KillTimer()"); + + return progress; } static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) @@ -315,7 +319,6 @@ uiTable *uiNewTable(uiTableModel *model) { uiTable *t; int n; - int i; HRESULT hr; uiWindowsNewControl(uiTable, t); diff --git a/windows/tabledispinfo.cpp b/windows/tabledispinfo.cpp index 33559bb3..2e3ae4b9 100644 --- a/windows/tabledispinfo.cpp +++ b/windows/tabledispinfo.cpp @@ -32,7 +32,7 @@ static HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnP } if (p->progressBarModelColumn != -1) { - progress = uiprivTableProgress(t, nm->item.iItem, p->progressBarModelColumn, NULL); + progress = uiprivTableProgress(t, nm->item.iItem, nm->item.iSubItem, p->progressBarModelColumn, NULL); if (progress == -1) { // TODO either localize this or replace it with something that's language-neutral diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 59b9d90c..bc2e76d9 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -327,7 +327,7 @@ static HRESULT drawProgressBarPart(struct drawState *s) if (s->p->progressBarModelColumn == -1) return S_OK; - progress = uiprivTableProgress(s->t, s->iItem, s->p->progressBarModelColumn, &indeterminatePos); + progress = uiprivTableProgress(s->t, s->iItem, s->iSubItem, s->p->progressBarModelColumn, &indeterminatePos); theme = OpenThemeData(s->t->hwnd, L"TODO"); @@ -376,6 +376,7 @@ static HRESULT drawProgressBarPart(struct drawState *s) LONG pieceWidth; // TODO explain all this + // TODO this should really start the progressbar scrolling into view instead of already on screen when first set rFill[1] = rFill[0]; // save in case we need it barWidth = rFill[0].right - rFill[0].left; pieceWidth = barWidth / indeterminateSegments; From a00ca05136f4e339152ca4bb6df1d097441bc588 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 20:10:39 -0400 Subject: [PATCH 108/166] Added themed progressbars. --- windows/tabledraw.cpp | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index bc2e76d9..8e6cb940 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -329,7 +329,7 @@ static HRESULT drawProgressBarPart(struct drawState *s) return S_OK; progress = uiprivTableProgress(s->t, s->iItem, s->iSubItem, s->p->progressBarModelColumn, &indeterminatePos); - theme = OpenThemeData(s->t->hwnd, L"TODO"); + theme = OpenThemeData(s->t->hwnd, L"PROGRESS"); if (GetTextMetricsW(s->dc, &tm) == 0) { logLastError(L"GetTextMetricsW()"); @@ -345,8 +345,23 @@ static HRESULT drawProgressBarPart(struct drawState *s) rBorder = r; InflateRect(&rBorder, -1, -1); if (theme != NULL) { - // TODO - }/* else */{ + RECT crect; + + hr = GetThemeBackgroundContentRect(theme, s->dc, + PP_TRANSPARENTBAR, PBBS_NORMAL, + &rBorder, &crect); + if (hr != S_OK) { + logHRESULT(L"GetThemeBackgroundContentRect()", hr); + goto fail; + } + hr = DrawThemeBackground(theme, s->dc, + PP_TRANSPARENTBAR, PBBS_NORMAL, + &crect, NULL); + if (hr != S_OK) { + logHRESULT(L"DrawThemeBackground() border", hr); + goto fail; + } + } else { HPEN pen, prevPen; HBRUSH brush, prevBrush; @@ -389,12 +404,18 @@ static HRESULT drawProgressBarPart(struct drawState *s) rFill[0].right = rFill[0].left + pieceWidth; } for (i = 0; i < nFill; i++) -{ if (theme != NULL) { - // TODO - }/* else*/ + if (theme != NULL) { + hr = DrawThemeBackground(theme, s->dc, + PP_FILL, PBFS_NORMAL, + &rFill[i], NULL); + if (hr != S_OK) { + logHRESULT(L"DrawThemeBackground() fill", hr); + goto fail; + } + } else // TODO check errors FillRect(s->dc, &rFill[i], GetSysColorBrush(sysColor)); -} + hr = S_OK; fail: // TODO check errors From 9fba903c4a4e02b965842192901d0b5b2cbe39eb Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 16 Jun 2018 22:12:54 -0400 Subject: [PATCH 109/166] Started button columns. LVN_GETDISPINFO handled. Also filled in the rest of the new column functions. --- windows/table.cpp | 12 ++++++++++-- windows/tabledispinfo.cpp | 13 +++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 9dba46b7..8e0348d2 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -258,7 +258,10 @@ void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) { - // TODO + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->imageModelColumn = imageModelColumn; } void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) @@ -305,7 +308,12 @@ void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressMo void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModelColumn, int buttonClickableModelColumn) { - // TODO + uiprivTableColumnParams *p; + + // TODO see if we can get rid of this parameter + p = appendColumn(t, name, LVCFMT_LEFT); + p->buttonModelColumn = buttonTextModelColumn; + p->buttonClickableModelColumn = buttonClickableModelColumn; } void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) diff --git a/windows/tabledispinfo.cpp b/windows/tabledispinfo.cpp index 2e3ae4b9..f8a06842 100644 --- a/windows/tabledispinfo.cpp +++ b/windows/tabledispinfo.cpp @@ -2,8 +2,12 @@ #include "uipriv_windows.hpp" #include "table.hpp" +// further reading: +// - https://msdn.microsoft.com/en-us/library/ye4z8x58.aspx + static HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) { + int strcol; uiTableData *data; WCHAR *wstr; int progress; @@ -12,8 +16,13 @@ static HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnP if ((nm->item.mask & LVIF_TEXT) == 0) return S_OK; - if (p->textModelColumn != -1) { - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); + strcol = -1; + if (p->textModelColumn != -1) + strcol = p->textModelColumn; + else if (p->buttonModelColumn != -1) + strcol = p->buttonModelColumn; + if (strcol != -1) { + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, strcol); wstr = toUTF16(uiTableDataString(data)); uiFreeTableData(data); // We *could* just make pszText into a freshly allocated From 7bc121b1ec26f676916c71d383611b488c559cf2 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 17 Jun 2018 09:01:24 -0400 Subject: [PATCH 110/166] And drew buttons. Woo! Now for the harder part: editing. --- windows/tabledraw.cpp | 108 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 8e6cb940..ca1982f8 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -327,6 +327,7 @@ static HRESULT drawProgressBarPart(struct drawState *s) if (s->p->progressBarModelColumn == -1) return S_OK; + progress = uiprivTableProgress(s->t, s->iItem, s->iSubItem, s->p->progressBarModelColumn, &indeterminatePos); theme = OpenThemeData(s->t->hwnd, L"PROGRESS"); @@ -424,6 +425,110 @@ fail: return hr; } +static HRESULT drawButtonPart(struct drawState *s) +{ + uiTableData *data; + WCHAR *wstr; + bool enabled; + HTHEME theme; + RECT r; + TEXTMETRICW tm; + HRESULT hr; + + if (s->p->buttonModelColumn == -1) + return S_OK; + + data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->buttonModelColumn); + wstr = toUTF16(uiTableDataString(data)); + uiFreeTableData(data); + switch (s->p->buttonClickableModelColumn) { + case uiTableModelColumnNeverEditable: + enabled = 0; + break; + case uiTableModelColumnAlwaysEditable: + enabled = 1; + break; + default: + data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->checkboxEditableColumn); + enabled = uiTableDataInt(data); + uiFreeTableData(data); + } + + theme = OpenThemeData(s->t->hwnd, L"button"); + + if (GetTextMetricsW(s->dc, &tm) == 0) { + logLastError(L"GetTextMetricsW()"); + hr = E_FAIL; + goto fail; + } + r = s->subitemBounds; + + if (theme != NULL) { + int state; + + state = PBS_NORMAL; + if (!enabled) + state = PBS_DISABLED; + hr = DrawThemeBackground(theme, s->dc, + BP_PUSHBUTTON, state, + &r, NULL); + if (hr != S_OK) { + logHRESULT(L"DrawThemeBackground()", hr); + goto fail; + } + // TODO DT_EDITCONTROL? + // TODO DT_PATH_ELLIPSIS DT_WORD_ELLIPSIS instead of DT_END_ELLIPSIS? a middle-ellipsis option would be ideal here + // TODO is there a theme property we can get instead of hardcoding these flags? if not, make these flags a macro + hr = DrawThemeText(theme, s->dc, + BP_PUSHBUTTON, state, + wstr, -1, + DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX, 0, + &r); + if (hr != S_OK) { + logHRESULT(L"DrawThemeText()", hr); + goto fail; + } + } else { + UINT state; + HBRUSH color, prevColor; + int prevBkMode; + + // TODO check errors + // TODO explain why we're not doing this in the themed case (it has to do with extra transparent pixels) + InflateRect(&r, -1, -1); + state = DFCS_BUTTONPUSH; + if (!enabled) + state |= DFCS_INACTIVE; + if (DrawFrameControl(s->dc, &r, DFC_BUTTON, state) == 0) { + logLastError(L"DrawFrameControl()"); + hr = E_FAIL; + goto fail; + } + color = GetSysColorBrush(COLOR_BTNTEXT); + // TODO check errors for these two + prevColor = (HBRUSH) SelectObject(s->dc, color); + prevBkMode = SetBkMode(s->dc, TRANSPARENT); + // TODO DT_EDITCONTROL? + // TODO DT_PATH_ELLIPSIS DT_WORD_ELLIPSIS instead of DT_END_ELLIPSIS? a middle-ellipsis option would be ideal here + if (DrawTextW(s->dc, wstr, -1, &r, DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX) == 0) { + logLastError(L"DrawTextW()"); + hr = E_FAIL; + goto fail; + } + // TODO check errors for these two + SetBkMode(s->dc, prevBkMode); + SelectObject(s->dc, prevColor); + } + + hr = S_OK; +fail: + // TODO check errors + if (theme != NULL) + CloseThemeData(theme); + uiprivFree(wstr); + return hr; +} + static HRESULT freeDrawState(struct drawState *s) { HRESULT hr, hrret; @@ -624,6 +729,9 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * if (hr != S_OK) goto fail; hr = drawProgressBarPart(&s); + if (hr != S_OK) + goto fail; + hr = drawButtonPart(&s); if (hr != S_OK) goto fail; hr = freeDrawState(&s); From db2f3352c422190ee5af9b5b810d9b27da3ce654 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 17 Jun 2018 09:29:29 -0400 Subject: [PATCH 111/166] Added dummy code to evaluate LVM_SUBITEMHITTEST. The code will remain, but will be #if'd out. --- windows/table.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/windows/table.cpp b/windows/table.cpp index 8e0348d2..2104d893 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -162,6 +162,27 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) return FALSE; } return TRUE; + case NM_CLICK: +#if 1 + { + NMITEMACTIVATE *nm = (NMITEMACTIVATE *) nmhdr; + LVHITTESTINFO ht; + WCHAR buf[256]; + + ZeroMemory(&ht, sizeof (LVHITTESTINFO)); + ht.pt = nm->ptAction; + if (SendMessageW(t->hwnd, LVM_SUBITEMHITTEST, 0, (LPARAM) (&ht)) == (LRESULT) (-1)) + MessageBoxW(GetAncestor(t->hwnd, GA_ROOT), L"No hit", L"No hit", MB_OK); + else { + wsprintf(buf, L"item %d subitem %d htflags 0x%I32X", + ht.iItem, ht.iSubItem, ht.flags); + MessageBoxW(GetAncestor(t->hwnd, GA_ROOT), buf, buf, MB_OK); + } + } +#else +#endif + *lResult = 0; + return TRUE; } return FALSE; } From 2fb3676a8fcc54765bbda6093ea9f7b76c549165 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 17 Jun 2018 10:50:24 -0400 Subject: [PATCH 112/166] More TODOs. --- windows/table.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/windows/table.cpp b/windows/table.cpp index 2104d893..42dd57d7 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -1,6 +1,9 @@ #include "uipriv_windows.hpp" #include "table.hpp" +// general TODOs: +// - tooltips don't work properly on columns with icons (the listview always thinks there's enough room for a short label because it's not taking the icon into account); is this a bug in our LVN_GETDISPINFO handler or something else? + static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { /*TODO.ColorModelColumn = */-1, }; From 6d0b276d6d0727f3adfe21a1843af856af136888 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 17 Jun 2018 11:48:39 -0400 Subject: [PATCH 113/166] Started handling table events. This covers checkboxes and buttons. --- windows/CMakeLists.txt | 1 + windows/table.cpp | 12 ++++++-- windows/table.hpp | 3 ++ windows/tableevents.cpp | 67 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 windows/tableevents.cpp diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 5a15459c..c1747c50 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -53,6 +53,7 @@ list(APPEND _LIBUI_SOURCES windows/table.cpp windows/tabledispinfo.cpp windows/tabledraw.cpp + windows/tableevents.cpp windows/tabpage.cpp windows/text.cpp windows/utf16.cpp diff --git a/windows/table.cpp b/windows/table.cpp index 42dd57d7..4dd1370d 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -166,7 +166,7 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) } return TRUE; case NM_CLICK: -#if 1 +#if 0 { NMITEMACTIVATE *nm = (NMITEMACTIVATE *) nmhdr; LVHITTESTINFO ht; @@ -182,10 +182,16 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) MessageBoxW(GetAncestor(t->hwnd, GA_ROOT), buf, buf, MB_OK); } } -#else -#endif *lResult = 0; return TRUE; +#else + hr = uiprivTableHandleNM_CLICK(t, (NMITEMACTIVATE *) nmhdr, lResult); + if (hr != S_OK) { + // TODO + return FALSE; + } + return TRUE; +#endif } return FALSE; } diff --git a/windows/table.hpp b/windows/table.hpp index bf55ef6f..30bea161 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -42,3 +42,6 @@ extern HRESULT uiprivTableHandleLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm, L // tabledraw.cpp extern HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult); extern HRESULT uiprivUpdateImageListSize(uiTable *t); + +// tableevents.cpp +extern HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult); diff --git a/windows/tableevents.cpp b/windows/tableevents.cpp new file mode 100644 index 00000000..eae3a096 --- /dev/null +++ b/windows/tableevents.cpp @@ -0,0 +1,67 @@ +// 17 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult) +{ + LVHITTESTINFO ht; + uiprivTableColumnParams *p; + int modelColumn, editableColumn; + bool checkbox; + uiTableData *data; + int checked, editable; + + ZeroMemory(&ht, sizeof (LVHITTESTINFO)); + ht.pt = nm->ptAction; + if (SendMessageW(t->hwnd, LVM_SUBITEMHITTEST, 0, (LPARAM) (&ht)) == (LRESULT) (-1)) + goto done; + + modelColumn = -1; + editableColumn = -1; + checkbox = false; + p = (*(t->columns))[ht.iSubItem]; + if (p->checkboxModelColumn != -1) { + modelColumn = p->checkboxModelColumn; + editableColumn = p->checkboxEditableColumn; + checkbox = true; + } else if (p->buttonModelColumn != -1) { + modelColumn = p->buttonModelColumn; + editableColumn = p->buttonClickableModelColumn; + } + if (modelColumn == -1) + goto done; + + switch (editableColumn) { + case uiTableModelColumnNeverEditable: + goto done; + case uiTableModelColumnAlwaysEditable: + break; + default: + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, ht.iItem, editableColumn); + editable = uiTableDataInt(data); + uiFreeTableData(data); + if (!editable) + goto done; + } + + if (checkbox) { + if ((ht.flags & LVHT_ONITEMICON) == 0) + goto done; + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, ht.iItem, modelColumn); + checked = uiTableDataInt(data); + uiFreeTableData(data); + data = uiNewTableDataInt(!checked); + (*(t->model->mh->SetCellValue))(t->model->mh, t->model, ht.iItem, modelColumn, data); + uiFreeTableData(data); + } else + (*(t->model->mh->SetCellValue))(t->model->mh, t->model, ht.iItem, modelColumn, NULL); + // always refresh the value in case the model rejected it + if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) (ht.iItem), 0) == (LRESULT) (-1)) { + logLastError(L"LVM_UPDATE"); + return E_FAIL; + } + +done: + *lResult = 0; + return S_OK; +} From 799c613a6faa700cb44a28bc0382cbad88753e08 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 17 Jun 2018 15:06:45 -0400 Subject: [PATCH 114/166] Added code for detecting that text is to be edited. Now for actually implementing editing text. --- windows/table.cpp | 31 +++++++++++++++++++++++++++++++ windows/table.hpp | 2 ++ windows/tableevents.cpp | 17 ++++++++++++++--- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 4dd1370d..40716fc2 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -83,9 +83,16 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData) { uiTable *t = (uiTable *) dwRefData; + LRESULT lResult; switch (uMsg) { case WM_TIMER: + if (wParam == (WPARAM) (&(t->inDoubleClickTimer))) { + t->inDoubleClickTimer = FALSE; + // TODO check errors + KillTimer(hwnd, wParam); + return 0; + } if (wParam != (WPARAM) t) break; // TODO only increment and update if visible? @@ -95,6 +102,11 @@ static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM SendMessageW(hwnd, LVM_UPDATE, (WPARAM) (i.first.first), 0); } return 0; + case WM_LBUTTONDOWN: + t->inLButtonDown = TRUE; + lResult = DefSubclassProc(hwnd, uMsg, wParam, lParam); + t->inLButtonDown = FALSE; + return lResult; case WM_NCDESTROY: if (RemoveWindowSubclass(hwnd, tableSubProc, uIDSubclass) == FALSE) logLastError(L"RemoveWindowSubclass()"); @@ -192,6 +204,25 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) } return TRUE; #endif + case LVN_ITEMCHANGED: + { + NMLISTVIEW *nm = (NMLISTVIEW *) nmhdr; + UINT oldSelected, newSelected; + + if (!t->inLButtonDown) + return FALSE; + oldSelected = nm->uOldState & LVIS_SELECTED; + newSelected = nm->uNewState & LVIS_SELECTED; + if (oldSelected == 0 && newSelected != 0) { + t->inDoubleClickTimer = TRUE; + // TODO check error + SetTimer(t->hwnd, (UINT_PTR) (&(t->inDoubleClickTimer)), + GetDoubleClickTime(), NULL); + *lResult = 0; + return TRUE; + } + return FALSE; + } } return FALSE; } diff --git a/windows/table.hpp b/windows/table.hpp index 30bea161..3cfd8fa7 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -33,6 +33,8 @@ struct uiTable { HIMAGELIST imagelist; // TODO document all this std::map, LONG> *indeterminatePositions; + BOOL inLButtonDown; + BOOL inDoubleClickTimer; }; extern int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos); diff --git a/windows/tableevents.cpp b/windows/tableevents.cpp index eae3a096..189b005c 100644 --- a/windows/tableevents.cpp +++ b/windows/tableevents.cpp @@ -7,7 +7,7 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu LVHITTESTINFO ht; uiprivTableColumnParams *p; int modelColumn, editableColumn; - bool checkbox; + bool text, checkbox; uiTableData *data; int checked, editable; @@ -18,9 +18,14 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu modelColumn = -1; editableColumn = -1; + text = false; checkbox = false; p = (*(t->columns))[ht.iSubItem]; - if (p->checkboxModelColumn != -1) { + if (p->textModelColumn != -1) { + modelColumn = p->textModelColumn; + editableColumn = p->textEditableColumn; + text = true; + } else if (p->checkboxModelColumn != -1) { modelColumn = p->checkboxModelColumn; editableColumn = p->checkboxEditableColumn; checkbox = true; @@ -31,6 +36,10 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu if (modelColumn == -1) goto done; + if (text && t->inDoubleClickTimer) + // don't even ask for info if it's too soon to edit text + goto done; + switch (editableColumn) { case uiTableModelColumnNeverEditable: goto done; @@ -44,7 +53,9 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu goto done; } - if (checkbox) { + if (text) { + MessageBoxW(NULL, L"editing text", L"ok", MB_OK); + } else if (checkbox) { if ((ht.flags & LVHT_ONITEMICON) == 0) goto done; data = (*(t->model->mh->CellValue))(t->model->mh, t->model, ht.iItem, modelColumn); From 51f98d1f7b5500d9e5b918f44c8936f82b61a9dd Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 17 Jun 2018 17:56:45 -0400 Subject: [PATCH 115/166] Started implementing the edit control for our table view. --- windows/table.hpp | 1 + windows/tableevents.cpp | 99 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/windows/table.hpp b/windows/table.hpp index 3cfd8fa7..6ab77d57 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -35,6 +35,7 @@ struct uiTable { std::map, LONG> *indeterminatePositions; BOOL inLButtonDown; BOOL inDoubleClickTimer; + HWND edit; }; extern int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos); diff --git a/windows/tableevents.cpp b/windows/tableevents.cpp index 189b005c..cceb1598 100644 --- a/windows/tableevents.cpp +++ b/windows/tableevents.cpp @@ -2,6 +2,100 @@ #include "uipriv_windows.hpp" #include "table.hpp" +// TODO deduplicate this with tabledraw.cpp +static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG left, LONG top, LRESULT bad, RECT *r) +{ + if (hr != S_OK) + return hr; + ZeroMemory(r, sizeof (RECT)); + r->left = left; + r->top = top; + if (SendMessageW(t->hwnd, uMsg, wParam, (LPARAM) r) == bad) { + logLastError(L"itemRect() message"); + return E_FAIL; + } + return S_OK; +} + +static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableColumnParams *p) +{ + RECT itemLabel; + RECT subitemBounds, subitemIcon, subitemLabel; + uiTableData *data; + WCHAR *wstr; + RECT r; + LONG xInflate, yInflate; + HRESULT hr; + + // compute this in advance so we don't have to needlessly call DestroyWindow() later + // TODO deduplicate this code with tabledraw.cpp + // TODO check LRESULT bad parameters here + hr = itemRect(S_OK, t, LVM_GETITEMRECT, iItem, + LVIR_LABEL, 0, FALSE, &itemLabel); + hr = itemRect(hr, t, LVM_GETSUBITEMRECT, iItem, + LVIR_BOUNDS, iSubItem, 0, &subitemBounds); + hr = itemRect(hr, t, LVM_GETSUBITEMRECT, iItem, + LVIR_ICON, iSubItem, 0, &subitemIcon); + if (hr != S_OK) + return hr; + // LVM_GETSUBITEMRECT treats LVIR_LABEL as the same as + // LVIR_BOUNDS, so we can't use that directly. Instead, let's + // assume the text is immediately after the icon. The correct + // rect will be determined by + // computeOtherRectsAndDrawBackgrounds() above. + subitemLabel = subitemBounds; + subitemLabel.left = subitemIcon.right; + // And on iSubItem == 0, LVIF_GETSUBITEMRECT still includes + // all the subitems, which we don't want. + if (iSubItem == 0) { + subitemBounds.right = itemLabel.right; + subitemLabel.right = itemLabel.right; + } + if ((p->imageModelColumn == -1 && p->checkboxModelColumn == -1) && iSubItem != 0) + // By default, this will include images; we're not drawing + // images, so we will manually draw over the image area. + // There's a second part to this; see below. + subitemLabel.left = subitemBounds.left; + + // the real list view creates the edit control with the string + data = (*(t->model->mh->CellValue))(t->model->mh, t->model, iItem, p->textModelColumn); + wstr = toUTF16(uiTableDataString(data)); + uiFreeTableData(data); + // TODO copy WS_EX_RTLREADING + t->edit = CreateWindowExW(0, + L"EDIT", wstr, + // these styles are what the normal listview edit uses + WS_CHILD | WS_CLIPSIBLINGS | WS_BORDER | ES_AUTOHSCROLL, + // as is this size + 0, 0, 16384, 16384, + // and this control ID + t->hwnd, (HMENU) 1, hInstance, NULL); + if (t->edit == NULL) { + logLastError(L"CreateWindowExW()"); + uiprivFree(wstr); + return E_FAIL; + } + uiprivFree(wstr); + // TODO set font here + + // and this is what the real list view does to size the edit control + SendMessageW(t->edit, EM_GETRECT, 0, (LPARAM) (&r)); + xInflate = -(GetSystemMetrics(SM_CXEDGE) + GetSystemMetrics(SM_CXBORDER)); + yInflate = -r.top; + // TODO check error + InflateRect(&subitemLabel, xInflate, yInflate); + + // TODO check error or use the right function + SetWindowPos(t->edit, NULL, + subitemLabel.left, subitemLabel.top, + subitemLabel.right - subitemLabel.left, subitemLabel.bottom - subitemLabel.top, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + // TODO get the correct constant from the real list view + ShowWindow(t->edit, SW_SHOWDEFAULT); + + return S_OK; +} + HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult) { LVHITTESTINFO ht; @@ -10,6 +104,7 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu bool text, checkbox; uiTableData *data; int checked, editable; + HRESULT hr; ZeroMemory(&ht, sizeof (LVHITTESTINFO)); ht.pt = nm->ptAction; @@ -54,7 +149,9 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu } if (text) { - MessageBoxW(NULL, L"editing text", L"ok", MB_OK); + hr = openEditControl(t, ht.iItem, ht.iSubItem, p); + if (hr != S_OK) + return hr; } else if (checkbox) { if ((ht.flags & LVHT_ONITEMICON) == 0) goto done; From d81b3653964fe00e2084a12f240b41ea09cab5fe Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 17 Jun 2018 21:22:57 -0400 Subject: [PATCH 116/166] More edit control stuff. We've almost got it, but it's too wide and the text is still aligned wrong. --- windows/tableevents.cpp | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/windows/tableevents.cpp b/windows/tableevents.cpp index cceb1598..ac043fd1 100644 --- a/windows/tableevents.cpp +++ b/windows/tableevents.cpp @@ -76,7 +76,7 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC return E_FAIL; } uiprivFree(wstr); - // TODO set font here + SendMessageW(t->edit, WM_SETFONT, (WPARAM) hMessageFont, (LPARAM) TRUE); // and this is what the real list view does to size the edit control SendMessageW(t->edit, EM_GETRECT, 0, (LPARAM) (&r)); @@ -85,10 +85,42 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC // TODO check error InflateRect(&subitemLabel, xInflate, yInflate); + // TODO rewrite and integrate the variables of this; I'm not fully comfortable keeping it as it is + // TODO check errors everywhere + { + HDC dc; + HFONT prevFont; + RECT textRect, editRect; + int cxIconSpacing; + LONG offsetY; + + // yes, the list view control uses the list view DC for this + dc = GetDC(t->hwnd); + prevFont = (HFONT) SelectObject(dc, hMessageFont); + ZeroMemory(&textRect, sizeof (RECT)); + cxIconSpacing = GetSystemMetrics(SM_CXICONSPACING); + textRect.right = cxIconSpacing - 2 * GetSystemMetrics(SM_CXEDGE); + // yes, the real edit control uses DT_CENTER for some reason + // TODO the real edit control filters out certain types of characters but I'm not sure what + DrawTextW(dc, wstr, -1, &textRect, DT_CENTER | DT_NOPREFIX | DT_SINGLELINE | DT_EDITCONTROL | DT_CALCRECT); + ReleaseDC(t->hwnd, dc); + if (textRect.right < cxIconSpacing / 4) + textRect.right = cxIconSpacing / 4; + offsetY = subitemLabel.top + ((textRect.bottom - textRect.top) - (subitemLabel.bottom - subitemLabel.top)) / 2; + OffsetRect(&textRect, subitemLabel.left, offsetY); + textRect.right += 4 * GetSystemMetrics(SM_CXEDGE) + GetSystemMetrics(SM_CYEDGE); + SendMessageW(t->edit, EM_GETRECT, 0, (LPARAM) (&editRect)); + editRect.left = -editRect.left; + editRect.top = -editRect.top; + AdjustWindowRectEx(&editRect, getStyle(t->edit), FALSE, getExStyle(t->edit)); + r = textRect; + InflateRect(&r, -editRect.left, -editRect.top); + } + // TODO check error or use the right function SetWindowPos(t->edit, NULL, - subitemLabel.left, subitemLabel.top, - subitemLabel.right - subitemLabel.left, subitemLabel.bottom - subitemLabel.top, + r.left, r.top, + r.right - r.left, r.bottom - r.top, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); // TODO get the correct constant from the real list view ShowWindow(t->edit, SW_SHOWDEFAULT); From 1edb406045ce77f4b729789decd40296beb28cc6 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 18 Jun 2018 09:38:34 -0400 Subject: [PATCH 117/166] Let's start over with this edit control sizing stuff. --- windows/tableevents.cpp | 56 +++++++++++++---------------------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/windows/tableevents.cpp b/windows/tableevents.cpp index ac043fd1..a73055c8 100644 --- a/windows/tableevents.cpp +++ b/windows/tableevents.cpp @@ -56,7 +56,23 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC // images, so we will manually draw over the image area. // There's a second part to this; see below. subitemLabel.left = subitemBounds.left; +/* + if ((p->imageModelColumn != -1 || p->checkboxModelColumn != -1) && iSubItem != 0) + // Normally there's this many hard-coded logical units + // of blank space, followed by the background, followed + // by a bitmap margin's worth of space. This looks bad, + // so we overrule that to start the background immediately + // and the text after the hard-coded amount. + subitemLabel.left += 2; + else if (iSubItem != 0) { + HWND header; + // In the case of subitem text without an image, we draw + // text one bitmap margin away from the left edge. + header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); + subitemLabel.left += SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); + } +*/ // the real list view creates the edit control with the string data = (*(t->model->mh->CellValue))(t->model->mh, t->model, iItem, p->textModelColumn); wstr = toUTF16(uiTableDataString(data)); @@ -78,44 +94,8 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC uiprivFree(wstr); SendMessageW(t->edit, WM_SETFONT, (WPARAM) hMessageFont, (LPARAM) TRUE); - // and this is what the real list view does to size the edit control - SendMessageW(t->edit, EM_GETRECT, 0, (LPARAM) (&r)); - xInflate = -(GetSystemMetrics(SM_CXEDGE) + GetSystemMetrics(SM_CXBORDER)); - yInflate = -r.top; - // TODO check error - InflateRect(&subitemLabel, xInflate, yInflate); - - // TODO rewrite and integrate the variables of this; I'm not fully comfortable keeping it as it is - // TODO check errors everywhere - { - HDC dc; - HFONT prevFont; - RECT textRect, editRect; - int cxIconSpacing; - LONG offsetY; - - // yes, the list view control uses the list view DC for this - dc = GetDC(t->hwnd); - prevFont = (HFONT) SelectObject(dc, hMessageFont); - ZeroMemory(&textRect, sizeof (RECT)); - cxIconSpacing = GetSystemMetrics(SM_CXICONSPACING); - textRect.right = cxIconSpacing - 2 * GetSystemMetrics(SM_CXEDGE); - // yes, the real edit control uses DT_CENTER for some reason - // TODO the real edit control filters out certain types of characters but I'm not sure what - DrawTextW(dc, wstr, -1, &textRect, DT_CENTER | DT_NOPREFIX | DT_SINGLELINE | DT_EDITCONTROL | DT_CALCRECT); - ReleaseDC(t->hwnd, dc); - if (textRect.right < cxIconSpacing / 4) - textRect.right = cxIconSpacing / 4; - offsetY = subitemLabel.top + ((textRect.bottom - textRect.top) - (subitemLabel.bottom - subitemLabel.top)) / 2; - OffsetRect(&textRect, subitemLabel.left, offsetY); - textRect.right += 4 * GetSystemMetrics(SM_CXEDGE) + GetSystemMetrics(SM_CYEDGE); - SendMessageW(t->edit, EM_GETRECT, 0, (LPARAM) (&editRect)); - editRect.left = -editRect.left; - editRect.top = -editRect.top; - AdjustWindowRectEx(&editRect, getStyle(t->edit), FALSE, getExStyle(t->edit)); - r = textRect; - InflateRect(&r, -editRect.left, -editRect.top); - } + // TODO + r = subitemLabel; // TODO check error or use the right function SetWindowPos(t->edit, NULL, From 4c107997d259a3a277f049e5e2e11cbbb73fdeed Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 18 Jun 2018 23:51:58 -0400 Subject: [PATCH 118/166] Round two: fully custom edit sizing code. We're getting somewhere now. Now we'll need to actually manage this thing =P --- windows/tableevents.cpp | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/windows/tableevents.cpp b/windows/tableevents.cpp index a73055c8..4441da2e 100644 --- a/windows/tableevents.cpp +++ b/windows/tableevents.cpp @@ -56,7 +56,6 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC // images, so we will manually draw over the image area. // There's a second part to this; see below. subitemLabel.left = subitemBounds.left; -/* if ((p->imageModelColumn != -1 || p->checkboxModelColumn != -1) && iSubItem != 0) // Normally there's this many hard-coded logical units // of blank space, followed by the background, followed @@ -72,7 +71,7 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); subitemLabel.left += SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); } -*/ + // the real list view creates the edit control with the string data = (*(t->model->mh->CellValue))(t->model->mh, t->model, iItem, p->textModelColumn); wstr = toUTF16(uiTableDataString(data)); @@ -91,11 +90,38 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC uiprivFree(wstr); return E_FAIL; } - uiprivFree(wstr); SendMessageW(t->edit, WM_SETFONT, (WPARAM) hMessageFont, (LPARAM) TRUE); - // TODO - r = subitemLabel; + // this is not how the real list view positions and sizes the edit control, but this is a) close enough b) a lot easier to follow c) something I can actually get working d) something I'm slightly more comfortable including in libui + { + HDC dc; + HFONT prevFont; + TEXTMETRICW tm; + SIZE textSize; + RECT editRect; + + // TODO deduplicate this with tabledraw.cpp + // TODO check errors for all these + dc = GetDC(t->hwnd); // yes, real list view uses itself here + prevFont = (HFONT) SelectObject(dc, hMessageFont); + GetTextMetricsW(dc, &tm); + GetTextExtentPoint32W(dc, wstr, wcslen(wstr), &textSize); + SelectObject(dc, prevFont); + ReleaseDC(t->hwnd, dc); + + SendMessageW(t->edit, EM_GETRECT, 0, (LPARAM) (&editRect)); + r.left = subitemLabel.left - editRect.left; + // find the top of the text + r.top = subitemLabel.top + ((subitemLabel.bottom - subitemLabel.top) - tm.tmHeight) / 2; + // and move THAT by the right offset + r.top = r.top - editRect.top; + r.right = subitemLabel.left + textSize.cx; + // the real listview does this to add some extra space at the end + // TODO this still isn't enough space + r.right += 4 * GetSystemMetrics(SM_CXEDGE) + GetSystemMetrics(SM_CYEDGE); + // and make the bottom equally positioned to the top + r.bottom = r.top + editRect.top + tm.tmHeight + editRect.top; + } // TODO check error or use the right function SetWindowPos(t->edit, NULL, @@ -105,6 +131,7 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC // TODO get the correct constant from the real list view ShowWindow(t->edit, SW_SHOWDEFAULT); + uiprivFree(wstr); return S_OK; } From ff4b424ab001d77d4c5dec512f5b752111ba25aa Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 19 Jun 2018 07:51:34 -0400 Subject: [PATCH 119/166] Added WS_CLIPCHILDREN to uiTable to prevent drawing over children. --- windows/table.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/windows/table.cpp b/windows/table.cpp index 40716fc2..b3b397e8 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -394,9 +394,10 @@ uiTable *uiNewTable(uiTableModel *model) t->columns = new std::vector; t->model = model; + // WS_CLIPCHILDREN is here to prevent drawing over the edit box used for editing text t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, WC_LISTVIEW, L"", - LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL | WS_TABSTOP | WS_HSCROLL | WS_VSCROLL, + LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL | WS_CLIPCHILDREN | WS_TABSTOP | WS_HSCROLL | WS_VSCROLL, hInstance, NULL, TRUE); model->tables->push_back(t); From 25a443f4f2d89c7dfea4ef244052d73dd04e91c8 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 19 Jun 2018 23:07:24 -0400 Subject: [PATCH 120/166] Handled the finished-editing cases I can right now. It is... mostly good???????????? IListView is very tantalizing now... --- windows/table.cpp | 98 ++++++++++++++++++++++++++++++++++++++++- windows/table.hpp | 4 ++ windows/tableevents.cpp | 61 +++++++++++++++++++++++++ 3 files changed, 161 insertions(+), 2 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index b3b397e8..5704b189 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -3,6 +3,7 @@ // general TODOs: // - tooltips don't work properly on columns with icons (the listview always thinks there's enough room for a short label because it's not taking the icon into account); is this a bug in our LVN_GETDISPINFO handler or something else? +// - should clicking on some other column of the same row, even one that doesn't edit, cancel editing? static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { /*TODO.ColorModelColumn = */-1, @@ -83,8 +84,14 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData) { uiTable *t = (uiTable *) dwRefData; + NMHDR *nmhdr = (NMHDR *) lParam; + bool finishEdit, abortEdit; + HWND header; LRESULT lResult; + HRESULT hr; + finishEdit = false; + abortEdit = false; switch (uMsg) { case WM_TIMER: if (wParam == (WPARAM) (&(t->inDoubleClickTimer))) { @@ -107,11 +114,69 @@ static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lResult = DefSubclassProc(hwnd, uMsg, wParam, lParam); t->inLButtonDown = FALSE; return lResult; + case WM_COMMAND: + if (HIWORD(wParam) == EN_UPDATE) { + // the real list view resizes the edit control on this notification specifically + // TODO + } + // the real list view accepts changes in this case + if (HIWORD(wParam) == EN_KILLFOCUS) + finishEdit = true; + break; // don't override default handling + case WM_NOTIFY: + // list view accepts changes on column resize, but does not provide such notifications :/ + header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); + if (nmhdr->hwndFrom == header) { + NMHEADERW *nm = (NMHEADERW *) nmhdr; + + switch (nmhdr->code) { + case HDN_ITEMCHANGED: + if ((nm->pitem->mask & HDI_WIDTH) == 0) + break; + // fall through + case HDN_DIVIDERDBLCLICK: + case HDN_TRACK: + case HDN_ENDTRACK: + finishEdit = true; + } + } + // I think this mirrors the WM_COMMAND one above... TODO + if (nmhdr->code == NM_KILLFOCUS) + finishEdit = true; + break; // don't override default handling + case LVM_CANCELEDITLABEL: + finishEdit = true; + // TODO properly imitate notifiactions + break; // don't override default handling + // TODO finish edit on WM_WINDOWPOSCHANGING and WM_SIZE? + // for the next three: this item is about to go away; don't bother keeping changes + case LVM_SETITEMCOUNT: + if (wParam <= t->editedItem) + abortEdit = true; + break; // don't override default handling + case LVM_DELETEITEM: + if (wParam == t->editedItem) + abortEdit = true; + break; // don't override default handling + case LVM_DELETEALLITEMS: + abortEdit = true; + break; // don't override default handling case WM_NCDESTROY: if (RemoveWindowSubclass(hwnd, tableSubProc, uIDSubclass) == FALSE) logLastError(L"RemoveWindowSubclass()"); // fall through } + if (finishEdit) { + hr = uiprivTableFinishEditingText(t); + if (hr != S_OK) { + // TODO + } + } else if (abortEdit) { + hr = uiprivTableAbortEditingText(t); + if (hr != S_OK) { + // TODO + } + } return DefSubclassProc(hwnd, uMsg, wParam, lParam); } @@ -157,6 +222,7 @@ int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG return progress; } +// TODO properly integrate compound statements static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) { uiTable *t = uiTable(c); @@ -208,12 +274,14 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) { NMLISTVIEW *nm = (NMLISTVIEW *) nmhdr; UINT oldSelected, newSelected; + HRESULT hr; - if (!t->inLButtonDown) + // TODO clean up these if cases + if (!t->inLButtonDown && t->edit == NULL) return FALSE; oldSelected = nm->uOldState & LVIS_SELECTED; newSelected = nm->uNewState & LVIS_SELECTED; - if (oldSelected == 0 && newSelected != 0) { + if (t->inLButtonDown && oldSelected == 0 && newSelected != 0) { t->inDoubleClickTimer = TRUE; // TODO check error SetTimer(t->hwnd, (UINT_PTR) (&(t->inDoubleClickTimer)), @@ -221,8 +289,29 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) *lResult = 0; return TRUE; } + // the nm->iItem == -1 case is because that is used if "the change has been applied to all items in the list view" + if (t->edit != NULL && oldSelected != 0 && newSelected == 0 && (t->editedItem == nm->iItem || nm->iItem == -1)) { + // TODO see if the real list view accepts or rejects changes here; Windows Explorer accepts + hr = uiprivTableFinishEditingText(t); + if (hr != S_OK) { + // TODO + return FALSE; + } + *lResult = 0; + return TRUE; + } return FALSE; } + // the real list view accepts changes when scrolling or clicking column headers + case LVN_BEGINSCROLL: + case LVN_COLUMNCLICK: + hr = uiprivTableFinishEditingText(t); + if (hr != S_OK) { + // TODO + return FALSE; + } + *lResult = 0; + return TRUE; } return FALSE; } @@ -232,7 +321,12 @@ static void uiTableDestroy(uiControl *c) uiTable *t = uiTable(c); uiTableModel *model = t->model; std::vector::iterator it; + HRESULT hr; + hr = uiprivTableAbortEditingText(t); + if (hr != S_OK) { + // TODO + } uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); uiWindowsEnsureDestroyWindow(t->hwnd); // detach table from model diff --git a/windows/table.hpp b/windows/table.hpp index 6ab77d57..67b166f8 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -36,6 +36,8 @@ struct uiTable { BOOL inLButtonDown; BOOL inDoubleClickTimer; HWND edit; + int editedItem; + int editedSubitem; }; extern int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos); @@ -48,3 +50,5 @@ extern HRESULT uiprivUpdateImageListSize(uiTable *t); // tableevents.cpp extern HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult); +extern HRESULT uiprivTableFinishEditingText(uiTable *t); +extern HRESULT uiprivTableAbortEditingText(uiTable *t); diff --git a/windows/tableevents.cpp b/windows/tableevents.cpp index 4441da2e..d77e1ab6 100644 --- a/windows/tableevents.cpp +++ b/windows/tableevents.cpp @@ -17,6 +17,41 @@ static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG l return S_OK; } +// the real list view intercepts these keys to control editing +static LRESULT CALLBACK editSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData) +{ + uiTable *t = (uiTable *) dwRefData; + HRESULT hr; + + switch (uMsg) { + case WM_KEYDOWN: + switch (wParam) { + // TODO handle VK_TAB and VK_SHIFT+VK_TAB + case VK_RETURN: + hr = uiprivTableFinishEditingText(t); + if (hr != S_OK) { + // TODO + } + return 0; // yes, the real list view just returns here + case VK_ESCAPE: + hr = uiprivTableAbortEditingText(t); + if (hr != S_OK) { + // TODO + } + return 0; + } + break; + // the real list view also forces these flags + case WM_GETDLGCODE: + return DLGC_HASSETSEL | DLGC_WANTALLKEYS; + case WM_NCDESTROY: + if (RemoveWindowSubclass(hwnd, editSubProc, uIDSubclass) == FALSE) + logLastError(L"RemoveWindowSubclass()"); + // fall through + } + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} + static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableColumnParams *p) { RECT itemLabel; @@ -27,6 +62,11 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC LONG xInflate, yInflate; HRESULT hr; + // the real list view accepts changes to the existing item when editing a new item + hr = uiprivTableFinishEditingText(t); + if (hr != S_OK) + return hr; + // compute this in advance so we don't have to needlessly call DestroyWindow() later // TODO deduplicate this code with tabledraw.cpp // TODO check LRESULT bad parameters here @@ -132,6 +172,27 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC ShowWindow(t->edit, SW_SHOWDEFAULT); uiprivFree(wstr); + t->editedItem = iItem; + t->editedSubitem = iSubItem; + return S_OK; +} + +HRESULT uiprivTableFinishEditingText(uiTable *t) +{ + if (t->edit == NULL) + return S_OK; + return uiprivTableAbortEditingText(t); +} + +HRESULT uiprivTableAbortEditingText(uiTable *t) +{ + if (t->edit == NULL) + return S_OK; + if (DestroyWindow(t->edit) == 0) { + logLastError(L"DestroyWindow()"); + return E_FAIL; + } + t->edit = NULL; return S_OK; } From bff9d0e31144898ee5548a944890c359a82033b3 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 20 Jun 2018 10:39:27 -0400 Subject: [PATCH 121/166] More work on table edit controls, including: subclassing the edit control to actually handle escape and enter, setting focus on the eidt control, selecting all text in the edit control, and splitting the sizing stuff into a separate function. We'll have to split the rect-gathering code into a separate file before we can add live resize to the edit control... which will probably be useful because then I could just write a function to enumerate focus rects later. --- windows/tableevents.cpp | 89 +++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/windows/tableevents.cpp b/windows/tableevents.cpp index d77e1ab6..b8582fba 100644 --- a/windows/tableevents.cpp +++ b/windows/tableevents.cpp @@ -17,6 +17,47 @@ static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG l return S_OK; } +// this is not how the real list view positions and sizes the edit control, but this is a) close enough b) a lot easier to follow c) something I can actually get working d) something I'm slightly more comfortable including in libui +// r should be the subitem label rect +static HRESULT resizeEdit(uiTable *t, WCHAR *wstr, RECT *r) +{ + HDC dc; + HFONT prevFont; + TEXTMETRICW tm; + SIZE textSize; + RECT editRect; + + // TODO check errors for all these + dc = GetDC(t->hwnd); // use the list view DC since we're using its coordinate space + prevFont = (HFONT) SelectObject(dc, hMessageFont); + GetTextMetricsW(dc, &tm); + GetTextExtentPoint32W(dc, wstr, wcslen(wstr), &textSize); + SelectObject(dc, prevFont); + ReleaseDC(t->hwnd, dc); + + SendMessageW(t->edit, EM_GETRECT, 0, (LPARAM) (&editRect)); + r->left -= editRect.left; + // find the top of the text + r->top += ((r->bottom - r->top) - tm.tmHeight) / 2; + // and move THAT by the right offset + r->top -= editRect.top; + r->right = r->left + textSize.cx; + // the real listview does this to add some extra space at the end + // TODO this still isn't enough space + r->right += 4 * GetSystemMetrics(SM_CXEDGE) + GetSystemMetrics(SM_CYEDGE); + // and make the bottom equally positioned to the top + r->bottom = r->top + editRect.top + tm.tmHeight + editRect.top; + + // TODO intersect r with the list view's client rect to prevent clipping + + // TODO check error or use the right function + SetWindowPos(t->edit, NULL, + r->left, r->top, + r->right - r->left, r->bottom - r->top, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + return S_OK; +} + // the real list view intercepts these keys to control editing static LRESULT CALLBACK editSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData) { @@ -131,45 +172,17 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC return E_FAIL; } SendMessageW(t->edit, WM_SETFONT, (WPARAM) hMessageFont, (LPARAM) TRUE); + // TODO check errors + SetWindowSubclass(t->edit, editSubProc, 0, (DWORD_PTR) t); - // this is not how the real list view positions and sizes the edit control, but this is a) close enough b) a lot easier to follow c) something I can actually get working d) something I'm slightly more comfortable including in libui - { - HDC dc; - HFONT prevFont; - TEXTMETRICW tm; - SIZE textSize; - RECT editRect; - - // TODO deduplicate this with tabledraw.cpp - // TODO check errors for all these - dc = GetDC(t->hwnd); // yes, real list view uses itself here - prevFont = (HFONT) SelectObject(dc, hMessageFont); - GetTextMetricsW(dc, &tm); - GetTextExtentPoint32W(dc, wstr, wcslen(wstr), &textSize); - SelectObject(dc, prevFont); - ReleaseDC(t->hwnd, dc); - - SendMessageW(t->edit, EM_GETRECT, 0, (LPARAM) (&editRect)); - r.left = subitemLabel.left - editRect.left; - // find the top of the text - r.top = subitemLabel.top + ((subitemLabel.bottom - subitemLabel.top) - tm.tmHeight) / 2; - // and move THAT by the right offset - r.top = r.top - editRect.top; - r.right = subitemLabel.left + textSize.cx; - // the real listview does this to add some extra space at the end - // TODO this still isn't enough space - r.right += 4 * GetSystemMetrics(SM_CXEDGE) + GetSystemMetrics(SM_CYEDGE); - // and make the bottom equally positioned to the top - r.bottom = r.top + editRect.top + tm.tmHeight + editRect.top; - } - - // TODO check error or use the right function - SetWindowPos(t->edit, NULL, - r.left, r.top, - r.right - r.left, r.bottom - r.top, - SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); - // TODO get the correct constant from the real list view - ShowWindow(t->edit, SW_SHOWDEFAULT); + hr = resizeEdit(t, wstr, &subitemLabel); + if (hr != S_OK) + // TODO proper cleanup + return hr; + // TODO check errors on these two, if any + SetFocus(t->edit); + ShowWindow(t->edit, SW_SHOW); + SendMessageW(t->edit, EM_SETSEL, 0, (LPARAM) (-1)); uiprivFree(wstr); t->editedItem = iItem; From df59eee7834451bbc8762cea8b5c107e1b028e5b Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 20 Jun 2018 10:45:14 -0400 Subject: [PATCH 122/166] Renamed tableevents.cpp to the more accurate tableediting.cpp. --- windows/CMakeLists.txt | 2 +- windows/table.hpp | 2 +- windows/{tableevents.cpp => tableediting.cpp} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename windows/{tableevents.cpp => tableediting.cpp} (100%) diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index c1747c50..bec8a2f6 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -53,7 +53,7 @@ list(APPEND _LIBUI_SOURCES windows/table.cpp windows/tabledispinfo.cpp windows/tabledraw.cpp - windows/tableevents.cpp + windows/tableediting.cpp windows/tabpage.cpp windows/text.cpp windows/utf16.cpp diff --git a/windows/table.hpp b/windows/table.hpp index 67b166f8..2a210a8c 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -48,7 +48,7 @@ extern HRESULT uiprivTableHandleLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm, L extern HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult); extern HRESULT uiprivUpdateImageListSize(uiTable *t); -// tableevents.cpp +// tableediting.cpp extern HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult); extern HRESULT uiprivTableFinishEditingText(uiTable *t); extern HRESULT uiprivTableAbortEditingText(uiTable *t); diff --git a/windows/tableevents.cpp b/windows/tableediting.cpp similarity index 100% rename from windows/tableevents.cpp rename to windows/tableediting.cpp From ec07b12295b8024b2036d4c1d8dd6209f42b7ebf Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 20 Jun 2018 11:05:34 -0400 Subject: [PATCH 123/166] Split table cell metrics into its own file. We still need to actually integrate this with everything. --- windows/tablemetrics.cpp | 135 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 windows/tablemetrics.cpp diff --git a/windows/tablemetrics.cpp b/windows/tablemetrics.cpp new file mode 100644 index 00000000..76c879c7 --- /dev/null +++ b/windows/tablemetrics.cpp @@ -0,0 +1,135 @@ +// 14 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +struct uiprivTableMetrics { + uiTable *t; + uiTableModel *m; + uiprivTableColumnParams *p; + + int iItem; + int iSubItem; + BOOL hasText; + BOOL hasImage; + BOOL focused; + BOOL selected; + + RECT itemBounds; + RECT itemIcon; + RECT itemLabel; + RECT subitemBounds; + RECT subitemIcon; + RECT subitemLabel; + + LRESULT bitmapMargin; + int cxIcon; + int cyIcon; + + RECT realTextBackground; + RECT realTextRect; +}; + +static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG left, LONG top, LRESULT bad, RECT *r) +{ + if (hr != S_OK) + return hr; + ZeroMemory(r, sizeof (RECT)); + r->left = left; + r->top = top; + if (SendMessageW(t->hwnd, uMsg, wParam, (LPARAM) r) == bad) { + logLastError(L"itemRect() message"); + return E_FAIL; + } + return S_OK; +} + +HRESULT uiprivTableGetMetrics(uiTable *t, int iItem, int iSubItem, uiprivTableMetrics **mout) +{ + uiprivTableMetrics *m; + LRESULT state; + HWND header; + RECT r; + HRESULT hr; + + if (mout == NULL) + return E_POINTER; + + m = uiprivNew(uiprivTableMetrics); + m->t = t; + m->m = t->model; + m->p = (*(t->columns))[iSubItem]; + + m->iItem = iItem; + m->iSubItem = iSubItem; + m->hasText = m->p->textModelColumn != -1; + m->hasImage = (m->p->imageModelColumn != -1) || (m->p->checkboxModelColumn != -1); + state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, iItem, LVIS_FOCUSED | LVIS_SELECTED); + m->focused = (state & LVIS_FOCUSED) != 0; + m->selected = (state & LVIS_SELECTED) != 0; + + // TODO check LRESULT bad parameters here + hr = itemRect(S_OK, t, LVM_GETITEMRECT, s->iItem, + LVIR_BOUNDS, 0, FALSE, &(m->itemBounds)); + hr = itemRect(hr, t, LVM_GETITEMRECT, s->iItem, + LVIR_ICON, 0, FALSE, &(m->itemIcon)); + hr = itemRect(hr, t, LVM_GETITEMRECT, s->iItem, + LVIR_LABEL, 0, FALSE, &(m->itemLabel)); + hr = itemRect(hr, t, LVM_GETSUBITEMRECT, s->iItem, + LVIR_BOUNDS, s->iSubItem, 0, &(m->subitemBounds)); + hr = itemRect(hr, t, LVM_GETSUBITEMRECT, s->iItem, + LVIR_ICON, s->iSubItem, 0, &(m->subitemIcon)); + if (hr != S_OK) + goto fail; + // LVM_GETSUBITEMRECT treats LVIR_LABEL as the same as + // LVIR_BOUNDS, so we can't use that directly. Instead, let's + // assume the text is immediately after the icon. The correct + // rect will be determined by + // computeOtherRectsAndDrawBackgrounds() above. + m->subitemLabel = m->subitemBounds; + m->subitemLabel.left = m->subitemIcon.right; + // And on iSubItem == 0, LVIF_GETSUBITEMRECT still includes + // all the subitems, which we don't want. + if (iSubItem == 0) { + m->subitemBounds.right = m->itemLabel.right; + m->subitemLabel.right = m->itemLabel.right; + } + + header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); + m->bitmapMargin = SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); + if (ImageList_GetIconSize(t->imagelist, &(m->cxIcon), &(m->cyIcon)) == 0) { + logLastError(L"ImageList_GetIconSize()"); + hr = E_FAIL; + goto fail; + } + + r = m->subitemLabel; + if (!m->hasText && !m->hasImage) + r = s->subitemBounds; + else if (!m->hasImage && iSubItem != 0) + // By default, this will include images; we're not drawing + // images, so we will manually draw over the image area. + // There's a second part to this; see below. + r.left = m->subitemBounds.left; + m->realTextBackground = r; + + m->realTextRect = r; + // TODO confirm whether this really happens on column 0 as well + if (m->hasImage && iSubItem != 0) + // Normally there's this many hard-coded logical units + // of blank space, followed by the background, followed + // by a bitmap margin's worth of space. This looks bad, + // so we overrule that to start the background immediately + // and the text after the hard-coded amount. + m->realTextRect.left += 2; + else if (iSubItem != 0) + // In the case of subitem text without an image, we draw + // text one bitmap margin away from the left edge. + m->realTextRect.left += m->bitmapMargin; + + *mout = m; + return S_OK; +fail: + uiprivFree(m); + *mout = NULL; + return hr; +} From 5ae45a1fcb8e7e6832882dcc72b1354cd8ec53b2 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 20 Jun 2018 18:03:56 -0400 Subject: [PATCH 124/166] Integrated tablemetrics.cpp with tabledraw.cpp. --- windows/CMakeLists.txt | 1 + windows/table.hpp | 25 ++++++ windows/tabledraw.cpp | 167 ++++++++++----------------------------- windows/tablemetrics.cpp | 54 +++---------- 4 files changed, 78 insertions(+), 169 deletions(-) diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index bec8a2f6..ac9af411 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -54,6 +54,7 @@ list(APPEND _LIBUI_SOURCES windows/tabledispinfo.cpp windows/tabledraw.cpp windows/tableediting.cpp + windows/tablemetrics.cpp windows/tabpage.cpp windows/text.cpp windows/utf16.cpp diff --git a/windows/table.hpp b/windows/table.hpp index 2a210a8c..c1de0393 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -34,6 +34,7 @@ struct uiTable { // TODO document all this std::map, LONG> *indeterminatePositions; BOOL inLButtonDown; + // TODO is this even necessary? it seems NM_CLICK is not sent if NM_DBLCLICK or LVN_ITEMACTIVATE (one of the two) happens... BOOL inDoubleClickTimer; HWND edit; int editedItem; @@ -52,3 +53,27 @@ extern HRESULT uiprivUpdateImageListSize(uiTable *t); extern HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult); extern HRESULT uiprivTableFinishEditingText(uiTable *t); extern HRESULT uiprivTableAbortEditingText(uiTable *t); + +// tablemetrics.cpp +typedef struct uiprivTableMetrics uiprivTableMetrics; +struct uiprivTableMetrics { + BOOL hasText; + BOOL hasImage; + BOOL focused; + BOOL selected; + + RECT itemBounds; + RECT itemIcon; + RECT itemLabel; + RECT subitemBounds; + RECT subitemIcon; + RECT subitemLabel; + + LRESULT bitmapMargin; + int cxIcon; + int cyIcon; + + RECT realTextBackground; + RECT realTextRect; +}; +extern HRESULT uiprivTableGetMetrics(uiTable *t, int iItem, int iSubItem, uiprivTableMetrics **mout); diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index ca1982f8..64044455 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -2,25 +2,19 @@ #include "uipriv_windows.hpp" #include "table.hpp" +// TODO +#define cellValue(model, row, column) ((*((model)->mh->CellValue))((model)->mh, (model), (row), (column))) + struct drawState { uiTable *t; - uiTableModel *m; + uiTableModel *model; uiprivTableColumnParams *p; HDC dc; int iItem; int iSubItem; - BOOL hasText; - BOOL hasImage; - BOOL selected; - BOOL focused; - RECT itemBounds; - RECT itemIcon; - RECT itemLabel; - RECT subitemBounds; - RECT subitemIcon; - RECT subitemLabel; + uiprivTableMetrics *m; COLORREF bgColor; HBRUSH bgBrush; @@ -28,52 +22,19 @@ struct drawState { COLORREF textColor; HBRUSH textBrush; BOOL freeTextBrush; - - LRESULT bitmapMargin; - int cxIcon; - int cyIcon; - - RECT realTextRect; - RECT focusRect; }; -static HRESULT computeOtherRectsAndDrawBackgrounds(struct drawState *s) +static HRESULT drawBackgrounds(struct drawState *s) { - RECT r; - - r = s->subitemLabel; - if (!s->hasText && !s->hasImage) - r = s->subitemBounds; - else if (!s->hasImage && s->iSubItem != 0) - // By default, this will include images; we're not drawing - // images, so we will manually draw over the image area. - // There's a second part to this; see below. - r.left = s->subitemBounds.left; - - if (s->hasImage) - if (FillRect(s->dc, &(s->subitemIcon), GetSysColorBrush(COLOR_WINDOW)) == 0) { + if (s->m->hasImage) + if (FillRect(s->dc, &(s->m->subitemIcon), GetSysColorBrush(COLOR_WINDOW)) == 0) { logLastError(L"FillRect() icon"); return E_FAIL; } - if (FillRect(s->dc, &r, s->bgBrush) == 0) { + if (FillRect(s->dc, &(s->m->realTextBackground), s->bgBrush) == 0) { logLastError(L"FillRect()"); return E_FAIL; } - UnionRect(&(s->focusRect), &(s->focusRect), &r); - - s->realTextRect = r; - // TODO confirm whether this really happens on column 0 as well - if (s->hasImage && s->iSubItem != 0) - // Normally there's this many hard-coded logical units - // of blank space, followed by the background, followed - // by a bitmap margin's worth of space. This looks bad, - // so we overrule that to start the background immediately - // and the text after the hard-coded amount. - s->realTextRect.left += 2; - else if (s->iSubItem != 0) - // In the case of subitem text without an image, we draw - // text one bitmap margin away from the left edge. - s->realTextRect.left += s->bitmapMargin; return S_OK; } @@ -110,11 +71,11 @@ static HRESULT drawImagePart(struct drawState *s) if (s->p->imageModelColumn == -1) return S_OK; - data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->imageModelColumn); + data = cellValue(s->model, s->iItem, s->p->imageModelColumn); wb = uiprivImageAppropriateForDC(uiTableDataImage(data), s->dc); uiFreeTableData(data); - hr = uiprivWICToGDI(wb, s->dc, s->cxIcon, s->cyIcon, &b); + hr = uiprivWICToGDI(wb, s->dc, s->m->cxIcon, s->m->cyIcon, &b); if (hr != S_OK) return hr; // TODO rewrite this condition to make more sense; possibly swap the if and else blocks too @@ -132,12 +93,12 @@ static HRESULT drawImagePart(struct drawState *s) // TODO error check DeleteObject(b); - r = s->subitemIcon; - r.right = r.left + s->cxIcon; - r.bottom = r.top + s->cyIcon; - centerImageRect(&r, &(s->subitemIcon)); + r = s->m->subitemIcon; + r.right = r.left + s->m->cxIcon; + r.bottom = r.top + s->m->cyIcon; + centerImageRect(&r, &(s->m->subitemIcon)); fStyle = ILD_NORMAL; - if (s->selected) + if (s->m->selected) fStyle = ILD_SELECTED; if (ImageList_Draw(s->t->imagelist, 0, s->dc, r.left, r.top, fStyle) == 0) { @@ -156,7 +117,7 @@ static HRESULT drawUnthemedCheckbox(struct drawState *s, int checked, int enable RECT r; UINT state; - r = s->subitemIcon; + r = s->m->subitemIcon; // this is what the actual list view LVS_EX_CHECKBOXES code does to size the checkboxes // TODO reverify the initial size r.right = r.left + GetSystemMetrics(SM_CXSMICON); @@ -168,7 +129,7 @@ static HRESULT drawUnthemedCheckbox(struct drawState *s, int checked, int enable r.right++; r.bottom++; - centerImageRect(&r, &(s->subitemIcon)); + centerImageRect(&r, &(s->m->subitemIcon)); state = DFCS_BUTTONCHECK | DFCS_FLAT; if (checked) state |= DFCS_CHECKED; @@ -195,11 +156,11 @@ static HRESULT drawThemedCheckbox(struct drawState *s, HTHEME theme, int checked logHRESULT(L"GetThemePartSize()", hr); return hr; // TODO fall back? } - r = s->subitemIcon; + r = s->m->subitemIcon; r.right = r.left + size.cx; r.bottom = r.top + size.cy; - centerImageRect(&r, &(s->subitemIcon)); + centerImageRect(&r, &(s->m->subitemIcon)); if (!checked && enabled) state = CBS_UNCHECKEDNORMAL; else if (checked && enabled) @@ -228,7 +189,7 @@ static HRESULT drawCheckboxPart(struct drawState *s) if (s->p->checkboxModelColumn == -1) return S_OK; - data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->checkboxModelColumn); + data = cellValue(s->model, s->iItem, s->p->checkboxModelColumn); checked = uiTableDataInt(data); uiFreeTableData(data); switch (s->p->checkboxEditableColumn) { @@ -239,7 +200,7 @@ static HRESULT drawCheckboxPart(struct drawState *s) enabled = 1; break; default: - data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->checkboxEditableColumn); + data = cellValue(s->model, s->iItem, s->p->checkboxEditableColumn); enabled = uiTableDataInt(data); uiFreeTableData(data); } @@ -270,7 +231,7 @@ static HRESULT drawTextPart(struct drawState *s) uiTableData *data; WCHAR *wstr; - if (!s->hasText) + if (!s->m->hasText) return S_OK; prevText = SetTextColor(s->dc, s->textColor); @@ -284,14 +245,14 @@ static HRESULT drawTextPart(struct drawState *s) return E_FAIL; } - data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->textModelColumn); + data = cellValue(s->model, s->iItem, s->p->textModelColumn); wstr = toUTF16(uiTableDataString(data)); uiFreeTableData(data); // These flags are a menagerie of flags from various sources: // guessing, the Windows 2000 source leak, various custom // draw examples on the web, etc. // TODO find the real correct flags - if (DrawTextW(s->dc, wstr, -1, &(s->realTextRect), DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL) == 0) { + if (DrawTextW(s->dc, wstr, -1, &(s->m->realTextRect), DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL) == 0) { uiprivFree(wstr); logLastError(L"DrawTextW()"); return E_FAIL; @@ -337,7 +298,7 @@ static HRESULT drawProgressBarPart(struct drawState *s) hr = E_FAIL; goto fail; } - r = s->subitemBounds; + r = s->m->subitemBounds; // this sets the height of the progressbar and vertically centers it in one fell swoop r.top += (r.bottom - tm.tmHeight - r.top) / 2; r.bottom = r.top + tm.tmHeight; @@ -367,7 +328,7 @@ static HRESULT drawProgressBarPart(struct drawState *s) HBRUSH brush, prevBrush; sysColor = COLOR_HIGHLIGHT; - if (s->selected) + if (s->m->selected) sysColor = COLOR_HIGHLIGHTTEXT; // TODO check errors everywhere @@ -438,7 +399,7 @@ static HRESULT drawButtonPart(struct drawState *s) if (s->p->buttonModelColumn == -1) return S_OK; - data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->buttonModelColumn); + data = cellValue(s->model, s->iItem, s->p->buttonModelColumn); wstr = toUTF16(uiTableDataString(data)); uiFreeTableData(data); switch (s->p->buttonClickableModelColumn) { @@ -449,7 +410,7 @@ static HRESULT drawButtonPart(struct drawState *s) enabled = 1; break; default: - data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->checkboxEditableColumn); + data = cellValue(s->model, s->iItem, s->p->checkboxEditableColumn); enabled = uiTableDataInt(data); uiFreeTableData(data); } @@ -461,7 +422,7 @@ static HRESULT drawButtonPart(struct drawState *s) hr = E_FAIL; goto fail; } - r = s->subitemBounds; + r = s->m->subitemBounds; if (theme != NULL) { int state; @@ -534,6 +495,10 @@ static HRESULT freeDrawState(struct drawState *s) HRESULT hr, hrret; hrret = S_OK; + if (s->m != NULL) { + uiprivFree(s->m); + s->m = NULL; + } if (s->freeTextBrush) { if (DeleteObject(s->textBrush) == 0) { logLastError(L"DeleteObject()"); @@ -553,20 +518,6 @@ static HRESULT freeDrawState(struct drawState *s) return hrret; } -static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG left, LONG top, LRESULT bad, RECT *r) -{ - if (hr != S_OK) - return hr; - ZeroMemory(r, sizeof (RECT)); - r->left = left; - r->top = top; - if (SendMessageW(t->hwnd, uMsg, wParam, (LPARAM) r) == bad) { - logLastError(L"itemRect() message"); - return E_FAIL; - } - return S_OK; -} - static COLORREF blend(COLORREF base, double r, double g, double b, double a) { double br, bg, bb; @@ -591,48 +542,18 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm ZeroMemory(s, sizeof (struct drawState)); s->t = t; - s->m = t->model; + s->model = t->model; s->p = p; s->dc = nm->nmcd.hdc; s->iItem = nm->nmcd.dwItemSpec; s->iSubItem = nm->iSubItem; - s->hasText = p->textModelColumn != -1; - s->hasImage = (p->imageModelColumn != -1) || (p->checkboxModelColumn != -1); - // nm->nmcd.uItemState CDIS_SELECTED is unreliable for the - // listview configuration we have, so we must do this. - state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, nm->nmcd.dwItemSpec, LVIS_SELECTED); - s->selected = (state & LVIS_SELECTED) != 0; - s->focused = (nm->nmcd.uItemState & CDIS_FOCUS) != 0; - // TODO check LRESULT bad parameters here - hr = itemRect(S_OK, t, LVM_GETITEMRECT, s->iItem, - LVIR_BOUNDS, 0, FALSE, &(s->itemBounds)); - hr = itemRect(hr, t, LVM_GETITEMRECT, s->iItem, - LVIR_ICON, 0, FALSE, &(s->itemIcon)); - hr = itemRect(hr, t, LVM_GETITEMRECT, s->iItem, - LVIR_LABEL, 0, FALSE, &(s->itemLabel)); - hr = itemRect(hr, t, LVM_GETSUBITEMRECT, s->iItem, - LVIR_BOUNDS, s->iSubItem, 0, &(s->subitemBounds)); - hr = itemRect(hr, t, LVM_GETSUBITEMRECT, s->iItem, - LVIR_ICON, s->iSubItem, 0, &(s->subitemIcon)); + hr = uiprivTableGetMetrics(t, s->iItem, s->iSubItem, &(s->m)); if (hr != S_OK) goto fail; - // LVM_GETSUBITEMRECT treats LVIR_LABEL as the same as - // LVIR_BOUNDS, so we can't use that directly. Instead, let's - // assume the text is immediately after the icon. The correct - // rect will be determined by - // computeOtherRectsAndDrawBackgrounds() above. - s->subitemLabel = s->subitemBounds; - s->subitemLabel.left = s->subitemIcon.right; - // And on iSubItem == 0, LVIF_GETSUBITEMRECT still includes - // all the subitems, which we don't want. - if (s->iSubItem == 0) { - s->subitemBounds.right = s->itemLabel.right; - s->subitemLabel.right = s->itemLabel.right; - } - if (s->selected) { + if (s->m->selected) { s->bgColor = GetSysColor(COLOR_HIGHLIGHT); s->bgBrush = GetSysColorBrush(COLOR_HIGHLIGHT); s->textColor = GetSysColor(COLOR_HIGHLIGHTTEXT); @@ -644,7 +565,7 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm s->bgColor = GetSysColor(COLOR_WINDOW); s->bgBrush = GetSysColorBrush(COLOR_WINDOW); if (t->backgroundColumn != -1) { - data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, t->backgroundColumn); + data = cellValue(s->model, s->iItem, t->backgroundColumn); if (data != NULL) { uiTableDataColor(data, &r, &g, &b, &a); uiFreeTableData(data); @@ -661,7 +582,7 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm s->textColor = GetSysColor(COLOR_WINDOWTEXT); s->textBrush = GetSysColorBrush(COLOR_WINDOWTEXT); if (p->textParams.ColorModelColumn != -1) { - data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, p->textParams.ColorModelColumn); + data = cellValue(s->model, s->iItem, p->textParams.ColorModelColumn); if (data != NULL) { uiTableDataColor(data, &r, &g, &b, &a); uiFreeTableData(data); @@ -677,14 +598,6 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm } } - header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); - s->bitmapMargin = SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); - if (ImageList_GetIconSize(t->imagelist, &(s->cxIcon), &(s->cyIcon)) == 0) { - logLastError(L"ImageList_GetIconSize()"); - hr = E_FAIL; - goto fail; - } - return S_OK; fail: // ignore the error; we need to return the one we got above @@ -716,7 +629,7 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * hr = fillDrawState(&s, t, nm, p); if (hr != S_OK) return hr; - hr = computeOtherRectsAndDrawBackgrounds(&s); + hr = drawBackgrounds(&s); if (hr != S_OK) goto fail; hr = drawImagePart(&s); diff --git a/windows/tablemetrics.cpp b/windows/tablemetrics.cpp index 76c879c7..64236b9f 100644 --- a/windows/tablemetrics.cpp +++ b/windows/tablemetrics.cpp @@ -2,33 +2,6 @@ #include "uipriv_windows.hpp" #include "table.hpp" -struct uiprivTableMetrics { - uiTable *t; - uiTableModel *m; - uiprivTableColumnParams *p; - - int iItem; - int iSubItem; - BOOL hasText; - BOOL hasImage; - BOOL focused; - BOOL selected; - - RECT itemBounds; - RECT itemIcon; - RECT itemLabel; - RECT subitemBounds; - RECT subitemIcon; - RECT subitemLabel; - - LRESULT bitmapMargin; - int cxIcon; - int cyIcon; - - RECT realTextBackground; - RECT realTextRect; -}; - static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG left, LONG top, LRESULT bad, RECT *r) { if (hr != S_OK) @@ -46,6 +19,7 @@ static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG l HRESULT uiprivTableGetMetrics(uiTable *t, int iItem, int iSubItem, uiprivTableMetrics **mout) { uiprivTableMetrics *m; + uiprivTableColumnParams *p; LRESULT state; HWND header; RECT r; @@ -55,29 +29,25 @@ HRESULT uiprivTableGetMetrics(uiTable *t, int iItem, int iSubItem, uiprivTableMe return E_POINTER; m = uiprivNew(uiprivTableMetrics); - m->t = t; - m->m = t->model; - m->p = (*(t->columns))[iSubItem]; - m->iItem = iItem; - m->iSubItem = iSubItem; - m->hasText = m->p->textModelColumn != -1; - m->hasImage = (m->p->imageModelColumn != -1) || (m->p->checkboxModelColumn != -1); + p = (*(t->columns))[iSubItem]; + m->hasText = p->textModelColumn != -1; + m->hasImage = (p->imageModelColumn != -1) || (p->checkboxModelColumn != -1); state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, iItem, LVIS_FOCUSED | LVIS_SELECTED); m->focused = (state & LVIS_FOCUSED) != 0; m->selected = (state & LVIS_SELECTED) != 0; // TODO check LRESULT bad parameters here - hr = itemRect(S_OK, t, LVM_GETITEMRECT, s->iItem, + hr = itemRect(S_OK, t, LVM_GETITEMRECT, iItem, LVIR_BOUNDS, 0, FALSE, &(m->itemBounds)); - hr = itemRect(hr, t, LVM_GETITEMRECT, s->iItem, + hr = itemRect(hr, t, LVM_GETITEMRECT, iItem, LVIR_ICON, 0, FALSE, &(m->itemIcon)); - hr = itemRect(hr, t, LVM_GETITEMRECT, s->iItem, + hr = itemRect(hr, t, LVM_GETITEMRECT, iItem, LVIR_LABEL, 0, FALSE, &(m->itemLabel)); - hr = itemRect(hr, t, LVM_GETSUBITEMRECT, s->iItem, - LVIR_BOUNDS, s->iSubItem, 0, &(m->subitemBounds)); - hr = itemRect(hr, t, LVM_GETSUBITEMRECT, s->iItem, - LVIR_ICON, s->iSubItem, 0, &(m->subitemIcon)); + hr = itemRect(hr, t, LVM_GETSUBITEMRECT, iItem, + LVIR_BOUNDS, iSubItem, 0, &(m->subitemBounds)); + hr = itemRect(hr, t, LVM_GETSUBITEMRECT, iItem, + LVIR_ICON, iSubItem, 0, &(m->subitemIcon)); if (hr != S_OK) goto fail; // LVM_GETSUBITEMRECT treats LVIR_LABEL as the same as @@ -104,7 +74,7 @@ HRESULT uiprivTableGetMetrics(uiTable *t, int iItem, int iSubItem, uiprivTableMe r = m->subitemLabel; if (!m->hasText && !m->hasImage) - r = s->subitemBounds; + r = m->subitemBounds; else if (!m->hasImage && iSubItem != 0) // By default, this will include images; we're not drawing // images, so we will manually draw over the image area. From f180423096242aa00a1d088eb0b492d307523ee3 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 20 Jun 2018 18:47:55 -0400 Subject: [PATCH 125/166] Integrated tablemetrics.cpp into tableediting.cpp and added resize-on-type. --- windows/table.cpp | 6 ++- windows/table.hpp | 1 + windows/tableediting.cpp | 107 ++++++++++++--------------------------- 3 files changed, 38 insertions(+), 76 deletions(-) diff --git a/windows/table.cpp b/windows/table.cpp index 5704b189..a070c82b 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -117,7 +117,11 @@ static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM case WM_COMMAND: if (HIWORD(wParam) == EN_UPDATE) { // the real list view resizes the edit control on this notification specifically - // TODO + hr = uiprivTableResizeWhileEditing(t); + if (hr != S_OK) { + // TODO + } + break; } // the real list view accepts changes in this case if (HIWORD(wParam) == EN_KILLFOCUS) diff --git a/windows/table.hpp b/windows/table.hpp index c1de0393..e997395f 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -50,6 +50,7 @@ extern HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LR extern HRESULT uiprivUpdateImageListSize(uiTable *t); // tableediting.cpp +extern HRESULT uiprivTableResizeWhileEditing(uiTable *t); extern HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult); extern HRESULT uiprivTableFinishEditingText(uiTable *t); extern HRESULT uiprivTableAbortEditingText(uiTable *t); diff --git a/windows/tableediting.cpp b/windows/tableediting.cpp index b8582fba..35540e1f 100644 --- a/windows/tableediting.cpp +++ b/windows/tableediting.cpp @@ -2,30 +2,23 @@ #include "uipriv_windows.hpp" #include "table.hpp" -// TODO deduplicate this with tabledraw.cpp -static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG left, LONG top, LRESULT bad, RECT *r) -{ - if (hr != S_OK) - return hr; - ZeroMemory(r, sizeof (RECT)); - r->left = left; - r->top = top; - if (SendMessageW(t->hwnd, uMsg, wParam, (LPARAM) r) == bad) { - logLastError(L"itemRect() message"); - return E_FAIL; - } - return S_OK; -} - // this is not how the real list view positions and sizes the edit control, but this is a) close enough b) a lot easier to follow c) something I can actually get working d) something I'm slightly more comfortable including in libui -// r should be the subitem label rect -static HRESULT resizeEdit(uiTable *t, WCHAR *wstr, RECT *r) +static HRESULT resizeEdit(uiTable *t, WCHAR *wstr, int iItem, int iSubItem) { + uiprivTableMetrics *m; + RECT r; HDC dc; HFONT prevFont; TEXTMETRICW tm; SIZE textSize; RECT editRect; + HRESULT hr; + + hr = uiprivTableGetMetrics(t, iItem, iSubItem, &m); + if (hr != S_OK) + return hr; + r = m->realTextRect; + uiprivFree(m); // TODO check errors for all these dc = GetDC(t->hwnd); // use the list view DC since we're using its coordinate space @@ -36,24 +29,24 @@ static HRESULT resizeEdit(uiTable *t, WCHAR *wstr, RECT *r) ReleaseDC(t->hwnd, dc); SendMessageW(t->edit, EM_GETRECT, 0, (LPARAM) (&editRect)); - r->left -= editRect.left; + r.left -= editRect.left; // find the top of the text - r->top += ((r->bottom - r->top) - tm.tmHeight) / 2; + r.top += ((r.bottom - r.top) - tm.tmHeight) / 2; // and move THAT by the right offset - r->top -= editRect.top; - r->right = r->left + textSize.cx; + r.top -= editRect.top; + r.right = r.left + textSize.cx; // the real listview does this to add some extra space at the end // TODO this still isn't enough space - r->right += 4 * GetSystemMetrics(SM_CXEDGE) + GetSystemMetrics(SM_CYEDGE); + r.right += 4 * GetSystemMetrics(SM_CXEDGE) + GetSystemMetrics(SM_CYEDGE); // and make the bottom equally positioned to the top - r->bottom = r->top + editRect.top + tm.tmHeight + editRect.top; + r.bottom = r.top + editRect.top + tm.tmHeight + editRect.top; // TODO intersect r with the list view's client rect to prevent clipping // TODO check error or use the right function SetWindowPos(t->edit, NULL, - r->left, r->top, - r->right - r->left, r->bottom - r->top, + r.left, r.top, + r.right - r.left, r.bottom - r.top, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); return S_OK; } @@ -95,12 +88,8 @@ static LRESULT CALLBACK editSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableColumnParams *p) { - RECT itemLabel; - RECT subitemBounds, subitemIcon, subitemLabel; uiTableData *data; WCHAR *wstr; - RECT r; - LONG xInflate, yInflate; HRESULT hr; // the real list view accepts changes to the existing item when editing a new item @@ -108,51 +97,6 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC if (hr != S_OK) return hr; - // compute this in advance so we don't have to needlessly call DestroyWindow() later - // TODO deduplicate this code with tabledraw.cpp - // TODO check LRESULT bad parameters here - hr = itemRect(S_OK, t, LVM_GETITEMRECT, iItem, - LVIR_LABEL, 0, FALSE, &itemLabel); - hr = itemRect(hr, t, LVM_GETSUBITEMRECT, iItem, - LVIR_BOUNDS, iSubItem, 0, &subitemBounds); - hr = itemRect(hr, t, LVM_GETSUBITEMRECT, iItem, - LVIR_ICON, iSubItem, 0, &subitemIcon); - if (hr != S_OK) - return hr; - // LVM_GETSUBITEMRECT treats LVIR_LABEL as the same as - // LVIR_BOUNDS, so we can't use that directly. Instead, let's - // assume the text is immediately after the icon. The correct - // rect will be determined by - // computeOtherRectsAndDrawBackgrounds() above. - subitemLabel = subitemBounds; - subitemLabel.left = subitemIcon.right; - // And on iSubItem == 0, LVIF_GETSUBITEMRECT still includes - // all the subitems, which we don't want. - if (iSubItem == 0) { - subitemBounds.right = itemLabel.right; - subitemLabel.right = itemLabel.right; - } - if ((p->imageModelColumn == -1 && p->checkboxModelColumn == -1) && iSubItem != 0) - // By default, this will include images; we're not drawing - // images, so we will manually draw over the image area. - // There's a second part to this; see below. - subitemLabel.left = subitemBounds.left; - if ((p->imageModelColumn != -1 || p->checkboxModelColumn != -1) && iSubItem != 0) - // Normally there's this many hard-coded logical units - // of blank space, followed by the background, followed - // by a bitmap margin's worth of space. This looks bad, - // so we overrule that to start the background immediately - // and the text after the hard-coded amount. - subitemLabel.left += 2; - else if (iSubItem != 0) { - HWND header; - - // In the case of subitem text without an image, we draw - // text one bitmap margin away from the left edge. - header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); - subitemLabel.left += SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); - } - // the real list view creates the edit control with the string data = (*(t->model->mh->CellValue))(t->model->mh, t->model, iItem, p->textModelColumn); wstr = toUTF16(uiTableDataString(data)); @@ -175,7 +119,7 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC // TODO check errors SetWindowSubclass(t->edit, editSubProc, 0, (DWORD_PTR) t); - hr = resizeEdit(t, wstr, &subitemLabel); + hr = resizeEdit(t, wstr, iItem, iSubItem); if (hr != S_OK) // TODO proper cleanup return hr; @@ -190,6 +134,19 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC return S_OK; } +HRESULT uiprivTableResizeWhileEditing(uiTable *t) +{ + WCHAR *text; + HRESULT hr; + + if (t->edit == NULL) + return S_OK; + text = windowText(t->edit); + hr = resizeEdit(t, text, t->editedItem, t->editedSubitem); + uiprivFree(text); + return hr; +} + HRESULT uiprivTableFinishEditingText(uiTable *t) { if (t->edit == NULL) From ccb7005dc6325fc863a97876cd51dbc5a72cb167 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 20 Jun 2018 18:56:03 -0400 Subject: [PATCH 126/166] Properly clipped the edit within the listview. --- windows/tableediting.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/windows/tableediting.cpp b/windows/tableediting.cpp index 35540e1f..bff84937 100644 --- a/windows/tableediting.cpp +++ b/windows/tableediting.cpp @@ -11,7 +11,7 @@ static HRESULT resizeEdit(uiTable *t, WCHAR *wstr, int iItem, int iSubItem) HFONT prevFont; TEXTMETRICW tm; SIZE textSize; - RECT editRect; + RECT editRect, clientRect; HRESULT hr; hr = uiprivTableGetMetrics(t, iItem, iSubItem, &m); @@ -41,7 +41,11 @@ static HRESULT resizeEdit(uiTable *t, WCHAR *wstr, int iItem, int iSubItem) // and make the bottom equally positioned to the top r.bottom = r.top + editRect.top + tm.tmHeight + editRect.top; - // TODO intersect r with the list view's client rect to prevent clipping + // make sure the edit box doesn't stretch outside the listview + // the list view just does this, which is dumb for when the list view wouldn't be visible at all, but given that it doesn't scroll the edit into view either... + // TODO check errors + GetClientRect(t->hwnd, &clientRect); + IntersectRect(&r, &r, &clientRect); // TODO check error or use the right function SetWindowPos(t->edit, NULL, From 5854b0f63858fbd4fc1c35e89983e1e10aca729d Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 20 Jun 2018 19:31:21 -0400 Subject: [PATCH 127/166] Loose ends for editing: drew the proper background for text and committing text works now. --- windows/tabledraw.cpp | 3 +++ windows/tableediting.cpp | 24 ++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 64044455..062926e9 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -233,6 +233,9 @@ static HRESULT drawTextPart(struct drawState *s) if (!s->m->hasText) return S_OK; + // don't draw the text underneath an edit control + if (s->t->edit != NULL && s->t->editedItem == s->iItem && s->t->editedSubitem == s->iSubItem) + return S_OK; prevText = SetTextColor(s->dc, s->textColor); if (prevText == CLR_INVALID) { diff --git a/windows/tableediting.cpp b/windows/tableediting.cpp index bff84937..4e1131ab 100644 --- a/windows/tableediting.cpp +++ b/windows/tableediting.cpp @@ -153,20 +153,40 @@ HRESULT uiprivTableResizeWhileEditing(uiTable *t) HRESULT uiprivTableFinishEditingText(uiTable *t) { + uiprivTableColumnParams *p; + uiTableData *data; + char *text; + if (t->edit == NULL) return S_OK; + text = uiWindowsWindowText(t->edit); + data = uiNewTableDataString(text); + uiFreeText(text); + p = (*(t->columns))[t->editedSubitem]; + (*(t->model->mh->SetCellValue))(t->model->mh, t->model, t->editedItem, p->textModelColumn, data); + uiFreeTableData(data); + // always refresh the value in case the model rejected it + if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) (t->editedItem), 0) == (LRESULT) (-1)) { + logLastError(L"LVM_UPDATE"); + return E_FAIL; + } return uiprivTableAbortEditingText(t); } HRESULT uiprivTableAbortEditingText(uiTable *t) { + HWND edit; + if (t->edit == NULL) return S_OK; - if (DestroyWindow(t->edit) == 0) { + // set t->edit to NULL now so we don't trigger commits on focus killed + edit = t->edit; + t->edit = NULL; + + if (DestroyWindow(edit) == 0) { logLastError(L"DestroyWindow()"); return E_FAIL; } - t->edit = NULL; return S_OK; } From 4dc7f4c2def7ab987c3da0b92866d17d527a6277 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 20 Jun 2018 21:11:15 -0400 Subject: [PATCH 128/166] Wrote the initial code to draw the focus rect. --- windows/tabledraw.cpp | 55 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 062926e9..4660f6b8 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -608,6 +608,52 @@ fail: return hr; } +// TODO this has overdraw issues on non-selected rows; do they happen on selected rows too? +static HRESULT drawFocusRects(uiTable *t, NMLVCUSTOMDRAW *nm) +{ + RECT r; + bool first; + size_t i, n; + uiprivTableMetrics *m; + HRESULT hr; + + // TODO check error here? + if (GetFocus() != t->hwnd) + return S_OK; + // TODO only if the current item is focused + + ZeroMemory(&r, sizeof (RECT)); + first = true; + n = t->columns->size(); + for (i = 0; i < n; i++) { + RECT b; + + hr = uiprivTableGetMetrics(t, nm->nmcd.dwItemSpec, i, &m); + if (hr != S_OK) + return hr; + b = m->realTextBackground; + uiprivFree(m); + if (first) { + r = b; + first = false; + } else if (r.right == b.left) + r.right = b.right; + else { + if (DrawFocusRect(nm->nmcd.hdc, &r) == 0) { + logLastError(L"DrawFocusRect()"); + return E_FAIL; + } + r = b; + } + } + // and draw the last rect + if (DrawFocusRect(nm->nmcd.hdc, &r) == 0) { + logLastError(L"DrawFocusRect() last"); + return E_FAIL; + } + return S_OK; +} + HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult) { struct drawState s; @@ -619,10 +665,17 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * *lResult = CDRF_NOTIFYITEMDRAW; return S_OK; case CDDS_ITEMPREPAINT: - *lResult = CDRF_NOTIFYSUBITEMDRAW; + *lResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NOTIFYPOSTPAINT; return S_OK; case CDDS_SUBITEM | CDDS_ITEMPREPAINT: break; + case CDDS_ITEMPOSTPAINT: + // draw the focus rects only at the end, so they are drawn over subitems + hr = drawFocusRects(t, nm); + if (hr != S_OK) + return hr; + *lResult = CDRF_SKIPDEFAULT; + return S_OK; default: *lResult = CDRF_DODEFAULT; return S_OK; From c2000ea54dca5dc0627045c1c3f1162911c8291e Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Thu, 21 Jun 2018 21:56:24 -0400 Subject: [PATCH 129/166] Gave up with CDDS_SUBITEM; it just refuses to play nice with focus rects. Will try drawing focus rects again next commit. --- windows/tabledraw.cpp | 75 +++++++++++++++++++++------------------- windows/tableediting.cpp | 3 ++ 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 4660f6b8..29b181bc 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -654,10 +654,19 @@ static HRESULT drawFocusRects(uiTable *t, NMLVCUSTOMDRAW *nm) return S_OK; } +// normally we would only draw stuff in subitem stages +// this broke when we tried drawing focus rects in postpaint; the drawing either kept getting wiped or overdrawn, mouse hovering had something to do with it, and none of the "solutions" to this or similar problems on the internet worked +// so now we do everything in the item prepaint stage +// TODO consider switching to full-on owner draw +// TODO only compute the background brushes once? +// TODO integrate the focus rect stuff above +// TODO do hr chaining like in tablemetrics.cpp HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult) { struct drawState s; uiprivTableColumnParams *p; + NMLVCUSTOMDRAW b; + size_t i, n; HRESULT hr; switch (nm->nmcd.dwDrawStage) { @@ -665,51 +674,47 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * *lResult = CDRF_NOTIFYITEMDRAW; return S_OK; case CDDS_ITEMPREPAINT: - *lResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NOTIFYPOSTPAINT; - return S_OK; - case CDDS_SUBITEM | CDDS_ITEMPREPAINT: break; - case CDDS_ITEMPOSTPAINT: - // draw the focus rects only at the end, so they are drawn over subitems - hr = drawFocusRects(t, nm); - if (hr != S_OK) - return hr; - *lResult = CDRF_SKIPDEFAULT; - return S_OK; default: *lResult = CDRF_DODEFAULT; return S_OK; } - p = (*(t->columns))[nm->iSubItem]; - hr = fillDrawState(&s, t, nm, p); - if (hr != S_OK) - return hr; - hr = drawBackgrounds(&s); - if (hr != S_OK) - goto fail; - hr = drawImagePart(&s); - if (hr != S_OK) - goto fail; - hr = drawCheckboxPart(&s); - if (hr != S_OK) - goto fail; - hr = drawTextPart(&s); - if (hr != S_OK) - goto fail; - hr = drawProgressBarPart(&s); - if (hr != S_OK) - goto fail; - hr = drawButtonPart(&s); - if (hr != S_OK) - goto fail; - hr = freeDrawState(&s); - if (hr != S_OK) // TODO really error out here? - return hr; + n = t->columns->size(); + b = *nm; + for (i = 0; i < n; i++) { + b.iSubItem = i; + p = (*(t->columns))[i]; + hr = fillDrawState(&s, t, &b, p); + if (hr != S_OK) + return hr; + hr = drawBackgrounds(&s); + if (hr != S_OK) + goto fail; + hr = drawImagePart(&s); + if (hr != S_OK) + goto fail; + hr = drawCheckboxPart(&s); + if (hr != S_OK) + goto fail; + hr = drawTextPart(&s); + if (hr != S_OK) + goto fail; + hr = drawProgressBarPart(&s); + if (hr != S_OK) + goto fail; + hr = drawButtonPart(&s); + if (hr != S_OK) + goto fail; + hr = freeDrawState(&s); + if (hr != S_OK) // TODO really error out here? + return hr; + } *lResult = CDRF_SKIPDEFAULT; return S_OK; fail: // ignore error here + // TODO this is awkward cleanup placement for something that only really exists in a for loop freeDrawState(&s); return hr; } diff --git a/windows/tableediting.cpp b/windows/tableediting.cpp index 4e1131ab..4eb8d2d2 100644 --- a/windows/tableediting.cpp +++ b/windows/tableediting.cpp @@ -2,6 +2,9 @@ #include "uipriv_windows.hpp" #include "table.hpp" +// TODOs +// - clicking on the same item restarts editing instead of cancels it + // this is not how the real list view positions and sizes the edit control, but this is a) close enough b) a lot easier to follow c) something I can actually get working d) something I'm slightly more comfortable including in libui static HRESULT resizeEdit(uiTable *t, WCHAR *wstr, int iItem, int iSubItem) { From d0f7cf81c5458837ff1d17dcb852345110578374 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Thu, 21 Jun 2018 23:47:24 -0400 Subject: [PATCH 130/166] And rewired the focus rect. --- windows/tabledraw.cpp | 65 +++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 29b181bc..ddeac51e 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -2,6 +2,9 @@ #include "uipriv_windows.hpp" #include "table.hpp" +// TODOs: +// - properly hide selection when not focused (or switch on LVS_SHOWSELALWAYS and draw that state) + // TODO #define cellValue(model, row, column) ((*((model)->mh->CellValue))((model)->mh, (model), (row), (column))) @@ -608,49 +611,33 @@ fail: return hr; } -// TODO this has overdraw issues on non-selected rows; do they happen on selected rows too? -static HRESULT drawFocusRects(uiTable *t, NMLVCUSTOMDRAW *nm) +static HRESULT updateAndDrawFocusRects(uiTable *t, HDC dc, int iItem, RECT *realTextBackground, RECT *focus, bool *first) { - RECT r; - bool first; - size_t i, n; - uiprivTableMetrics *m; - HRESULT hr; + LRESULT state; - // TODO check error here? if (GetFocus() != t->hwnd) return S_OK; - // TODO only if the current item is focused + // uItemState CDIS_FOCUS doesn't quite work right because of bugs in the Windows list view that causes spurious redraws without the flag while we hover over the focused item + // TODO only call this once + state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, (WPARAM) iItem, (LRESULT) (LVIS_FOCUSED)); + if ((state & LVIS_FOCUSED) == 0) + return S_OK; - ZeroMemory(&r, sizeof (RECT)); - first = true; - n = t->columns->size(); - for (i = 0; i < n; i++) { - RECT b; - - hr = uiprivTableGetMetrics(t, nm->nmcd.dwItemSpec, i, &m); - if (hr != S_OK) - return hr; - b = m->realTextBackground; - uiprivFree(m); - if (first) { - r = b; + if (realTextBackground != NULL) + if (*first) { + *focus = *realTextBackground; first = false; - } else if (r.right == b.left) - r.right = b.right; - else { - if (DrawFocusRect(nm->nmcd.hdc, &r) == 0) { - logLastError(L"DrawFocusRect()"); - return E_FAIL; - } - r = b; + return S_OK; + } else if (focus->right == realTextBackground->left) { + focus->right = realTextBackground->right; + return S_OK; } - } - // and draw the last rect - if (DrawFocusRect(nm->nmcd.hdc, &r) == 0) { - logLastError(L"DrawFocusRect() last"); + if (DrawFocusRect(dc, focus) == 0) { + logLastError(L"DrawFocusRect()"); return E_FAIL; } + if (realTextBackground != NULL) + *focus = *realTextBackground; return S_OK; } @@ -667,6 +654,8 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * uiprivTableColumnParams *p; NMLVCUSTOMDRAW b; size_t i, n; + RECT focus; + bool focusFirst; HRESULT hr; switch (nm->nmcd.dwDrawStage) { @@ -682,6 +671,7 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * n = t->columns->size(); b = *nm; + focusFirst = false; for (i = 0; i < n; i++) { b.iSubItem = i; p = (*(t->columns))[i]; @@ -704,12 +694,19 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * if (hr != S_OK) goto fail; hr = drawButtonPart(&s); + if (hr != S_OK) + goto fail; + hr = updateAndDrawFocusRects(s.t, s.dc, nm->nmcd.dwItemSpec, &(s.m->realTextBackground), &focus, &focusFirst); if (hr != S_OK) goto fail; hr = freeDrawState(&s); if (hr != S_OK) // TODO really error out here? return hr; } + // and draw the last focus rect + hr = updateAndDrawFocusRects(t, nm->nmcd.hdc, nm->nmcd.dwItemSpec, NULL, &focus, &focusFirst); + if (hr != S_OK) + return hr; *lResult = CDRF_SKIPDEFAULT; return S_OK; fail: From 7d17df712147ffdb09eb8ea0344015bd0970ea41 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 22 Jun 2018 21:50:04 -0400 Subject: [PATCH 131/166] HRESULT-chained the drawing functions together and fixed bugs in the focus drawing code. --- windows/tabledraw.cpp | 66 ++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index ddeac51e..190a6c0b 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -27,8 +27,10 @@ struct drawState { BOOL freeTextBrush; }; -static HRESULT drawBackgrounds(struct drawState *s) +static HRESULT drawBackgrounds(HRESULT hr, struct drawState *s) { + if (hr != S_OK) + return hr; if (s->m->hasImage) if (FillRect(s->dc, &(s->m->subitemIcon), GetSysColorBrush(COLOR_WINDOW)) == 0) { logLastError(L"FillRect() icon"); @@ -62,15 +64,16 @@ static void centerImageRect(RECT *image, RECT *space) image->bottom += yoff; } -static HRESULT drawImagePart(struct drawState *s) +static HRESULT drawImagePart(HRESULT hr, struct drawState *s) { uiTableData *data; IWICBitmap *wb; HBITMAP b; RECT r; UINT fStyle; - HRESULT hr; + if (hr != S_OK) + return hr; if (s->p->imageModelColumn == -1) return S_OK; @@ -182,13 +185,14 @@ static HRESULT drawThemedCheckbox(struct drawState *s, HTHEME theme, int checked return S_OK; } -static HRESULT drawCheckboxPart(struct drawState *s) +static HRESULT drawCheckboxPart(HRESULT hr, struct drawState *s) { uiTableData *data; int checked, enabled; HTHEME theme; - HRESULT hr; + if (hr != S_OK) + return hr; if (s->p->checkboxModelColumn == -1) return S_OK; @@ -226,7 +230,7 @@ static HRESULT drawCheckboxPart(struct drawState *s) return S_OK; } -static HRESULT drawTextPart(struct drawState *s) +static HRESULT drawTextPart(HRESULT hr, struct drawState *s) { COLORREF prevText; int prevMode; @@ -234,6 +238,8 @@ static HRESULT drawTextPart(struct drawState *s) uiTableData *data; WCHAR *wstr; + if (hr != S_OK) + return hr; if (!s->m->hasText) return S_OK; // don't draw the text underneath an edit control @@ -280,7 +286,7 @@ static HRESULT drawTextPart(struct drawState *s) // much of this is to imitate what shell32.dll's CDrawProgressBar does #define indeterminateSegments 8 -static HRESULT drawProgressBarPart(struct drawState *s) +static HRESULT drawProgressBarPart(HRESULT hr, struct drawState *s) { int progress; LONG indeterminatePos; @@ -290,8 +296,9 @@ static HRESULT drawProgressBarPart(struct drawState *s) int i, nFill; TEXTMETRICW tm; int sysColor; - HRESULT hr; + if (hr != S_OK) + return hr; if (s->p->progressBarModelColumn == -1) return S_OK; @@ -392,7 +399,7 @@ fail: return hr; } -static HRESULT drawButtonPart(struct drawState *s) +static HRESULT drawButtonPart(HRESULT hr, struct drawState *s) { uiTableData *data; WCHAR *wstr; @@ -400,8 +407,9 @@ static HRESULT drawButtonPart(struct drawState *s) HTHEME theme; RECT r; TEXTMETRICW tm; - HRESULT hr; + if (hr != S_OK) + return hr; if (s->p->buttonModelColumn == -1) return S_OK; @@ -611,10 +619,12 @@ fail: return hr; } -static HRESULT updateAndDrawFocusRects(uiTable *t, HDC dc, int iItem, RECT *realTextBackground, RECT *focus, bool *first) +static HRESULT updateAndDrawFocusRects(HRESULT hr, uiTable *t, HDC dc, int iItem, RECT *realTextBackground, RECT *focus, bool *first) { LRESULT state; + if (hr != S_OK) + return hr; if (GetFocus() != t->hwnd) return S_OK; // uItemState CDIS_FOCUS doesn't quite work right because of bugs in the Windows list view that causes spurious redraws without the flag while we hover over the focused item @@ -626,7 +636,7 @@ static HRESULT updateAndDrawFocusRects(uiTable *t, HDC dc, int iItem, RECT *real if (realTextBackground != NULL) if (*first) { *focus = *realTextBackground; - first = false; + *first = false; return S_OK; } else if (focus->right == realTextBackground->left) { focus->right = realTextBackground->right; @@ -646,8 +656,6 @@ static HRESULT updateAndDrawFocusRects(uiTable *t, HDC dc, int iItem, RECT *real // so now we do everything in the item prepaint stage // TODO consider switching to full-on owner draw // TODO only compute the background brushes once? -// TODO integrate the focus rect stuff above -// TODO do hr chaining like in tablemetrics.cpp HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult) { struct drawState s; @@ -671,32 +679,20 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * n = t->columns->size(); b = *nm; - focusFirst = false; + focusFirst = true; for (i = 0; i < n; i++) { b.iSubItem = i; p = (*(t->columns))[i]; hr = fillDrawState(&s, t, &b, p); if (hr != S_OK) return hr; - hr = drawBackgrounds(&s); - if (hr != S_OK) - goto fail; - hr = drawImagePart(&s); - if (hr != S_OK) - goto fail; - hr = drawCheckboxPart(&s); - if (hr != S_OK) - goto fail; - hr = drawTextPart(&s); - if (hr != S_OK) - goto fail; - hr = drawProgressBarPart(&s); - if (hr != S_OK) - goto fail; - hr = drawButtonPart(&s); - if (hr != S_OK) - goto fail; - hr = updateAndDrawFocusRects(s.t, s.dc, nm->nmcd.dwItemSpec, &(s.m->realTextBackground), &focus, &focusFirst); + hr = drawBackgrounds(S_OK, &s); + hr = drawImagePart(hr, &s); + hr = drawCheckboxPart(hr, &s); + hr = drawTextPart(hr, &s); + hr = drawProgressBarPart(hr, &s); + hr = drawButtonPart(hr, &s); + hr = updateAndDrawFocusRects(hr, s.t, s.dc, nm->nmcd.dwItemSpec, &(s.m->realTextBackground), &focus, &focusFirst); if (hr != S_OK) goto fail; hr = freeDrawState(&s); @@ -704,7 +700,7 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * return hr; } // and draw the last focus rect - hr = updateAndDrawFocusRects(t, nm->nmcd.hdc, nm->nmcd.dwItemSpec, NULL, &focus, &focusFirst); + hr = updateAndDrawFocusRects(hr, t, nm->nmcd.hdc, nm->nmcd.dwItemSpec, NULL, &focus, &focusFirst); if (hr != S_OK) return hr; *lResult = CDRF_SKIPDEFAULT; From 888bb450b36dcc226e6bbaf184d2675fe3536e0e Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 23 Jun 2018 11:26:14 -0400 Subject: [PATCH 132/166] More TODOs. --- windows/tabledraw.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 190a6c0b..c693d794 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -8,6 +8,7 @@ // TODO #define cellValue(model, row, column) ((*((model)->mh->CellValue))((model)->mh, (model), (row), (column))) +// TODO maybe split this into item and subitem structs? struct drawState { uiTable *t; uiTableModel *model; From ac27e24add9ac7992447efe8642bd9e1a5d59514 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 23 Jun 2018 11:27:02 -0400 Subject: [PATCH 133/166] And even more still. I'm just gonna clean everything up for a merge at this point. --- windows/table.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/windows/table.cpp b/windows/table.cpp index a070c82b..315f5e37 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -4,6 +4,8 @@ // general TODOs: // - tooltips don't work properly on columns with icons (the listview always thinks there's enough room for a short label because it's not taking the icon into account); is this a bug in our LVN_GETDISPINFO handler or something else? // - should clicking on some other column of the same row, even one that doesn't edit, cancel editing? +// - implement keyboard accessibility +// - implement accessibility in general (Dynamic Annotations maybe?) static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { /*TODO.ColorModelColumn = */-1, From 9a79eed2ac635612f169163ae152957a9df90e88 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 23 Jun 2018 20:19:30 -0400 Subject: [PATCH 134/166] Final cleanup, first part: renamed uiTableData to uiTableValue. --- common/CMakeLists.txt | 4 +- common/tabledata.c | 105 -------------------------------------- common/tablevalue.c | 105 ++++++++++++++++++++++++++++++++++++++ darwin/table.m | 10 ++-- darwin/tablecolumn.m | 83 +++++++++++++++--------------- test/page16.c | 44 ++++++++-------- uitable.h | 38 +++++++------- unix/table.c | 20 ++++---- unix/tablemodel.c | 38 +++++++------- windows/table.cpp | 8 +-- windows/tabledispinfo.cpp | 12 ++--- windows/tabledraw.cpp | 62 +++++++++++----------- windows/tableediting.cpp | 36 ++++++------- 13 files changed, 281 insertions(+), 284 deletions(-) delete mode 100644 common/tabledata.c create mode 100644 common/tablevalue.c diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 99ceda09..785c70db 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -7,12 +7,10 @@ list(APPEND _LIBUI_SOURCES common/areaevents.c common/control.c common/debug.c -# common/drawtext.c common/matrix.c common/opentype.c common/shouldquit.c -# common/table.c - common/tabledata.c + common/tablevalue.c common/userbugs.c common/utf.c ) diff --git a/common/tabledata.c b/common/tabledata.c deleted file mode 100644 index e66d4b4b..00000000 --- a/common/tabledata.c +++ /dev/null @@ -1,105 +0,0 @@ -// 3 june 2018 -#include "../ui.h" -#include "uipriv.h" - -struct uiTableData { - uiTableDataType type; - union { - char *str; - uiImage *img; - int i; - struct { - double r; - double g; - double b; - double a; - } color; - } u; -}; - -static uiTableData *newTableData(uiTableDataType type) -{ - uiTableData *d; - - d = uiprivNew(uiTableData); - d->type = type; - return d; -} - -void uiFreeTableData(uiTableData *d) -{ - switch (d->type) { - case uiTableDataTypeString: - uiprivFree(d->u.str); - break; - } - uiprivFree(d); -} - -uiTableDataType uiTableDataGetType(const uiTableData *d) -{ - return d->type; -} - -uiTableData *uiNewTableDataString(const char *str) -{ - uiTableData *d; - - d = newTableData(uiTableDataTypeString); - d->u.str = (char *) uiprivAlloc((strlen(str) + 1) * sizeof (char), "char[] (uiTableData)"); - strcpy(d->u.str, str); - return d; -} - -const char *uiTableDataString(const uiTableData *d) -{ - return d->u.str; -} - -uiTableData *uiNewTableDataImage(uiImage *img) -{ - uiTableData *d; - - d = newTableData(uiTableDataTypeImage); - d->u.img = img; - return d; -} - -uiImage *uiTableDataImage(const uiTableData *d) -{ - return d->u.img; -} - -uiTableData *uiNewTableDataInt(int i) -{ - uiTableData *d; - - d = newTableData(uiTableDataTypeInt); - d->u.i = i; - return d; -} - -int uiTableDataInt(const uiTableData *d) -{ - return d->u.i; -} - -uiTableData *uiNewTableDataColor(double r, double g, double b, double a) -{ - uiTableData *d; - - d = newTableData(uiTableDataTypeColor); - d->u.color.r = r; - d->u.color.g = g; - d->u.color.b = b; - d->u.color.a = a; - return d; -} - -void uiTableDataColor(const uiTableData *d, double *r, double *g, double *b, double *a) -{ - *r = d->u.color.r; - *g = d->u.color.g; - *b = d->u.color.b; - *a = d->u.color.a; -} diff --git a/common/tablevalue.c b/common/tablevalue.c new file mode 100644 index 00000000..b73a920e --- /dev/null +++ b/common/tablevalue.c @@ -0,0 +1,105 @@ +// 3 june 2018 +#include "../ui.h" +#include "uipriv.h" + +struct uiTableValue { + uiTableValueType type; + union { + char *str; + uiImage *img; + int i; + struct { + double r; + double g; + double b; + double a; + } color; + } u; +}; + +static uiTableValue *newTableValue(uiTableValueType type) +{ + uiTableValue *v; + + v = uiprivNew(uiTableValue); + v->type = type; + return v; +} + +void uiFreeTableValue(uiTableValue *v) +{ + switch (v->type) { + case uiTableValueTypeString: + uiprivFree(v->u.str); + break; + } + uiprivFree(v); +} + +uiTableValueType uiTableValueGetType(const uiTableValue *v) +{ + return v->type; +} + +uiTableValue *uiNewTableValueString(const char *str) +{ + uiTableValue *v; + + v = newTableValue(uiTableValueTypeString); + v->u.str = (char *) uiprivAlloc((strlen(str) + 1) * sizeof (char), "char[] (uiTableValue)"); + strcpy(v->u.str, str); + return v; +} + +const char *uiTableValueString(const uiTableValue *v) +{ + return v->u.str; +} + +uiTableValue *uiNewTableValueImage(uiImage *img) +{ + uiTableValue *v; + + v = newTableValue(uiTableValueTypeImage); + v->u.img = img; + return v; +} + +uiImage *uiTableValueImage(const uiTableValue *v) +{ + return v->u.img; +} + +uiTableValue *uiNewTableValueInt(int i) +{ + uiTableValue *v; + + v = newTableValue(uiTableValueTypeInt); + v->u.i = i; + return v; +} + +int uiTableValueInt(const uiTableValue *v) +{ + return v->u.i; +} + +uiTableValue *uiNewTableValueColor(double r, double g, double b, double a) +{ + uiTableValue *v; + + v = newTableValue(uiTableValueTypeColor); + v->u.color.r = r; + v->u.color.g = g; + v->u.color.b = b; + v->u.color.a = a; + return v; +} + +void uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a) +{ + *r = v->u.color.r; + *g = v->u.color.g; + *b = v->u.color.b; + *a = v->u.color.a; +} diff --git a/darwin/table.m b/darwin/table.m index 1d5093f9..378fb95d 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -31,16 +31,16 @@ // TODO is this correct for overflow scrolling? static void setBackgroundColor(uiprivTableView *t, NSTableRowView *rv, NSInteger row) { - uiTableData *data; + uiTableValue *value; NSColor *color; double r, g, b, a; if (t->uiprivT->backgroundColumn == -1) return; - data = (*(t->uiprivM->mh->CellValue))(t->uiprivM->mh, t->uiprivM, row, t->uiprivT->backgroundColumn); - if (data != NULL) { - uiTableDataColor(data, &r, &g, &b, &a); - uiFreeTableData(data); + value = (*(t->uiprivM->mh->CellValue))(t->uiprivM->mh, t->uiprivM, row, t->uiprivT->backgroundColumn); + if (value != NULL) { + uiTableValueColor(value, &r, &g, &b, &a); + uiFreeTableValue(value); color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a]; } else { NSArray *colors; diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index dbc85360..2e2e8299 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -36,8 +36,8 @@ static BOOL isCellEditable(uiTableModel *m, NSInteger row, int modelColumn) { - uiTableData *data; - int value; + uiTableValue *value; + int editable; switch (modelColumn) { case uiTableModelColumnNeverEditable: @@ -45,11 +45,10 @@ static BOOL isCellEditable(uiTableModel *m, NSInteger row, int modelColumn) case uiTableModelColumnAlwaysEditable: return YES; } - data = (*(m->mh->CellValue))(m->mh, m, row, modelColumn); - value = uiTableDataInt(data); - uiFreeTableData(data); - return value != 0; - // TODO free data + value = (*(m->mh->CellValue))(m->mh, m, row, modelColumn); + editable = uiTableValueInt(value); + uiFreeTableValue(value); + return editable != 0; } static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { @@ -266,15 +265,15 @@ struct textColumnCreateParams { - (void)uiprivUpdate:(NSInteger)row { - uiTableData *data; + uiTableValue *value; if (self->tf != nil) { NSString *str; NSColor *color; - data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textModelColumn); - str = uiprivToNSString(uiTableDataString(data)); - uiFreeTableData(data); + value = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textModelColumn); + str = uiprivToNSString(uiTableValueString(value)); + uiFreeTableValue(value); [self->tf setStringValue:str]; [self->tf setEditable:isCellEditable(self->m, row, self->textEditableColumn)]; @@ -283,11 +282,11 @@ struct textColumnCreateParams { if (self->textParams.ColorModelColumn != -1) { double r, g, b, a; - data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textParams.ColorModelColumn); + value = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textParams.ColorModelColumn); // TODO document this is allowed - if (data != NULL) { - uiTableDataColor(data, &r, &g, &b, &a); - uiFreeTableData(data); + if (value != NULL) { + uiTableValueColor(value, &r, &g, &b, &a); + uiFreeTableValue(value); color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a]; } } @@ -299,18 +298,18 @@ struct textColumnCreateParams { if (self->iv != nil) { uiImage *img; - data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->imageModelColumn); - img = uiTableDataImage(data); - uiFreeTableData(data); + value = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->imageModelColumn); + img = uiTableValueImage(value); + uiFreeTableValue(value); [self->iv setImage:uiprivImageNSImage(img)]; } if (self->cb != nil) { - data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->imageModelColumn); - if (uiTableDataInt(data) != 0) + value = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->imageModelColumn); + if (uiTableValueInt(value) != 0) [self->cb setState:NSOnState]; else [self->cb setState:NSOffState]; - uiFreeTableData(data); + uiFreeTableValue(value); [self->cb setEnabled:isCellEditable(self->m, row, self->checkboxEditableColumn)]; } @@ -319,13 +318,13 @@ struct textColumnCreateParams { - (IBAction)uiprivOnTextFieldAction:(id)sender { NSInteger row; - uiTableData *data; + uiTableValue *value; row = [self->t->tv rowForView:self->tf]; - data = uiNewTableDataString([[self->tf stringValue] UTF8String]); + value = uiNewTableValueString([[self->tf stringValue] UTF8String]); (*(self->m->mh->SetCellValue))(self->m->mh, self->m, - row, self->textModelColumn, data); - uiFreeTableData(data); + row, self->textModelColumn, value); + uiFreeTableValue(value); // always refresh the value in case the model rejected it // TODO document that we do this, but not for the whole row (or decide to do both, or do neither...) [self uiprivUpdate:row]; @@ -334,13 +333,13 @@ struct textColumnCreateParams { - (IBAction)uiprivOnCheckboxAction:(id)sender { NSInteger row; - void *data; + uiTableValue *value; row = [self->t->tv rowForView:self->cb]; - data = uiNewTableDataInt([self->cb state] != NSOffState); + value = uiNewTableValueInt([self->cb state] != NSOffState); (*(self->m->mh->SetCellValue))(self->m->mh, self->m, - row, self->checkboxModelColumn, data); - uiFreeTableData(data); + row, self->checkboxModelColumn, value); + uiFreeTableValue(value); // always refresh the value in case the model rejected it [self uiprivUpdate:row]; } @@ -434,16 +433,16 @@ struct textColumnCreateParams { - (void)uiprivUpdate:(NSInteger)row { - uiTableData *data; - int value; + uiTableValue *value; + int progress; - data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->modelColumn); - value = uiTableDataInt(data); - uiFreeTableData(data); - if (value == -1) { + value = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->modelColumn); + progress = uiTableValueInt(value); + uiFreeTableValue(value); + if (progress == -1) { [self->p setIndeterminate:YES]; [self->p startAnimation:self->p]; - } else if (value == 100) { + } else if (progress == 100) { [self->p setIndeterminate:NO]; [self->p setMaxValue:101]; [self->p setDoubleValue:101]; @@ -451,8 +450,8 @@ struct textColumnCreateParams { [self->p setMaxValue:100]; } else { [self->p setIndeterminate:NO]; - [self->p setDoubleValue:(value + 1)]; - [self->p setDoubleValue:value]; + [self->p setDoubleValue:(progress + 1)]; + [self->p setDoubleValue:progress]; } } @@ -557,12 +556,12 @@ struct textColumnCreateParams { - (void)uiprivUpdate:(NSInteger)row { - uiTableData *data; + uiTableValue *value; NSString *str; - data = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->modelColumn); - str = uiprivToNSString(uiTableDataString(data)); - uiFreeTableData(data); + value = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->modelColumn); + str = uiprivToNSString(uiTableValueString(value)); + uiFreeTableValue(value); [self->b setTitle:str]; [self->b setEnabled:isCellEditable(self->m, row, self->editableColumn)]; diff --git a/test/page16.c b/test/page16.c index d00bd39c..51aa3a59 100644 --- a/test/page16.c +++ b/test/page16.c @@ -8,15 +8,15 @@ static int modelNumColumns(uiTableModelHandler *mh, uiTableModel *m) return 9; } -static uiTableDataType modelColumnType(uiTableModelHandler *mh, uiTableModel *m, int column) +static uiTableValueType modelColumnType(uiTableModelHandler *mh, uiTableModel *m, int column) { if (column == 3 || column == 4) - return uiTableDataTypeColor; + return uiTableValueTypeColor; if (column == 5) - return uiTableDataTypeImage; + return uiTableValueTypeImage; if (column == 7 || column == 8) - return uiTableDataTypeInt; - return uiTableDataTypeString; + return uiTableValueTypeInt; + return uiTableValueTypeString; } static int modelNumRows(uiTableModelHandler *mh, uiTableModel *m) @@ -29,39 +29,39 @@ static char row9text[1024]; static int yellowRow = -1; static int checkStates[15]; -static uiTableData *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col) +static uiTableValue *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col) { char buf[256]; if (col == 3) { if (row == yellowRow) - return uiNewTableDataColor(1, 1, 0, 1); + return uiNewTableValueColor(1, 1, 0, 1); if (row == 3) - return uiNewTableDataColor(1, 0, 0, 1); + return uiNewTableValueColor(1, 0, 0, 1); if (row == 11) - return uiNewTableDataColor(0, 0.5, 1, 0.5); + return uiNewTableValueColor(0, 0.5, 1, 0.5); return NULL; } if (col == 4) { if ((row % 2) == 1) - return uiNewTableDataColor(0.5, 0, 0.75, 1); + return uiNewTableValueColor(0.5, 0, 0.75, 1); return NULL; } if (col == 5) { if (row < 8) - return uiNewTableDataImage(img[0]); - return uiNewTableDataImage(img[1]); + return uiNewTableValueImage(img[0]); + return uiNewTableValueImage(img[1]); } if (col == 7) - return uiNewTableDataInt(checkStates[row]); + return uiNewTableValueInt(checkStates[row]); if (col == 8) { if (row == 0) - return uiNewTableDataInt(0); + return uiNewTableValueInt(0); if (row == 13) - return uiNewTableDataInt(100); + return uiNewTableValueInt(100); if (row == 14) - return uiNewTableDataInt(-1); - return uiNewTableDataInt(50); + return uiNewTableValueInt(-1); + return uiNewTableValueInt(50); } switch (col) { case 0: @@ -69,7 +69,7 @@ static uiTableData *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int break; case 2: if (row == 9) - return uiNewTableDataString(row9text); + return uiNewTableValueString(row9text); // fall through case 1: strcpy(buf, "Part"); @@ -78,13 +78,13 @@ static uiTableData *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int strcpy(buf, "Make Yellow"); break; } - return uiNewTableDataString(buf); + return uiNewTableValueString(buf); } -static void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col, const uiTableData *val) +static void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col, const uiTableValue *val) { if (row == 9 && col == 2) - strcpy(row9text, uiTableDataString(val)); + strcpy(row9text, uiTableValueString(val)); if (col == 6) { int prevYellowRow; @@ -95,7 +95,7 @@ static void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, uiTableModelRowChanged(m, yellowRow); } if (col == 7) - checkStates[row] = uiTableDataInt(val); + checkStates[row] = uiTableValueInt(val); } uiBox *makePage16(void) diff --git a/uitable.h b/uitable.h index 077e77f9..7a58b792 100644 --- a/uitable.h +++ b/uitable.h @@ -8,32 +8,32 @@ _UI_EXTERN uiImage *uiNewImage(double width, double height); _UI_EXTERN void uiFreeImage(uiImage *i); _UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride); -typedef struct uiTableData uiTableData; +typedef struct uiTableValue uiTableValue; -_UI_EXTERN void uiFreeTableData(uiTableData *d); +_UI_EXTERN void uiFreeTableValue(uiTableValue *v); // TODO actually validate these -_UI_ENUM(uiTableDataType) { - uiTableDataTypeString, - uiTableDataTypeImage, - uiTableDataTypeInt, - uiTableDataTypeColor, +_UI_ENUM(uiTableValueType) { + uiTableValueTypeString, + uiTableValueTypeImage, + uiTableValueTypeInt, + uiTableValueTypeColor, }; // TODO I don't like this name -_UI_EXTERN uiTableDataType uiTableDataGetType(const uiTableData *d); +_UI_EXTERN uiTableValueType uiTableValueGetType(const uiTableValue *v); -_UI_EXTERN uiTableData *uiNewTableDataString(const char *str); -_UI_EXTERN const char *uiTableDataString(const uiTableData *d); +_UI_EXTERN uiTableValue *uiNewTableValueString(const char *str); +_UI_EXTERN const char *uiTableValueString(const uiTableValue *v); -_UI_EXTERN uiTableData *uiNewTableDataImage(uiImage *img); -_UI_EXTERN uiImage *uiTableDataImage(const uiTableData *d); +_UI_EXTERN uiTableValue *uiNewTableValueImage(uiImage *img); +_UI_EXTERN uiImage *uiTableValueImage(const uiTableValue *v); -_UI_EXTERN uiTableData *uiNewTableDataInt(int i); -_UI_EXTERN int uiTableDataInt(const uiTableData *d); +_UI_EXTERN uiTableValue *uiNewTableValueInt(int i); +_UI_EXTERN int uiTableValueInt(const uiTableValue *v); -_UI_EXTERN uiTableData *uiNewTableDataColor(double r, double g, double b, double a); -_UI_EXTERN void uiTableDataColor(const uiTableData *d, double *r, double *g, double *b, double *a); +_UI_EXTERN uiTableValue *uiNewTableValueColor(double r, double g, double b, double a); +_UI_EXTERN void uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a); typedef struct uiTableModel uiTableModel; typedef struct uiTableModelHandler uiTableModelHandler; @@ -41,10 +41,10 @@ typedef struct uiTableModelHandler uiTableModelHandler; // TODO validate ranges; validate types on each getter/setter call (? table columns only?) struct uiTableModelHandler { int (*NumColumns)(uiTableModelHandler *, uiTableModel *); - uiTableDataType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int); + uiTableValueType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int); int (*NumRows)(uiTableModelHandler *, uiTableModel *); - uiTableData *(*CellValue)(uiTableModelHandler *, uiTableModel *, int, int); - void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const uiTableData *); + uiTableValue *(*CellValue)(uiTableModelHandler *, uiTableModel *, int, int); + void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const uiTableValue *); }; _UI_EXTERN uiTableModel *uiNewTableModel(uiTableModelHandler *mh); diff --git a/unix/table.c b/unix/table.c index 2e85fbd8..82755fb0 100644 --- a/unix/table.c +++ b/unix/table.c @@ -74,7 +74,7 @@ static void applyBackgroundColor(uiTable *t, GtkTreeModel *m, GtkTreeIter *iter, r, "cell-background-rgba", "cell-background-set"); } -static void onEdited(uiTableModel *m, int column, const char *pathstr, const uiTableData *data, GtkTreeIter *iter) +static void onEdited(uiTableModel *m, int column, const char *pathstr, const uiTableValue *tvalue, GtkTreeIter *iter) { GtkTreePath *path; int row; @@ -84,7 +84,7 @@ static void onEdited(uiTableModel *m, int column, const char *pathstr, const uiT if (iter != NULL) gtk_tree_model_get_iter(GTK_TREE_MODEL(m), iter, path); gtk_tree_path_free(path); - (*(m->mh->SetCellValue))(m->mh, m, row, column, data); + (*(m->mh->SetCellValue))(m->mh, m, row, column, tvalue); } // TODO deduplicate this between platforms @@ -123,12 +123,12 @@ static void textColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTree static void textColumnEdited(GtkCellRendererText *r, gchar *path, gchar *newText, gpointer data) { struct textColumnParams *p = (struct textColumnParams *) data; - uiTableData *tdata; + uiTableValue *tvalue; GtkTreeIter iter; - tdata = uiNewTableDataString(newText); - onEdited(p->m, p->modelColumn, path, tdata, &iter); - uiFreeTableData(tdata); + tvalue = uiNewTableValueString(newText); + onEdited(p->m, p->modelColumn, path, tvalue, &iter); + uiFreeTableValue(tvalue); // and update the column TODO copy comment here textColumnDataFunc(NULL, GTK_CELL_RENDERER(r), GTK_TREE_MODEL(p->m), &iter, data); } @@ -183,7 +183,7 @@ static void checkboxColumnToggled(GtkCellRendererToggle *r, gchar *pathstr, gpoi struct checkboxColumnParams *p = (struct checkboxColumnParams *) data; GValue value = G_VALUE_INIT; int v; - uiTableData *tdata; + uiTableValue *tvalue; GtkTreePath *path; GtkTreeIter iter; @@ -193,9 +193,9 @@ static void checkboxColumnToggled(GtkCellRendererToggle *r, gchar *pathstr, gpoi gtk_tree_model_get_value(GTK_TREE_MODEL(p->m), &iter, p->modelColumn, &value); v = g_value_get_int(&value); g_value_unset(&value); - tdata = uiNewTableDataInt(!v); - onEdited(p->m, p->modelColumn, pathstr, tdata, NULL); - uiFreeTableData(tdata); + tvalue = uiNewTableValueInt(!v); + onEdited(p->m, p->modelColumn, pathstr, tvalue, NULL); + uiFreeTableValue(tvalue); // and update the column TODO copy comment here // TODO avoid fetching the model data twice checkboxColumnDataFunc(NULL, GTK_CELL_RENDERER(r), GTK_TREE_MODEL(p->m), &iter, data); diff --git a/unix/tablemodel.c b/unix/tablemodel.c index 3c6ee6d3..fa36a14a 100644 --- a/unix/tablemodel.c +++ b/unix/tablemodel.c @@ -39,13 +39,13 @@ static GType uiTableModel_get_column_type(GtkTreeModel *mm, gint index) uiTableModel *m = uiTableModel(mm); switch ((*(m->mh->ColumnType))(m->mh, m, index)) { - case uiTableDataTypeString: + case uiTableValueTypeString: return G_TYPE_STRING; - case uiTableDataTypeImage: + case uiTableValueTypeImage: return G_TYPE_POINTER; - case uiTableDataTypeInt: + case uiTableValueTypeInt: return G_TYPE_INT; - case uiTableDataTypeColor: + case uiTableValueTypeColor: return GDK_TYPE_RGBA; } // TODO @@ -91,38 +91,38 @@ static void uiTableModel_get_value(GtkTreeModel *mm, GtkTreeIter *iter, gint col { uiTableModel *m = uiTableModel(mm); gint row; - uiTableData *data; + uiTableValue *tvalue; double r, g, b, a; GdkRGBA rgba; if (iter->stamp != STAMP_GOOD) return; row = GPOINTER_TO_INT(iter->user_data); - data = (*(m->mh->CellValue))(m->mh, m, row, column); + tvalue = (*(m->mh->CellValue))(m->mh, m, row, column); switch ((*(m->mh->ColumnType))(m->mh, m, column)) { - case uiTableDataTypeString: + case uiTableValueTypeString: g_value_init(value, G_TYPE_STRING); - g_value_set_string(value, uiTableDataString(data)); - uiFreeTableData(data); + g_value_set_string(value, uiTableValueString(tvalue)); + uiFreeTableValue(tvalue); return; - case uiTableDataTypeImage: + case uiTableValueTypeImage: g_value_init(value, G_TYPE_POINTER); - g_value_set_pointer(value, uiTableDataImage(data)); - uiFreeTableData(data); + g_value_set_pointer(value, uiTableValueImage(tvalue)); + uiFreeTableValue(tvalue); return; - case uiTableDataTypeInt: + case uiTableValueTypeInt: g_value_init(value, G_TYPE_INT); - g_value_set_int(value, uiTableDataInt(data)); - uiFreeTableData(data); + g_value_set_int(value, uiTableValueInt(tvalue)); + uiFreeTableValue(tvalue); return; - case uiTableDataTypeColor: + case uiTableValueTypeColor: g_value_init(value, GDK_TYPE_RGBA); - if (data == NULL) { + if (tvalue == NULL) { g_value_set_boxed(value, NULL); return; } - uiTableDataColor(data, &r, &g, &b, &a); - uiFreeTableData(data); + uiTableValueColor(tvalue, &r, &g, &b, &a); + uiFreeTableValue(tvalue); rgba.red = r; rgba.green = g; rgba.blue = b; diff --git a/windows/table.cpp b/windows/table.cpp index 315f5e37..625ee8d3 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -188,16 +188,16 @@ static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos) { - uiTableData *data; + uiTableValue *value; int progress; std::pair p; std::map, LONG>::iterator iter; bool startTimer = false; bool stopTimer = false; - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, item, modelColumn); - progress = uiTableDataInt(data); - uiFreeTableData(data); + value = (*(t->model->mh->CellValue))(t->model->mh, t->model, item, modelColumn); + progress = uiTableValueInt(value); + uiFreeTableValue(value); p.first = item; p.second = subitem; diff --git a/windows/tabledispinfo.cpp b/windows/tabledispinfo.cpp index f8a06842..25606903 100644 --- a/windows/tabledispinfo.cpp +++ b/windows/tabledispinfo.cpp @@ -8,7 +8,7 @@ static HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) { int strcol; - uiTableData *data; + uiTableValue *value; WCHAR *wstr; int progress; HRESULT hr; @@ -22,9 +22,9 @@ static HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnP else if (p->buttonModelColumn != -1) strcol = p->buttonModelColumn; if (strcol != -1) { - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, strcol); - wstr = toUTF16(uiTableDataString(data)); - uiFreeTableData(data); + value = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, strcol); + wstr = toUTF16(uiTableValueString(value)); + uiFreeTableValue(value); // We *could* just make pszText into a freshly allocated // conversion and avoid the limitation of cchTextMax. // But then, we would have to keep things around for some @@ -59,7 +59,7 @@ static HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnP static HRESULT handleLVIF_IMAGE(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) { - uiTableData *data; + uiTableValue *value; HRESULT hr; if (nm->item.iSubItem == 0 && p->imageModelColumn == -1 && p->checkboxModelColumn == -1) { @@ -75,7 +75,7 @@ static HRESULT handleLVIF_IMAGE(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumn return S_OK; // nothing to do here // TODO see if the -1 part is correct - // TODO see if we should use state instead of images for checkbox data + // TODO see if we should use state instead of images for checkbox value nm->item.iImage = -1; if (p->imageModelColumn != -1 || p->checkboxModelColumn != -1) nm->item.iImage = 0; diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index c693d794..bf0f730d 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -67,7 +67,7 @@ static void centerImageRect(RECT *image, RECT *space) static HRESULT drawImagePart(HRESULT hr, struct drawState *s) { - uiTableData *data; + uiTableValue *value; IWICBitmap *wb; HBITMAP b; RECT r; @@ -78,9 +78,9 @@ static HRESULT drawImagePart(HRESULT hr, struct drawState *s) if (s->p->imageModelColumn == -1) return S_OK; - data = cellValue(s->model, s->iItem, s->p->imageModelColumn); - wb = uiprivImageAppropriateForDC(uiTableDataImage(data), s->dc); - uiFreeTableData(data); + value = cellValue(s->model, s->iItem, s->p->imageModelColumn); + wb = uiprivImageAppropriateForDC(uiTableValueImage(value), s->dc); + uiFreeTableValue(value); hr = uiprivWICToGDI(wb, s->dc, s->m->cxIcon, s->m->cyIcon, &b); if (hr != S_OK) @@ -188,7 +188,7 @@ static HRESULT drawThemedCheckbox(struct drawState *s, HTHEME theme, int checked static HRESULT drawCheckboxPart(HRESULT hr, struct drawState *s) { - uiTableData *data; + uiTableValue *value; int checked, enabled; HTHEME theme; @@ -197,9 +197,9 @@ static HRESULT drawCheckboxPart(HRESULT hr, struct drawState *s) if (s->p->checkboxModelColumn == -1) return S_OK; - data = cellValue(s->model, s->iItem, s->p->checkboxModelColumn); - checked = uiTableDataInt(data); - uiFreeTableData(data); + value = cellValue(s->model, s->iItem, s->p->checkboxModelColumn); + checked = uiTableValueInt(value); + uiFreeTableValue(value); switch (s->p->checkboxEditableColumn) { case uiTableModelColumnNeverEditable: enabled = 0; @@ -208,9 +208,9 @@ static HRESULT drawCheckboxPart(HRESULT hr, struct drawState *s) enabled = 1; break; default: - data = cellValue(s->model, s->iItem, s->p->checkboxEditableColumn); - enabled = uiTableDataInt(data); - uiFreeTableData(data); + value = cellValue(s->model, s->iItem, s->p->checkboxEditableColumn); + enabled = uiTableValueInt(value); + uiFreeTableValue(value); } theme = OpenThemeData(s->t->hwnd, L"button"); @@ -236,7 +236,7 @@ static HRESULT drawTextPart(HRESULT hr, struct drawState *s) COLORREF prevText; int prevMode; RECT r; - uiTableData *data; + uiTableValue *value; WCHAR *wstr; if (hr != S_OK) @@ -258,9 +258,9 @@ static HRESULT drawTextPart(HRESULT hr, struct drawState *s) return E_FAIL; } - data = cellValue(s->model, s->iItem, s->p->textModelColumn); - wstr = toUTF16(uiTableDataString(data)); - uiFreeTableData(data); + value = cellValue(s->model, s->iItem, s->p->textModelColumn); + wstr = toUTF16(uiTableValueString(value)); + uiFreeTableValue(value); // These flags are a menagerie of flags from various sources: // guessing, the Windows 2000 source leak, various custom // draw examples on the web, etc. @@ -402,7 +402,7 @@ fail: static HRESULT drawButtonPart(HRESULT hr, struct drawState *s) { - uiTableData *data; + uiTableValue *value; WCHAR *wstr; bool enabled; HTHEME theme; @@ -414,9 +414,9 @@ static HRESULT drawButtonPart(HRESULT hr, struct drawState *s) if (s->p->buttonModelColumn == -1) return S_OK; - data = cellValue(s->model, s->iItem, s->p->buttonModelColumn); - wstr = toUTF16(uiTableDataString(data)); - uiFreeTableData(data); + value = cellValue(s->model, s->iItem, s->p->buttonModelColumn); + wstr = toUTF16(uiTableValueString(value)); + uiFreeTableValue(value); switch (s->p->buttonClickableModelColumn) { case uiTableModelColumnNeverEditable: enabled = 0; @@ -425,9 +425,9 @@ static HRESULT drawButtonPart(HRESULT hr, struct drawState *s) enabled = 1; break; default: - data = cellValue(s->model, s->iItem, s->p->checkboxEditableColumn); - enabled = uiTableDataInt(data); - uiFreeTableData(data); + value = cellValue(s->model, s->iItem, s->p->checkboxEditableColumn); + enabled = uiTableValueInt(value); + uiFreeTableValue(value); } theme = OpenThemeData(s->t->hwnd, L"button"); @@ -574,16 +574,16 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm s->textColor = GetSysColor(COLOR_HIGHLIGHTTEXT); s->textBrush = GetSysColorBrush(COLOR_HIGHLIGHTTEXT); } else { - uiTableData *data; + uiTableValue *value; double r, g, b, a; s->bgColor = GetSysColor(COLOR_WINDOW); s->bgBrush = GetSysColorBrush(COLOR_WINDOW); if (t->backgroundColumn != -1) { - data = cellValue(s->model, s->iItem, t->backgroundColumn); - if (data != NULL) { - uiTableDataColor(data, &r, &g, &b, &a); - uiFreeTableData(data); + value = cellValue(s->model, s->iItem, t->backgroundColumn); + if (value != NULL) { + uiTableValueColor(value, &r, &g, &b, &a); + uiFreeTableValue(value); s->bgColor = blend(s->bgColor, r, g, b, a); s->bgBrush = CreateSolidBrush(s->bgColor); if (s->bgBrush == NULL) { @@ -597,10 +597,10 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm s->textColor = GetSysColor(COLOR_WINDOWTEXT); s->textBrush = GetSysColorBrush(COLOR_WINDOWTEXT); if (p->textParams.ColorModelColumn != -1) { - data = cellValue(s->model, s->iItem, p->textParams.ColorModelColumn); - if (data != NULL) { - uiTableDataColor(data, &r, &g, &b, &a); - uiFreeTableData(data); + value = cellValue(s->model, s->iItem, p->textParams.ColorModelColumn); + if (value != NULL) { + uiTableValueColor(value, &r, &g, &b, &a); + uiFreeTableValue(value); s->textColor = blend(s->bgColor, r, g, b, a); s->textBrush = CreateSolidBrush(s->textColor); if (s->textBrush == NULL) { diff --git a/windows/tableediting.cpp b/windows/tableediting.cpp index 4eb8d2d2..ccbfb825 100644 --- a/windows/tableediting.cpp +++ b/windows/tableediting.cpp @@ -95,7 +95,7 @@ static LRESULT CALLBACK editSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableColumnParams *p) { - uiTableData *data; + uiTableValue *value; WCHAR *wstr; HRESULT hr; @@ -105,9 +105,9 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC return hr; // the real list view creates the edit control with the string - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, iItem, p->textModelColumn); - wstr = toUTF16(uiTableDataString(data)); - uiFreeTableData(data); + value = (*(t->model->mh->CellValue))(t->model->mh, t->model, iItem, p->textModelColumn); + wstr = toUTF16(uiTableValueString(value)); + uiFreeTableValue(value); // TODO copy WS_EX_RTLREADING t->edit = CreateWindowExW(0, L"EDIT", wstr, @@ -157,17 +157,17 @@ HRESULT uiprivTableResizeWhileEditing(uiTable *t) HRESULT uiprivTableFinishEditingText(uiTable *t) { uiprivTableColumnParams *p; - uiTableData *data; + uiTableValue *value; char *text; if (t->edit == NULL) return S_OK; text = uiWindowsWindowText(t->edit); - data = uiNewTableDataString(text); + value = uiNewTableValueString(text); uiFreeText(text); p = (*(t->columns))[t->editedSubitem]; - (*(t->model->mh->SetCellValue))(t->model->mh, t->model, t->editedItem, p->textModelColumn, data); - uiFreeTableData(data); + (*(t->model->mh->SetCellValue))(t->model->mh, t->model, t->editedItem, p->textModelColumn, value); + uiFreeTableValue(value); // always refresh the value in case the model rejected it if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) (t->editedItem), 0) == (LRESULT) (-1)) { logLastError(L"LVM_UPDATE"); @@ -199,7 +199,7 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu uiprivTableColumnParams *p; int modelColumn, editableColumn; bool text, checkbox; - uiTableData *data; + uiTableValue *value; int checked, editable; HRESULT hr; @@ -238,9 +238,9 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu case uiTableModelColumnAlwaysEditable: break; default: - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, ht.iItem, editableColumn); - editable = uiTableDataInt(data); - uiFreeTableData(data); + value = (*(t->model->mh->CellValue))(t->model->mh, t->model, ht.iItem, editableColumn); + editable = uiTableValueInt(value); + uiFreeTableValue(value); if (!editable) goto done; } @@ -252,12 +252,12 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu } else if (checkbox) { if ((ht.flags & LVHT_ONITEMICON) == 0) goto done; - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, ht.iItem, modelColumn); - checked = uiTableDataInt(data); - uiFreeTableData(data); - data = uiNewTableDataInt(!checked); - (*(t->model->mh->SetCellValue))(t->model->mh, t->model, ht.iItem, modelColumn, data); - uiFreeTableData(data); + value = (*(t->model->mh->CellValue))(t->model->mh, t->model, ht.iItem, modelColumn); + checked = uiTableValueInt(value); + uiFreeTableValue(value); + value = uiNewTableValueInt(!checked); + (*(t->model->mh->SetCellValue))(t->model->mh, t->model, ht.iItem, modelColumn, value); + uiFreeTableValue(value); } else (*(t->model->mh->SetCellValue))(t->model->mh, t->model, ht.iItem, modelColumn, NULL); // always refresh the value in case the model rejected it From 74ec21f4c715341c1e375a735376cf872119c3b3 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 23 Jun 2018 23:35:42 -0400 Subject: [PATCH 135/166] Turned the direct method calls on uiTableModel into functions to make the call sites look a bit cleaner. More stuff will go into common/tablemodel.c... --- common/CMakeLists.txt | 1 + common/table.h | 17 +++++++++++++++ common/tablemodel.c | 44 +++++++++++++++++++++++++++++++++++++++ common/tablevalue.c | 1 + darwin/table.h | 1 + darwin/table.m | 9 ++++++-- darwin/tablecolumn.m | 23 +++++++++----------- unix/table.c | 2 +- unix/table.h | 1 + unix/tablemodel.c | 21 ++++++++++++------- windows/table.cpp | 13 ++++++++---- windows/table.hpp | 1 + windows/tabledispinfo.cpp | 2 +- windows/tabledraw.cpp | 19 +++++++---------- windows/tableediting.cpp | 12 +++++------ 15 files changed, 121 insertions(+), 46 deletions(-) create mode 100644 common/table.h create mode 100644 common/tablemodel.c diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 785c70db..0e1360d8 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -10,6 +10,7 @@ list(APPEND _LIBUI_SOURCES common/matrix.c common/opentype.c common/shouldquit.c + common/tablemodel.c common/tablevalue.c common/userbugs.c common/utf.c diff --git a/common/table.h b/common/table.h new file mode 100644 index 00000000..3c603674 --- /dev/null +++ b/common/table.h @@ -0,0 +1,17 @@ +// 23 june 2018 + +#ifdef __cplusplus +extern "C" { +#endif + +// tablemodel.c +extern uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m); +extern int uiprivTableModelNumColumns(uiTableModel *m); +extern uiTableValueType uiprivTableModelColumnType(uiTableModel *m, int column); +extern int uiprivTableModelNumRows(uiTableModel *m); +extern uiTableValue *uiprivTableModelCellValue(uiTableModel *m, int row, int column); +extern void uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const uiTableValue *value); + +#ifdef __cplusplus +} +#endif diff --git a/common/tablemodel.c b/common/tablemodel.c new file mode 100644 index 00000000..c7c52a3f --- /dev/null +++ b/common/tablemodel.c @@ -0,0 +1,44 @@ +// 23 june 2018 +#include "../ui.h" +#include "uipriv.h" +#include "table.h" + +int uiprivTableModelNumColumns(uiTableModel *m) +{ + uiTableModelHandler *mh; + + mh = uiprivTableModelHandler(m); + return (*(mh->NumColumns))(mh, m); +} + +uiTableValueType uiprivTableModelColumnType(uiTableModel *m, int column) +{ + uiTableModelHandler *mh; + + mh = uiprivTableModelHandler(m); + return (*(mh->ColumnType))(mh, m, column); +} + +int uiprivTableModelNumRows(uiTableModel *m) +{ + uiTableModelHandler *mh; + + mh = uiprivTableModelHandler(m); + return (*(mh->NumRows))(mh, m); +} + +uiTableValue *uiprivTableModelCellValue(uiTableModel *m, int row, int column) +{ + uiTableModelHandler *mh; + + mh = uiprivTableModelHandler(m); + return (*(mh->CellValue))(mh, m, row, column); +} + +void uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const uiTableValue *value) +{ + uiTableModelHandler *mh; + + mh = uiprivTableModelHandler(m); + (*(mh->SetCellValue))(mh, m, row, column, value); +} diff --git a/common/tablevalue.c b/common/tablevalue.c index b73a920e..10043de6 100644 --- a/common/tablevalue.c +++ b/common/tablevalue.c @@ -1,6 +1,7 @@ // 3 june 2018 #include "../ui.h" #include "uipriv.h" +#include "table.h" struct uiTableValue { uiTableValueType type; diff --git a/darwin/table.h b/darwin/table.h index b38e9378..4146ab71 100644 --- a/darwin/table.h +++ b/darwin/table.h @@ -1,4 +1,5 @@ // 3 june 2018 +#import "../common/table.h" // table.m // TODO get rid of forward declaration diff --git a/darwin/table.m b/darwin/table.m index 378fb95d..f148472d 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -37,7 +37,7 @@ static void setBackgroundColor(uiprivTableView *t, NSTableRowView *rv, NSInteger if (t->uiprivT->backgroundColumn == -1) return; - value = (*(t->uiprivM->mh->CellValue))(t->uiprivM->mh, t->uiprivM, row, t->uiprivT->backgroundColumn); + value = uiprivTableModelCellValue(t->uiprivM, row, t->uiprivT->backgroundColumn); if (value != NULL) { uiTableValueColor(value, &r, &g, &b, &a); uiFreeTableValue(value); @@ -70,7 +70,7 @@ static void setBackgroundColor(uiprivTableView *t, NSTableRowView *rv, NSInteger - (NSInteger)numberOfRowsInTableView:(NSTableView *)tv { - return (*(self->m->mh->NumRows))(self->m->mh, self->m); + return uiprivTableModelNumRows(self->m); } - (NSView *)tableView:(NSTableView *)tv viewForTableColumn:(NSTableColumn *)cc row:(NSInteger)row @@ -155,6 +155,11 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) // set is autoreleased } +uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m) +{ + return m->mh; +} + uiDarwinControlAllDefaultsExceptDestroy(uiTable, sv) static void uiTableDestroy(uiControl *c) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 2e2e8299..d2ff2577 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -45,7 +45,7 @@ static BOOL isCellEditable(uiTableModel *m, NSInteger row, int modelColumn) case uiTableModelColumnAlwaysEditable: return YES; } - value = (*(m->mh->CellValue))(m->mh, m, row, modelColumn); + value = uiprivTableModelCellValue(m, row, modelColumn); editable = uiTableValueInt(value); uiFreeTableValue(value); return editable != 0; @@ -271,7 +271,7 @@ struct textColumnCreateParams { NSString *str; NSColor *color; - value = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textModelColumn); + value = uiprivTableModelCellValue(self->m, row, self->textModelColumn); str = uiprivToNSString(uiTableValueString(value)); uiFreeTableValue(value); [self->tf setStringValue:str]; @@ -282,7 +282,7 @@ struct textColumnCreateParams { if (self->textParams.ColorModelColumn != -1) { double r, g, b, a; - value = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->textParams.ColorModelColumn); + value = uiprivTableModelCellValue(self->m, row, self->textParams.ColorModelColumn); // TODO document this is allowed if (value != NULL) { uiTableValueColor(value, &r, &g, &b, &a); @@ -298,13 +298,13 @@ struct textColumnCreateParams { if (self->iv != nil) { uiImage *img; - value = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->imageModelColumn); + value = uiprivTableModelCellValue(self->m, row, self->imageModelColumn); img = uiTableValueImage(value); uiFreeTableValue(value); [self->iv setImage:uiprivImageNSImage(img)]; } if (self->cb != nil) { - value = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->imageModelColumn); + value = uiprivTableModelCellValue(self->m, row, self->imageModelColumn); if (uiTableValueInt(value) != 0) [self->cb setState:NSOnState]; else @@ -322,8 +322,7 @@ struct textColumnCreateParams { row = [self->t->tv rowForView:self->tf]; value = uiNewTableValueString([[self->tf stringValue] UTF8String]); - (*(self->m->mh->SetCellValue))(self->m->mh, self->m, - row, self->textModelColumn, value); + uiprivTableModelSetCellValue(self->m, row, self->textModelColumn, value); uiFreeTableValue(value); // always refresh the value in case the model rejected it // TODO document that we do this, but not for the whole row (or decide to do both, or do neither...) @@ -337,8 +336,7 @@ struct textColumnCreateParams { row = [self->t->tv rowForView:self->cb]; value = uiNewTableValueInt([self->cb state] != NSOffState); - (*(self->m->mh->SetCellValue))(self->m->mh, self->m, - row, self->checkboxModelColumn, value); + uiprivTableModelSetCellValue(self->m, row, self->checkboxModelColumn, value); uiFreeTableValue(value); // always refresh the value in case the model rejected it [self uiprivUpdate:row]; @@ -436,7 +434,7 @@ struct textColumnCreateParams { uiTableValue *value; int progress; - value = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->modelColumn); + value = uiprivTableModelCellValue(self->m, row, self->modelColumn); progress = uiTableValueInt(value); uiFreeTableValue(value); if (progress == -1) { @@ -559,7 +557,7 @@ struct textColumnCreateParams { uiTableValue *value; NSString *str; - value = (*(self->m->mh->CellValue))(self->m->mh, self->m, row, self->modelColumn); + value = uiprivTableModelCellValue(self->m, row, self->modelColumn); str = uiprivToNSString(uiTableValueString(value)); uiFreeTableValue(value); [self->b setTitle:str]; @@ -572,8 +570,7 @@ struct textColumnCreateParams { NSInteger row; row = [self->t->tv rowForView:self->b]; - (*(self->m->mh->SetCellValue))(self->m->mh, self->m, - row, self->modelColumn, NULL); + uiprivTableModelSetCellValue(self->m, row, self->modelColumn, NULL); // TODO document we DON'T update the cell after doing this } diff --git a/unix/table.c b/unix/table.c index 82755fb0..cd9734d3 100644 --- a/unix/table.c +++ b/unix/table.c @@ -84,7 +84,7 @@ static void onEdited(uiTableModel *m, int column, const char *pathstr, const uiT if (iter != NULL) gtk_tree_model_get_iter(GTK_TREE_MODEL(m), iter, path); gtk_tree_path_free(path); - (*(m->mh->SetCellValue))(m->mh, m, row, column, tvalue); + uiprivTableModelSetCellValue(m, row, column, tvalue); } // TODO deduplicate this between platforms diff --git a/unix/table.h b/unix/table.h index a023e7f5..af7e954a 100644 --- a/unix/table.h +++ b/unix/table.h @@ -1,4 +1,5 @@ // 4 june 2018 +#include "../common/table.h" // tablemodel.c #define uiTableModelType (uiTableModel_get_type()) diff --git a/unix/tablemodel.c b/unix/tablemodel.c index fa36a14a..ee957b58 100644 --- a/unix/tablemodel.c +++ b/unix/tablemodel.c @@ -31,14 +31,14 @@ static gint uiTableModel_get_n_columns(GtkTreeModel *mm) { uiTableModel *m = uiTableModel(mm); - return (*(m->mh->NumColumns))(m->mh, m); + return uiprivTableModelNumColumns(m); } static GType uiTableModel_get_column_type(GtkTreeModel *mm, gint index) { uiTableModel *m = uiTableModel(mm); - switch ((*(m->mh->ColumnType))(m->mh, m, index)) { + switch (uiprivTableModelColumnType(m, index)) { case uiTableValueTypeString: return G_TYPE_STRING; case uiTableValueTypeImage: @@ -65,7 +65,7 @@ static gboolean uiTableModel_get_iter(GtkTreeModel *mm, GtkTreeIter *iter, GtkTr row = gtk_tree_path_get_indices(path)[0]; if (row < 0) goto bad; - if (row >= (*(m->mh->NumRows))(m->mh, m)) + if (row >= uiprivTableModelNumRows(m)) goto bad; iter->stamp = STAMP_GOOD; iter->user_data = GINT_TO_POINTER(row); @@ -98,8 +98,8 @@ static void uiTableModel_get_value(GtkTreeModel *mm, GtkTreeIter *iter, gint col if (iter->stamp != STAMP_GOOD) return; row = GPOINTER_TO_INT(iter->user_data); - tvalue = (*(m->mh->CellValue))(m->mh, m, row, column); - switch ((*(m->mh->ColumnType))(m->mh, m, column)) { + tvalue = uiprivTableModelCellValue(m, row, column); + switch (uiprivTableModelColumnType(m, column)) { case uiTableValueTypeString: g_value_init(value, G_TYPE_STRING); g_value_set_string(value, uiTableValueString(tvalue)); @@ -142,7 +142,7 @@ static gboolean uiTableModel_iter_next(GtkTreeModel *mm, GtkTreeIter *iter) return FALSE; row = GPOINTER_TO_INT(iter->user_data); row++; - if (row >= (*(m->mh->NumRows))(m->mh, m)) { + if (row >= uiprivTableModelNumRows(m)) { iter->stamp = STAMP_BAD; return FALSE; } @@ -182,7 +182,7 @@ static gint uiTableModel_iter_n_children(GtkTreeModel *mm, GtkTreeIter *iter) if (iter != NULL) return 0; - return (*(m->mh->NumRows))(m->mh, m); + return uiprivTableModelNumRows(m); } static gboolean uiTableModel_iter_nth_child(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent, gint n) @@ -195,7 +195,7 @@ static gboolean uiTableModel_iter_nth_child(GtkTreeModel *mm, GtkTreeIter *iter, goto bad; if (n < 0) goto bad; - if (n >= (*(m->mh->NumRows))(m->mh, m)) + if (n >= uiprivTableModelNumRows(m)) goto bad; iter->stamp = STAMP_GOOD; iter->user_data = GINT_TO_POINTER(n); @@ -281,3 +281,8 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) gtk_tree_model_row_deleted(GTK_TREE_MODEL(m), path); gtk_tree_path_free(path); } + +uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m) +{ + return m->mh; +} diff --git a/windows/table.cpp b/windows/table.cpp index 625ee8d3..372a699b 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -33,7 +33,7 @@ void uiTableModelRowInserted(uiTableModel *m, int newIndex) LVITEMW item; int newCount; - newCount = (*(m->mh->NumRows))(m->mh, m); + newCount = uiprivTableModelNumRows(m); ZeroMemory(&item, sizeof (LVITEMW)); item.mask = 0; item.iItem = newIndex; @@ -66,7 +66,7 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) { int newCount; - newCount = (*(m->mh->NumRows))(m->mh, m); + newCount = uiprivTableModelNumRows(m); newCount--; for (auto t : *(m->tables)) { // update selection state @@ -82,6 +82,11 @@ void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) } } +uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m) +{ + return m->mh; +} + // TODO explain all this static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData) { @@ -195,7 +200,7 @@ int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG bool startTimer = false; bool stopTimer = false; - value = (*(t->model->mh->CellValue))(t->model->mh, t->model, item, modelColumn); + value = uiprivTableModelCellValue(t->model, item, modelColumn); progress = uiTableValueInt(value); uiFreeTableValue(value); @@ -508,7 +513,7 @@ uiTable *uiNewTable(uiTableModel *model) SendMessageW(t->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, (WPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_SUBITEMIMAGES), (LPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_SUBITEMIMAGES)); - n = (*(model->mh->NumRows))(model->mh, model); + n = uiprivTableModelNumRows(model); if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0) logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()"); diff --git a/windows/table.hpp b/windows/table.hpp index e997395f..65a70fae 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -1,4 +1,5 @@ // 10 june 2018 +#include "../common/table.h" // table.cpp #define uiprivNumLVN_GETDISPINFOSkip 3 diff --git a/windows/tabledispinfo.cpp b/windows/tabledispinfo.cpp index 25606903..4198a1a2 100644 --- a/windows/tabledispinfo.cpp +++ b/windows/tabledispinfo.cpp @@ -22,7 +22,7 @@ static HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnP else if (p->buttonModelColumn != -1) strcol = p->buttonModelColumn; if (strcol != -1) { - value = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, strcol); + value = uiprivTableModelCellValue(t->model, nm->item.iItem, strcol); wstr = toUTF16(uiTableValueString(value)); uiFreeTableValue(value); // We *could* just make pszText into a freshly allocated diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index bf0f730d..ffa901fc 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -5,9 +5,6 @@ // TODOs: // - properly hide selection when not focused (or switch on LVS_SHOWSELALWAYS and draw that state) -// TODO -#define cellValue(model, row, column) ((*((model)->mh->CellValue))((model)->mh, (model), (row), (column))) - // TODO maybe split this into item and subitem structs? struct drawState { uiTable *t; @@ -78,7 +75,7 @@ static HRESULT drawImagePart(HRESULT hr, struct drawState *s) if (s->p->imageModelColumn == -1) return S_OK; - value = cellValue(s->model, s->iItem, s->p->imageModelColumn); + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->imageModelColumn); wb = uiprivImageAppropriateForDC(uiTableValueImage(value), s->dc); uiFreeTableValue(value); @@ -197,7 +194,7 @@ static HRESULT drawCheckboxPart(HRESULT hr, struct drawState *s) if (s->p->checkboxModelColumn == -1) return S_OK; - value = cellValue(s->model, s->iItem, s->p->checkboxModelColumn); + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxModelColumn); checked = uiTableValueInt(value); uiFreeTableValue(value); switch (s->p->checkboxEditableColumn) { @@ -208,7 +205,7 @@ static HRESULT drawCheckboxPart(HRESULT hr, struct drawState *s) enabled = 1; break; default: - value = cellValue(s->model, s->iItem, s->p->checkboxEditableColumn); + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxEditableColumn); enabled = uiTableValueInt(value); uiFreeTableValue(value); } @@ -258,7 +255,7 @@ static HRESULT drawTextPart(HRESULT hr, struct drawState *s) return E_FAIL; } - value = cellValue(s->model, s->iItem, s->p->textModelColumn); + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->textModelColumn); wstr = toUTF16(uiTableValueString(value)); uiFreeTableValue(value); // These flags are a menagerie of flags from various sources: @@ -414,7 +411,7 @@ static HRESULT drawButtonPart(HRESULT hr, struct drawState *s) if (s->p->buttonModelColumn == -1) return S_OK; - value = cellValue(s->model, s->iItem, s->p->buttonModelColumn); + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->buttonModelColumn); wstr = toUTF16(uiTableValueString(value)); uiFreeTableValue(value); switch (s->p->buttonClickableModelColumn) { @@ -425,7 +422,7 @@ static HRESULT drawButtonPart(HRESULT hr, struct drawState *s) enabled = 1; break; default: - value = cellValue(s->model, s->iItem, s->p->checkboxEditableColumn); + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxEditableColumn); enabled = uiTableValueInt(value); uiFreeTableValue(value); } @@ -580,7 +577,7 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm s->bgColor = GetSysColor(COLOR_WINDOW); s->bgBrush = GetSysColorBrush(COLOR_WINDOW); if (t->backgroundColumn != -1) { - value = cellValue(s->model, s->iItem, t->backgroundColumn); + value = uiprivTableModelCellValue(s->model, s->iItem, t->backgroundColumn); if (value != NULL) { uiTableValueColor(value, &r, &g, &b, &a); uiFreeTableValue(value); @@ -597,7 +594,7 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm s->textColor = GetSysColor(COLOR_WINDOWTEXT); s->textBrush = GetSysColorBrush(COLOR_WINDOWTEXT); if (p->textParams.ColorModelColumn != -1) { - value = cellValue(s->model, s->iItem, p->textParams.ColorModelColumn); + value = uiprivTableModelCellValue(s->model, s->iItem, p->textParams.ColorModelColumn); if (value != NULL) { uiTableValueColor(value, &r, &g, &b, &a); uiFreeTableValue(value); diff --git a/windows/tableediting.cpp b/windows/tableediting.cpp index ccbfb825..f96e70e6 100644 --- a/windows/tableediting.cpp +++ b/windows/tableediting.cpp @@ -105,7 +105,7 @@ static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableC return hr; // the real list view creates the edit control with the string - value = (*(t->model->mh->CellValue))(t->model->mh, t->model, iItem, p->textModelColumn); + value = uiprivTableModelCellValue(t->model, iItem, p->textModelColumn); wstr = toUTF16(uiTableValueString(value)); uiFreeTableValue(value); // TODO copy WS_EX_RTLREADING @@ -166,7 +166,7 @@ HRESULT uiprivTableFinishEditingText(uiTable *t) value = uiNewTableValueString(text); uiFreeText(text); p = (*(t->columns))[t->editedSubitem]; - (*(t->model->mh->SetCellValue))(t->model->mh, t->model, t->editedItem, p->textModelColumn, value); + uiprivTableModelSetCellValue(t->model, t->editedItem, p->textModelColumn, value); uiFreeTableValue(value); // always refresh the value in case the model rejected it if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) (t->editedItem), 0) == (LRESULT) (-1)) { @@ -238,7 +238,7 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu case uiTableModelColumnAlwaysEditable: break; default: - value = (*(t->model->mh->CellValue))(t->model->mh, t->model, ht.iItem, editableColumn); + value = uiprivTableModelCellValue(t->model, ht.iItem, editableColumn); editable = uiTableValueInt(value); uiFreeTableValue(value); if (!editable) @@ -252,14 +252,14 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu } else if (checkbox) { if ((ht.flags & LVHT_ONITEMICON) == 0) goto done; - value = (*(t->model->mh->CellValue))(t->model->mh, t->model, ht.iItem, modelColumn); + value = uiprivTableModelCellValue(t->model, ht.iItem, modelColumn); checked = uiTableValueInt(value); uiFreeTableValue(value); value = uiNewTableValueInt(!checked); - (*(t->model->mh->SetCellValue))(t->model->mh, t->model, ht.iItem, modelColumn, value); + uiprivTableModelSetCellValue(t->model, ht.iItem, modelColumn, value); uiFreeTableValue(value); } else - (*(t->model->mh->SetCellValue))(t->model->mh, t->model, ht.iItem, modelColumn, NULL); + uiprivTableModelSetCellValue(t->model, ht.iItem, modelColumn, NULL); // always refresh the value in case the model rejected it if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) (ht.iItem), 0) == (LRESULT) (-1)) { logLastError(L"LVM_UPDATE"); From 8f4598f6419cfaeb02950b1cc7856895dc1411ce Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 23 Jun 2018 23:45:58 -0400 Subject: [PATCH 136/166] Finally removed the extra uiImage declarations that were there for long-irrelevant compiler errors. --- darwin/uipriv_darwin.h | 2 -- unix/uipriv_unix.h | 1 - 2 files changed, 3 deletions(-) diff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h index bc8b1837..5d50f623 100644 --- a/darwin/uipriv_darwin.h +++ b/darwin/uipriv_darwin.h @@ -23,8 +23,6 @@ #define NSAppKitVersionNumber10_9 1265 #endif -/*TODO remove this*/typedef struct uiImage uiImage; - // map.m typedef struct uiprivMap uiprivMap; extern uiprivMap *uiprivNewMap(void); diff --git a/unix/uipriv_unix.h b/unix/uipriv_unix.h index 27fd673b..de604ced 100644 --- a/unix/uipriv_unix.h +++ b/unix/uipriv_unix.h @@ -48,7 +48,6 @@ extern uiDrawContext *uiprivNewContext(cairo_t *cr, GtkStyleContext *style); extern void uiprivFreeContext(uiDrawContext *); // image.c -/*TODO remove this*/typedef struct uiImage uiImage; extern cairo_surface_t *uiprivImageAppropriateSurface(uiImage *i, GtkWidget *w); // cellrendererbutton.c From 9b340ed40c40254b0df2490565e00735d34eab8c Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 23 Jun 2018 23:48:47 -0400 Subject: [PATCH 137/166] Fixed checkboxes on Mac OS X. --- darwin/tablecolumn.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index d2ff2577..d37daf8a 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -304,7 +304,7 @@ struct textColumnCreateParams { [self->iv setImage:uiprivImageNSImage(img)]; } if (self->cb != nil) { - value = uiprivTableModelCellValue(self->m, row, self->imageModelColumn); + value = uiprivTableModelCellValue(self->m, row, self->checkboxModelColumn); if (uiTableValueInt(value) != 0) [self->cb setState:NSOnState]; else From 4dbf1994a63d3c7a8e9ca90237b1bbcf08fd01bd Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 24 Jun 2018 00:45:54 -0400 Subject: [PATCH 138/166] Deduplicated the default text column parameters across platforms. --- common/table.h | 1 + common/tablemodel.c | 4 ++++ darwin/tablecolumn.m | 25 ++++++++++++------------- unix/table.c | 7 +------ windows/table.cpp | 6 +----- 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/common/table.h b/common/table.h index 3c603674..21ce2d11 100644 --- a/common/table.h +++ b/common/table.h @@ -11,6 +11,7 @@ extern uiTableValueType uiprivTableModelColumnType(uiTableModel *m, int column); extern int uiprivTableModelNumRows(uiTableModel *m); extern uiTableValue *uiprivTableModelCellValue(uiTableModel *m, int row, int column); extern void uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const uiTableValue *value); +extern const uiTableTextColumnOptionalParams uiprivDefaultTextColumnOptionalParams; #ifdef __cplusplus } diff --git a/common/tablemodel.c b/common/tablemodel.c index c7c52a3f..b1cd1e89 100644 --- a/common/tablemodel.c +++ b/common/tablemodel.c @@ -42,3 +42,7 @@ void uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const ui mh = uiprivTableModelHandler(m); (*(mh->SetCellValue))(mh, m, row, column, value); } + +const uiTableTextColumnOptionalParams uiprivDefaultTextColumnOptionalParams = { + .ColorModelColumn = -1, +}; diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index d37daf8a..5547336d 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -51,10 +51,6 @@ static BOOL isCellEditable(uiTableModel *m, NSInteger row, int modelColumn) return editable != 0; } -static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { - .ColorModelColumn = -1, -}; - struct textColumnCreateParams { uiTable *t; uiTableModel *m; @@ -623,9 +619,10 @@ void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, p.makeTextField = YES; p.textModelColumn = textModelColumn; p.textEditableColumn = textEditableModelColumn; - if (params == NULL) - params = &defaultTextColumnOptionalParams; - p.textParams = *params; + if (params != NULL) + p.textParams = *params; + else + p.textParams = uiprivDefaultTextColumnOptionalParams; str = [NSString stringWithUTF8String:name]; col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; @@ -665,9 +662,10 @@ void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelCo p.makeTextField = YES; p.textModelColumn = textModelColumn; p.textEditableColumn = textEditableModelColumn; - if (textParams == NULL) - textParams = &defaultTextColumnOptionalParams; - p.textParams = *textParams; + if (textParams != NULL) + p.textParams = *textParams; + else + p.textParams = uiprivDefaultTextColumnOptionalParams; p.makeImage = YES; p.imageModelColumn = imageModelColumn; @@ -711,9 +709,10 @@ void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxM p.makeTextField = YES; p.textModelColumn = textModelColumn; p.textEditableColumn = textEditableModelColumn; - if (textParams == NULL) - textParams = &defaultTextColumnOptionalParams; - p.textParams = *textParams; + if (textParams != NULL) + p.textParams = *textParams; + else + p.textParams = uiprivDefaultTextColumnOptionalParams; p.makeCheckbox = YES; p.checkboxModelColumn = checkboxModelColumn; diff --git a/unix/table.c b/unix/table.c index cd9734d3..8fae7960 100644 --- a/unix/table.c +++ b/unix/table.c @@ -87,11 +87,6 @@ static void onEdited(uiTableModel *m, int column, const char *pathstr, const uiT uiprivTableModelSetCellValue(m, row, column, tvalue); } -// TODO deduplicate this between platforms -static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { - .ColorModelColumn = -1, -}; - struct textColumnParams { uiTable *t; uiTableModel *m; @@ -282,7 +277,7 @@ static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, if (params != NULL) p->params = *params; else - p->params = defaultTextColumnOptionalParams; + p->params = uiprivDefaultTextColumnOptionalParams; r = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_start(c, r, TRUE); diff --git a/windows/table.cpp b/windows/table.cpp index 372a699b..801f65d0 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -7,10 +7,6 @@ // - implement keyboard accessibility // - implement accessibility in general (Dynamic Annotations maybe?) -static uiTableTextColumnOptionalParams defaultTextColumnOptionalParams = { - /*TODO.ColorModelColumn = */-1, -}; - uiTableModel *uiNewTableModel(uiTableModelHandler *mh) { uiTableModel *m; @@ -401,7 +397,7 @@ static uiprivTableColumnParams *appendColumn(uiTable *t, const char *name, int c p = uiprivNew(uiprivTableColumnParams); p->textModelColumn = -1; p->textEditableColumn = -1; - p->textParams = defaultTextColumnOptionalParams; + p->textParams = uiprivDefaultTextColumnOptionalParams; p->imageModelColumn = -1; p->checkboxModelColumn = -1; p->checkboxEditableColumn = -1; From 72c7c05f0458039d5e44a5ed8760c25ce66fd767 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 24 Jun 2018 09:52:01 -0400 Subject: [PATCH 139/166] Fixed up AddColumn parameter and private fields names; made them consistent. --- darwin/tablecolumn.m | 36 ++++++++++++++++++------------------ uitable.h | 4 ++-- unix/table.c | 14 +++++++------- windows/table.cpp | 24 ++++++++++++------------ windows/table.hpp | 4 ++-- windows/tabledraw.cpp | 6 +++--- windows/tableediting.cpp | 4 ++-- 7 files changed, 46 insertions(+), 46 deletions(-) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 5547336d..7dac3304 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -57,7 +57,7 @@ struct textColumnCreateParams { BOOL makeTextField; int textModelColumn; - int textEditableColumn; + int textEditableModelColumn; uiTableTextColumnOptionalParams textParams; BOOL makeImage; @@ -65,7 +65,7 @@ struct textColumnCreateParams { BOOL makeCheckbox; int checkboxModelColumn; - int checkboxEditableColumn; + int checkboxEditableModelColumn; }; @interface uiprivTextImageCheckboxTableCellView : uiprivTableCellView { @@ -74,7 +74,7 @@ struct textColumnCreateParams { NSTextField *tf; int textModelColumn; - int textEditableColumn; + int textEditableModelColumn; uiTableTextColumnOptionalParams textParams; NSImageView *iv; @@ -82,7 +82,7 @@ struct textColumnCreateParams { NSButton *cb; int checkboxModelColumn; - int checkboxEditableColumn; + int checkboxEditableModelColumn; } - (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p; - (IBAction)uiprivOnTextFieldAction:(id)sender; @@ -104,7 +104,7 @@ struct textColumnCreateParams { self->tf = nil; if (p->makeTextField) { self->textModelColumn = p->textModelColumn; - self->textEditableColumn = p->textEditableColumn; + self->textEditableModelColumn = p->textEditableModelColumn; self->textParams = p->textParams; self->tf = uiprivNewLabel(@""); @@ -189,7 +189,7 @@ struct textColumnCreateParams { self->cb = nil; if (p->makeCheckbox) { self->checkboxModelColumn = p->checkboxModelColumn; - self->checkboxEditableColumn = p->checkboxEditableColumn; + self->checkboxEditableModelColumn = p->checkboxEditableModelColumn; self->cb = [[NSButton alloc] initWithFrame:NSZeroRect]; [self->cb setTitle:@""]; @@ -272,7 +272,7 @@ struct textColumnCreateParams { uiFreeTableValue(value); [self->tf setStringValue:str]; - [self->tf setEditable:isCellEditable(self->m, row, self->textEditableColumn)]; + [self->tf setEditable:isCellEditable(self->m, row, self->textEditableModelColumn)]; color = nil; if (self->textParams.ColorModelColumn != -1) { @@ -307,7 +307,7 @@ struct textColumnCreateParams { [self->cb setState:NSOffState]; uiFreeTableValue(value); - [self->cb setEnabled:isCellEditable(self->m, row, self->checkboxEditableColumn)]; + [self->cb setEnabled:isCellEditable(self->m, row, self->checkboxEditableModelColumn)]; } } @@ -606,7 +606,7 @@ struct textColumnCreateParams { @end -void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *params) +void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) { struct textColumnCreateParams p; uiprivTableColumn *col; @@ -618,9 +618,9 @@ void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, p.makeTextField = YES; p.textModelColumn = textModelColumn; - p.textEditableColumn = textEditableModelColumn; - if (params != NULL) - p.textParams = *params; + p.textEditableModelColumn = textEditableModelColumn; + if (textParams != NULL) + p.textParams = *textParams; else p.textParams = uiprivDefaultTextColumnOptionalParams; @@ -661,7 +661,7 @@ void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelCo p.makeTextField = YES; p.textModelColumn = textModelColumn; - p.textEditableColumn = textEditableModelColumn; + p.textEditableModelColumn = textEditableModelColumn; if (textParams != NULL) p.textParams = *textParams; else @@ -688,7 +688,7 @@ void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModel p.makeCheckbox = YES; p.checkboxModelColumn = checkboxModelColumn; - p.checkboxEditableColumn = checkboxEditableModelColumn; + p.checkboxEditableModelColumn = checkboxEditableModelColumn; str = [NSString stringWithUTF8String:name]; col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; @@ -708,7 +708,7 @@ void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxM p.makeTextField = YES; p.textModelColumn = textModelColumn; - p.textEditableColumn = textEditableModelColumn; + p.textEditableModelColumn = textEditableModelColumn; if (textParams != NULL) p.textParams = *textParams; else @@ -716,7 +716,7 @@ void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxM p.makeCheckbox = YES; p.checkboxModelColumn = checkboxModelColumn; - p.checkboxEditableColumn = checkboxEditableModelColumn; + p.checkboxEditableModelColumn = checkboxEditableModelColumn; str = [NSString stringWithUTF8String:name]; col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; @@ -735,13 +735,13 @@ void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressMo [t->tv addTableColumn:col]; } -void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModelColumn, int buttonClickableModelColumn) +void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn) { uiprivTableColumn *col; NSString *str; str = [NSString stringWithUTF8String:name]; - col = [[uiprivButtonTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:buttonTextModelColumn editableColumn:buttonClickableModelColumn]; + col = [[uiprivButtonTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:buttonModelColumn editableColumn:buttonClickableModelColumn]; [col setTitle:str]; [t->tv addTableColumn:col]; } diff --git a/uitable.h b/uitable.h index 7a58b792..80ed1887 100644 --- a/uitable.h +++ b/uitable.h @@ -69,7 +69,7 @@ _UI_EXTERN void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, - uiTableTextColumnOptionalParams *params); + uiTableTextColumnOptionalParams *textParams); _UI_EXTERN void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn); @@ -95,7 +95,7 @@ _UI_EXTERN void uiTableAppendProgressBarColumn(uiTable *t, int progressModelColumn); _UI_EXTERN void uiTableAppendButtonColumn(uiTable *t, const char *name, - int buttonTextModelColumn, + int buttonModelColumn, int buttonClickableModelColumn); // TODO getter? _UI_EXTERN void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn); diff --git a/unix/table.c b/unix/table.c index 8fae7960..97750cae 100644 --- a/unix/table.c +++ b/unix/table.c @@ -263,7 +263,7 @@ static GtkTreeViewColumn *addColumn(uiTable *t, const char *name) return c; } -static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *params) +static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) { struct textColumnParams *p; GtkCellRenderer *r; @@ -274,8 +274,8 @@ static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, p->m = t->model; p->modelColumn = textModelColumn; p->editableColumn = textEditableModelColumn; - if (params != NULL) - p->params = *params; + if (textParams != NULL) + p->params = *textParams; else p->params = uiprivDefaultTextColumnOptionalParams; @@ -287,12 +287,12 @@ static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, } // TODO rename modelCOlumn and params everywhere -void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *params) +void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) { GtkTreeViewColumn *c; c = addColumn(t, name); - addTextColumn(t, c, textModelColumn, textEditableModelColumn, params); + addTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams); } static void addImageColumn(uiTable *t, GtkTreeViewColumn *c, int imageModelColumn) @@ -381,7 +381,7 @@ void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressMo g_ptr_array_add(t->columnParams, p); } -void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModelColumn, int buttonClickableModelColumn) +void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn) { GtkTreeViewColumn *c; struct buttonColumnParams *p; @@ -392,7 +392,7 @@ void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModel p = uiprivNew(struct buttonColumnParams); p->t = t; p->m = t->model; - p->modelColumn = buttonTextModelColumn; + p->modelColumn = buttonModelColumn; p->clickableColumn = buttonClickableModelColumn; r = uiprivNewCellRendererButton(); diff --git a/windows/table.cpp b/windows/table.cpp index 801f65d0..2c821b6e 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -396,26 +396,26 @@ static uiprivTableColumnParams *appendColumn(uiTable *t, const char *name, int c p = uiprivNew(uiprivTableColumnParams); p->textModelColumn = -1; - p->textEditableColumn = -1; + p->textEditableModelColumn = -1; p->textParams = uiprivDefaultTextColumnOptionalParams; p->imageModelColumn = -1; p->checkboxModelColumn = -1; - p->checkboxEditableColumn = -1; + p->checkboxEditableModelColumn = -1; p->progressBarModelColumn = -1; p->buttonModelColumn = -1; t->columns->push_back(p); return p; } -void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *params) +void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) { uiprivTableColumnParams *p; p = appendColumn(t, name, LVCFMT_LEFT); p->textModelColumn = textModelColumn; - p->textEditableColumn = textEditableModelColumn; - if (params != NULL) - p->textParams = *params; + p->textEditableModelColumn = textEditableModelColumn; + if (textParams != NULL) + p->textParams = *textParams; } void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) @@ -432,7 +432,7 @@ void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelCo p = appendColumn(t, name, LVCFMT_LEFT); p->textModelColumn = textModelColumn; - p->textEditableColumn = textEditableModelColumn; + p->textEditableModelColumn = textEditableModelColumn; if (textParams != NULL) p->textParams = *textParams; p->imageModelColumn = imageModelColumn; @@ -444,7 +444,7 @@ void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModel p = appendColumn(t, name, LVCFMT_LEFT); p->checkboxModelColumn = checkboxModelColumn; - p->checkboxEditableColumn = checkboxEditableModelColumn; + p->checkboxEditableModelColumn = checkboxEditableModelColumn; } void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) @@ -453,11 +453,11 @@ void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxM p = appendColumn(t, name, LVCFMT_LEFT); p->textModelColumn = textModelColumn; - p->textEditableColumn = textEditableModelColumn; + p->textEditableModelColumn = textEditableModelColumn; if (textParams != NULL) p->textParams = *textParams; p->checkboxModelColumn = checkboxModelColumn; - p->checkboxEditableColumn = checkboxEditableModelColumn; + p->checkboxEditableModelColumn = checkboxEditableModelColumn; } void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) @@ -468,13 +468,13 @@ void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressMo p->progressBarModelColumn = progressModelColumn; } -void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonTextModelColumn, int buttonClickableModelColumn) +void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn) { uiprivTableColumnParams *p; // TODO see if we can get rid of this parameter p = appendColumn(t, name, LVCFMT_LEFT); - p->buttonModelColumn = buttonTextModelColumn; + p->buttonModelColumn = buttonModelColumn; p->buttonClickableModelColumn = buttonClickableModelColumn; } diff --git a/windows/table.hpp b/windows/table.hpp index 65a70fae..71946d62 100644 --- a/windows/table.hpp +++ b/windows/table.hpp @@ -10,13 +10,13 @@ struct uiTableModel { typedef struct uiprivTableColumnParams uiprivTableColumnParams; struct uiprivTableColumnParams { int textModelColumn; - int textEditableColumn; + int textEditableModelColumn; uiTableTextColumnOptionalParams textParams; int imageModelColumn; int checkboxModelColumn; - int checkboxEditableColumn; + int checkboxEditableModelColumn; int progressBarModelColumn; diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index ffa901fc..d8ab07e9 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -197,7 +197,7 @@ static HRESULT drawCheckboxPart(HRESULT hr, struct drawState *s) value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxModelColumn); checked = uiTableValueInt(value); uiFreeTableValue(value); - switch (s->p->checkboxEditableColumn) { + switch (s->p->checkboxEditableModelColumn) { case uiTableModelColumnNeverEditable: enabled = 0; break; @@ -205,7 +205,7 @@ static HRESULT drawCheckboxPart(HRESULT hr, struct drawState *s) enabled = 1; break; default: - value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxEditableColumn); + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxEditableModelColumn); enabled = uiTableValueInt(value); uiFreeTableValue(value); } @@ -422,7 +422,7 @@ static HRESULT drawButtonPart(HRESULT hr, struct drawState *s) enabled = 1; break; default: - value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxEditableColumn); + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxEditableModelColumn); enabled = uiTableValueInt(value); uiFreeTableValue(value); } diff --git a/windows/tableediting.cpp b/windows/tableediting.cpp index f96e70e6..00b07992 100644 --- a/windows/tableediting.cpp +++ b/windows/tableediting.cpp @@ -215,11 +215,11 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu p = (*(t->columns))[ht.iSubItem]; if (p->textModelColumn != -1) { modelColumn = p->textModelColumn; - editableColumn = p->textEditableColumn; + editableColumn = p->textEditableModelColumn; text = true; } else if (p->checkboxModelColumn != -1) { modelColumn = p->checkboxModelColumn; - editableColumn = p->checkboxEditableColumn; + editableColumn = p->checkboxEditableModelColumn; checkbox = true; } else if (p->buttonModelColumn != -1) { modelColumn = p->buttonModelColumn; From fb67c429d818af4f0e74d9b8d0a452112fcbc3e2 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 24 Jun 2018 10:28:41 -0400 Subject: [PATCH 140/166] Made background color columns only settable at creation time; added a uiTableParams struct for the purpose. This should end edits to uitable.h for now (until we're ready to document it). Now to just clean up all the implementations. --- darwin/table.m | 37 +++++++++++++++---------------------- test/page16.c | 16 +++++++++------- uitable.h | 10 +++++++--- unix/table.c | 12 +++--------- windows/table.cpp | 19 ++++++------------- 5 files changed, 40 insertions(+), 54 deletions(-) diff --git a/darwin/table.m b/darwin/table.m index f148472d..41726b6b 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -171,26 +171,21 @@ static void uiTableDestroy(uiControl *c) uiFreeControl(uiControl(t)); } -void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) -{ - t->backgroundColumn = modelColumn; - // TODO update all rows -} - -uiTable *uiNewTable(uiTableModel *model) +uiTable *uiNewTable(uiTableParams *p) { uiTable *t; - uiprivScrollViewCreateParams p; + uiprivScrollViewCreateParams sp; uiDarwinNewControl(uiTable, t); - t->m = model; + t->m = p->Model; + t->backgroundColumn = p->RowBackgroundColorModelColumn; t->tv = [[uiprivTableView alloc] initWithFrame:NSZeroRect uiprivT:t uiprivM:t->m]; - [t->tv setDataSource:model->m]; - [t->tv setDelegate:model->m]; + [t->tv setDataSource:t->m->m]; + [t->tv setDelegate:t->m->m]; [t->tv reloadData]; - [model->tables addObject:t->tv]; + [t->m->tables addObject:t->tv]; // TODO is this sufficient? [t->tv setAllowsColumnReordering:NO]; @@ -204,23 +199,21 @@ uiTable *uiNewTable(uiTableModel *model) [t->tv setAllowsTypeSelect:YES]; // TODO floatsGroupRows — do we even allow group rows? - memset(&p, 0, sizeof (uiprivScrollViewCreateParams)); - p.DocumentView = t->tv; + memset(&sp, 0, sizeof (uiprivScrollViewCreateParams)); + sp.DocumentView = t->tv; // this is what Interface Builder sets it to // TODO verify - p.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0]; - p.DrawsBackground = YES; - p.Bordered = YES; - p.HScroll = YES; - p.VScroll = YES; - t->sv = uiprivMkScrollView(&p, &(t->d)); + sp.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0]; + sp.DrawsBackground = YES; + sp.Bordered = YES; + sp.HScroll = YES; + sp.VScroll = YES; + t->sv = uiprivMkScrollView(&sp, &(t->d)); // TODO WHY DOES THIS REMOVE ALL GRAPHICAL GLITCHES? // I got the idea from http://jwilling.com/blog/optimized-nstableview-scrolling/ but that was on an unrelated problem I didn't seem to have (although I have small-ish tables to start with) // I don't get layer-backing... am I supposed to layer-back EVERYTHING manually? I need to check Interface Builder again... [t->sv setWantsLayer:YES]; - t->backgroundColumn = -1; - return t; } diff --git a/test/page16.c b/test/page16.c index 51aa3a59..c0f438f4 100644 --- a/test/page16.c +++ b/test/page16.c @@ -103,7 +103,8 @@ uiBox *makePage16(void) uiBox *page16; uiTableModel *m; uiTable *t; - uiTableTextColumnOptionalParams p; + uiTableParams p; + uiTableTextColumnOptionalParams tp; img[0] = uiNewImage(16, 16); appendImageNamed(img[0], "andlabs_16x16test_24june2016.png"); @@ -125,22 +126,23 @@ uiBox *makePage16(void) mh.SetCellValue = modelSetCellValue; m = uiNewTableModel(&mh); - t = uiNewTable(m); + memset(&p, 0, sizeof (uiTableParams)); + p.Model = m; + p.RowBackgroundColorModelColumn = 3; + t = uiNewTable(&p); uiBoxAppend(page16, uiControl(t), 1); uiTableAppendTextColumn(t, "Column 1", 0, uiTableModelColumnNeverEditable, NULL); - memset(&p, 0, sizeof (uiTableTextColumnOptionalParams)); - p.ColorModelColumn = 4; + memset(&tp, 0, sizeof (uiTableTextColumnOptionalParams)); + tp.ColorModelColumn = 4; uiTableAppendImageTextColumn(t, "Column 2", 5, - 1, uiTableModelColumnNeverEditable, &p); + 1, uiTableModelColumnNeverEditable, &tp); uiTableAppendTextColumn(t, "Editable", 2, uiTableModelColumnAlwaysEditable, NULL); - uiTableSetRowBackgroundColorModelColumn(t, 3); - uiTableAppendCheckboxColumn(t, "Checkboxes", 7, uiTableModelColumnAlwaysEditable); uiTableAppendButtonColumn(t, "Buttons", diff --git a/uitable.h b/uitable.h index 80ed1887..5302edfe 100644 --- a/uitable.h +++ b/uitable.h @@ -58,11 +58,17 @@ _UI_EXTERN void uiTableModelRowDeleted(uiTableModel *m, int oldIndex); #define uiTableModelColumnAlwaysEditable (-2) typedef struct uiTableTextColumnOptionalParams uiTableTextColumnOptionalParams; +typedef struct uiTableParams uiTableParams; struct uiTableTextColumnOptionalParams { int ColorModelColumn; }; +struct uiTableParams { + uiTableModel *Model; + int RowBackgroundColorModelColumn; +}; + typedef struct uiTable uiTable; #define uiTable(this) ((uiTable *) (this)) _UI_EXTERN void uiTableAppendTextColumn(uiTable *t, @@ -97,6 +103,4 @@ _UI_EXTERN void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn); -// TODO getter? -_UI_EXTERN void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn); -_UI_EXTERN uiTable *uiNewTable(uiTableModel *model); +_UI_EXTERN uiTable *uiNewTable(uiTableParams *params); diff --git a/unix/table.c b/unix/table.c index 97750cae..010b1b39 100644 --- a/unix/table.c +++ b/unix/table.c @@ -413,21 +413,15 @@ static void uiTableDestroy(uiControl *c) uiFreeControl(uiControl(t)); } -void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) -{ - t->backgroundColumn = modelColumn; - // TODO refresh table -} - -uiTable *uiNewTable(uiTableModel *model) +uiTable *uiNewTable(uiTableParams *p) { uiTable *t; uiUnixNewControl(uiTable, t); - t->model = model; + t->model = p->Model; t->columnParams = g_ptr_array_new(); - t->backgroundColumn = -1; + t->backgroundColumn = p->RowBackgroundColorModelColumn; t->widget = gtk_scrolled_window_new(NULL, NULL); t->scontainer = GTK_CONTAINER(t->widget); diff --git a/windows/table.cpp b/windows/table.cpp index 2c821b6e..b8690a73 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -478,14 +478,7 @@ void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColu p->buttonClickableModelColumn = buttonClickableModelColumn; } -void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) -{ - // TODO make the names consistent - t->backgroundColumn = modelColumn; - // TODO redraw? -} - -uiTable *uiNewTable(uiTableModel *model) +uiTable *uiNewTable(uiTableParams *p) { uiTable *t; int n; @@ -494,14 +487,16 @@ uiTable *uiNewTable(uiTableModel *model) uiWindowsNewControl(uiTable, t); t->columns = new std::vector; - t->model = model; + t->model = p->Model; + t->backgroundColumn = p->RowBackgroundColorModelColumn; + // WS_CLIPCHILDREN is here to prevent drawing over the edit box used for editing text t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, WC_LISTVIEW, L"", LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL | WS_CLIPCHILDREN | WS_TABSTOP | WS_HSCROLL | WS_VSCROLL, hInstance, NULL, TRUE); - model->tables->push_back(t); + t->model->tables->push_back(t); uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t)); // TODO: try LVS_EX_AUTOSIZECOLUMNS @@ -509,12 +504,10 @@ uiTable *uiNewTable(uiTableModel *model) SendMessageW(t->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, (WPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_SUBITEMIMAGES), (LPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_SUBITEMIMAGES)); - n = uiprivTableModelNumRows(model); + n = uiprivTableModelNumRows(t->model); if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0) logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()"); - t->backgroundColumn = -1; - hr = uiprivUpdateImageListSize(t); if (hr != S_OK) { // TODO From acb40964f3bd0c7e6e5e015248ef40b47516873f Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 24 Jun 2018 11:41:19 -0400 Subject: [PATCH 141/166] Deduplicated editable logic across platforms. --- common/table.h | 1 + common/tablemodel.c | 17 +++++++++++++++++ darwin/tablecolumn.m | 23 +++-------------------- unix/table.c | 19 ++++++------------- windows/tabledraw.cpp | 26 ++------------------------ windows/tableediting.cpp | 12 +----------- 6 files changed, 30 insertions(+), 68 deletions(-) diff --git a/common/table.h b/common/table.h index 21ce2d11..a7e2d8ff 100644 --- a/common/table.h +++ b/common/table.h @@ -12,6 +12,7 @@ extern int uiprivTableModelNumRows(uiTableModel *m); extern uiTableValue *uiprivTableModelCellValue(uiTableModel *m, int row, int column); extern void uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const uiTableValue *value); extern const uiTableTextColumnOptionalParams uiprivDefaultTextColumnOptionalParams; +extern int uiprivTableModelCellEditable(uiTableModel *m, int row, int column); #ifdef __cplusplus } diff --git a/common/tablemodel.c b/common/tablemodel.c index b1cd1e89..e1de4896 100644 --- a/common/tablemodel.c +++ b/common/tablemodel.c @@ -46,3 +46,20 @@ void uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const ui const uiTableTextColumnOptionalParams uiprivDefaultTextColumnOptionalParams = { .ColorModelColumn = -1, }; + +int uiprivTableModelCellEditable(uiTableModel *m, int row, int column) +{ + uiTableValue *value; + int editable; + + switch (column) { + case uiTableModelColumnNeverEditable: + return 0; + case uiTableModelColumnAlwaysEditable: + return 1; + } + value = uiprivTableModelCellValue(m, row, column); + editable = uiTableValueInt(value); + uiFreeTableValue(value); + return editable; +} diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 7dac3304..4b3eb11d 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -34,23 +34,6 @@ @end -static BOOL isCellEditable(uiTableModel *m, NSInteger row, int modelColumn) -{ - uiTableValue *value; - int editable; - - switch (modelColumn) { - case uiTableModelColumnNeverEditable: - return NO; - case uiTableModelColumnAlwaysEditable: - return YES; - } - value = uiprivTableModelCellValue(m, row, modelColumn); - editable = uiTableValueInt(value); - uiFreeTableValue(value); - return editable != 0; -} - struct textColumnCreateParams { uiTable *t; uiTableModel *m; @@ -272,7 +255,7 @@ struct textColumnCreateParams { uiFreeTableValue(value); [self->tf setStringValue:str]; - [self->tf setEditable:isCellEditable(self->m, row, self->textEditableModelColumn)]; + [self->tf setEditable:uiprivTableModelCellEditable(self->m, row, self->textEditableModelColumn)]; color = nil; if (self->textParams.ColorModelColumn != -1) { @@ -307,7 +290,7 @@ struct textColumnCreateParams { [self->cb setState:NSOffState]; uiFreeTableValue(value); - [self->cb setEnabled:isCellEditable(self->m, row, self->checkboxEditableModelColumn)]; + [self->cb setEnabled:uiprivTableModelCellEditable(self->m, row, self->checkboxEditableModelColumn)]; } } @@ -558,7 +541,7 @@ struct textColumnCreateParams { uiFreeTableValue(value); [self->b setTitle:str]; - [self->b setEnabled:isCellEditable(self->m, row, self->editableColumn)]; + [self->b setEnabled:uiprivTableModelCellEditable(self->m, row, self->editableColumn)]; } - (IBAction)uiprivOnClicked:(id)sender diff --git a/unix/table.c b/unix/table.c index 010b1b39..9e659163 100644 --- a/unix/table.c +++ b/unix/table.c @@ -49,21 +49,14 @@ static void applyColor(GtkTreeModel *m, GtkTreeIter *iter, int modelColumn, GtkC static void setEditable(uiTableModel *m, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop) { - GValue value = G_VALUE_INIT; + GtkTreePath *path; + int row; gboolean editable; - switch (modelColumn) { - case uiTableModelColumnNeverEditable: - editable = FALSE; - break; - case uiTableModelColumnAlwaysEditable: - editable = TRUE; - break; - default: - gtk_tree_model_get_value(GTK_TREE_MODEL(m), iter, modelColumn, &value); - editable = g_value_get_int(&value) != 0; - g_value_unset(&value); - } + // TODO avoid the need for this + path = gtk_tree_model_get_path(GTK_TREE_MODEL(m), iter); + row = gtk_tree_path_get_indices(path)[0]; + editable = uiprivTableModelCellEditable(m, row, modelColumn) != 0; g_object_set(r, prop, editable, NULL); } diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index d8ab07e9..33eeb700 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -197,18 +197,7 @@ static HRESULT drawCheckboxPart(HRESULT hr, struct drawState *s) value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxModelColumn); checked = uiTableValueInt(value); uiFreeTableValue(value); - switch (s->p->checkboxEditableModelColumn) { - case uiTableModelColumnNeverEditable: - enabled = 0; - break; - case uiTableModelColumnAlwaysEditable: - enabled = 1; - break; - default: - value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxEditableModelColumn); - enabled = uiTableValueInt(value); - uiFreeTableValue(value); - } + enabled = uiprivTableModelCellEditable(s->model, s->iItem, s->p->checkboxEditableModelColumn); theme = OpenThemeData(s->t->hwnd, L"button"); if (theme != NULL) { @@ -414,18 +403,7 @@ static HRESULT drawButtonPart(HRESULT hr, struct drawState *s) value = uiprivTableModelCellValue(s->model, s->iItem, s->p->buttonModelColumn); wstr = toUTF16(uiTableValueString(value)); uiFreeTableValue(value); - switch (s->p->buttonClickableModelColumn) { - case uiTableModelColumnNeverEditable: - enabled = 0; - break; - case uiTableModelColumnAlwaysEditable: - enabled = 1; - break; - default: - value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxEditableModelColumn); - enabled = uiTableValueInt(value); - uiFreeTableValue(value); - } + enabled = uiprivTableModelCellEditable(s->model, s->iItem, s->p->buttonClickableModelColumn); theme = OpenThemeData(s->t->hwnd, L"button"); diff --git a/windows/tableediting.cpp b/windows/tableediting.cpp index 00b07992..7bbb450c 100644 --- a/windows/tableediting.cpp +++ b/windows/tableediting.cpp @@ -232,18 +232,8 @@ HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResu // don't even ask for info if it's too soon to edit text goto done; - switch (editableColumn) { - case uiTableModelColumnNeverEditable: + if (!uiprivTableModelCellEditable(t->model, ht.iItem, editableColumn)) goto done; - case uiTableModelColumnAlwaysEditable: - break; - default: - value = uiprivTableModelCellValue(t->model, ht.iItem, editableColumn); - editable = uiTableValueInt(value); - uiFreeTableValue(value); - if (!editable) - goto done; - } if (text) { hr = openEditControl(t, ht.iItem, ht.iSubItem, p); From f3882d6124adada301f29733cb5bc20a5c8ccc27 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 24 Jun 2018 14:22:05 -0400 Subject: [PATCH 142/166] Cleaned up color access across platforms. --- common/table.h | 1 + common/tablemodel.c | 14 ++++++++++++++ darwin/table.m | 10 +++------- darwin/tablecolumn.m | 18 ++++-------------- windows/tabledraw.cpp | 43 ++++++++++++++++--------------------------- 5 files changed, 38 insertions(+), 48 deletions(-) diff --git a/common/table.h b/common/table.h index a7e2d8ff..f83205df 100644 --- a/common/table.h +++ b/common/table.h @@ -13,6 +13,7 @@ extern uiTableValue *uiprivTableModelCellValue(uiTableModel *m, int row, int col extern void uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const uiTableValue *value); extern const uiTableTextColumnOptionalParams uiprivDefaultTextColumnOptionalParams; extern int uiprivTableModelCellEditable(uiTableModel *m, int row, int column); +extern int uiprivTableModelColorIfProvided(uiTableModel *m, int row, int column, double *r, double *g, double *b, double *a); #ifdef __cplusplus } diff --git a/common/tablemodel.c b/common/tablemodel.c index e1de4896..dbda4a81 100644 --- a/common/tablemodel.c +++ b/common/tablemodel.c @@ -63,3 +63,17 @@ int uiprivTableModelCellEditable(uiTableModel *m, int row, int column) uiFreeTableValue(value); return editable; } + +int uiprivTableModelColorIfProvided(uiTableModel *m, int row, int column, double *r, double *g, double *b, double *a) +{ + uiTableValue *value; + + if (column == -1) + return 0; + value = uiprivTableModelCellValue(m, row, column); + if (value == NULL) + return 0; + uiTableValueColor(value, r, g, b, a); + uiFreeTableValue(value); + return 1; +} diff --git a/darwin/table.m b/darwin/table.m index 41726b6b..b23ed47d 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -31,18 +31,14 @@ // TODO is this correct for overflow scrolling? static void setBackgroundColor(uiprivTableView *t, NSTableRowView *rv, NSInteger row) { - uiTableValue *value; NSColor *color; double r, g, b, a; if (t->uiprivT->backgroundColumn == -1) - return; - value = uiprivTableModelCellValue(t->uiprivM, row, t->uiprivT->backgroundColumn); - if (value != NULL) { - uiTableValueColor(value, &r, &g, &b, &a); - uiFreeTableValue(value); + return; // let Cocoa do its default thing + if (uiprivTableModelColorIfProvided(t->uiprivM, row, t->uiprivT->backgroundColumn, &r, &g, &b, &a)) color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a]; - } else { + else { NSArray *colors; NSInteger index; diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index 4b3eb11d..b918c50c 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -249,6 +249,7 @@ struct textColumnCreateParams { if (self->tf != nil) { NSString *str; NSColor *color; + double r, g, b, a; value = uiprivTableModelCellValue(self->m, row, self->textModelColumn); str = uiprivToNSString(uiTableValueString(value)); @@ -257,20 +258,9 @@ struct textColumnCreateParams { [self->tf setEditable:uiprivTableModelCellEditable(self->m, row, self->textEditableModelColumn)]; - color = nil; - if (self->textParams.ColorModelColumn != -1) { - double r, g, b, a; - - value = uiprivTableModelCellValue(self->m, row, self->textParams.ColorModelColumn); - // TODO document this is allowed - if (value != NULL) { - uiTableValueColor(value, &r, &g, &b, &a); - uiFreeTableValue(value); - color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a]; - } - } - if (color == nil) - color = [NSColor controlTextColor]; + color = [NSColor controlTextColor]; + if (uiprivTableModelColorIfProvided(self->m, row, self->textParams.ColorModelColumn, &r, &g, &b, &a)) + color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a]; [self->tf setTextColor:color]; // we don't own color in ether case; don't release } diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 33eeb700..eb50f28a 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -549,42 +549,31 @@ static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm s->textColor = GetSysColor(COLOR_HIGHLIGHTTEXT); s->textBrush = GetSysColorBrush(COLOR_HIGHLIGHTTEXT); } else { - uiTableValue *value; double r, g, b, a; s->bgColor = GetSysColor(COLOR_WINDOW); s->bgBrush = GetSysColorBrush(COLOR_WINDOW); - if (t->backgroundColumn != -1) { - value = uiprivTableModelCellValue(s->model, s->iItem, t->backgroundColumn); - if (value != NULL) { - uiTableValueColor(value, &r, &g, &b, &a); - uiFreeTableValue(value); - s->bgColor = blend(s->bgColor, r, g, b, a); - s->bgBrush = CreateSolidBrush(s->bgColor); - if (s->bgBrush == NULL) { - logLastError(L"CreateSolidBrush()"); - hr = E_FAIL; - goto fail; - } - s->freeBgBrush = TRUE; + if (uiprivTableModelColorIfProvided(s->model, s->iItem, t->backgroundColumn, &r, &g, &b, &a)) { + s->bgColor = blend(s->bgColor, r, g, b, a); + s->bgBrush = CreateSolidBrush(s->bgColor); + if (s->bgBrush == NULL) { + logLastError(L"CreateSolidBrush()"); + hr = E_FAIL; + goto fail; } + s->freeBgBrush = TRUE; } s->textColor = GetSysColor(COLOR_WINDOWTEXT); s->textBrush = GetSysColorBrush(COLOR_WINDOWTEXT); - if (p->textParams.ColorModelColumn != -1) { - value = uiprivTableModelCellValue(s->model, s->iItem, p->textParams.ColorModelColumn); - if (value != NULL) { - uiTableValueColor(value, &r, &g, &b, &a); - uiFreeTableValue(value); - s->textColor = blend(s->bgColor, r, g, b, a); - s->textBrush = CreateSolidBrush(s->textColor); - if (s->textBrush == NULL) { - logLastError(L"CreateSolidBrush()"); - hr = E_FAIL; - goto fail; - } - s->freeTextBrush = TRUE; + if (uiprivTableModelColorIfProvided(s->model, s->iItem, p->textParams.ColorModelColumn, &r, &g, &b, &a)) { + s->textColor = blend(s->bgColor, r, g, b, a); + s->textBrush = CreateSolidBrush(s->textColor); + if (s->textBrush == NULL) { + logLastError(L"CreateSolidBrush()"); + hr = E_FAIL; + goto fail; } + s->freeTextBrush = TRUE; } } From 4ed6e3ec8a37dd4a41c6b066449e3fd969963b1e Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 24 Jun 2018 14:46:24 -0400 Subject: [PATCH 143/166] Minor TODO resolution. --- darwin/tablecolumn.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m index b918c50c..5038cc6b 100644 --- a/darwin/tablecolumn.m +++ b/darwin/tablecolumn.m @@ -43,7 +43,7 @@ struct textColumnCreateParams { int textEditableModelColumn; uiTableTextColumnOptionalParams textParams; - BOOL makeImage; + BOOL makeImageView; int imageModelColumn; BOOL makeCheckbox; @@ -121,8 +121,7 @@ struct textColumnCreateParams { } self->iv = nil; - // TODO rename to makeImageView - if (p->makeImage) { + if (p->makeImageView) { self->imageModelColumn = p->imageModelColumn; self->iv = [[NSImageView alloc] initWithFrame:NSZeroRect]; @@ -541,6 +540,7 @@ struct textColumnCreateParams { row = [self->t->tv rowForView:self->b]; uiprivTableModelSetCellValue(self->m, row, self->modelColumn, NULL); // TODO document we DON'T update the cell after doing this + // TODO or decide what to do instead } @end @@ -613,7 +613,7 @@ void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn p.t = t; p.m = t->m; - p.makeImage = YES; + p.makeImageView = YES; p.imageModelColumn = imageModelColumn; str = [NSString stringWithUTF8String:name]; @@ -640,7 +640,7 @@ void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelCo else p.textParams = uiprivDefaultTextColumnOptionalParams; - p.makeImage = YES; + p.makeImageView = YES; p.imageModelColumn = imageModelColumn; str = [NSString stringWithUTF8String:name]; From bd685f24f9524fb43e52008db38f31eb8ca1023c Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 24 Jun 2018 14:48:09 -0400 Subject: [PATCH 144/166] Removed a stale TODO; added more TODOs. --- darwin/table.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/darwin/table.m b/darwin/table.m index b23ed47d..07df7c0f 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -2,6 +2,8 @@ #import "uipriv_darwin.h" #import "table.h" +// TODO the initial scroll position is still wrong + @interface uiprivTableModel : NSObject { uiTableModel *m; } @@ -72,7 +74,6 @@ static void setBackgroundColor(uiprivTableView *t, NSTableRowView *rv, NSInteger - (NSView *)tableView:(NSTableView *)tv viewForTableColumn:(NSTableColumn *)cc row:(NSInteger)row { uiprivTableColumn *c = (uiprivTableColumn *) cc; - // TODO consider renaming this type to uiprivTableCellView uiprivTableCellView *cv; cv = (uiprivTableCellView *) [tv makeViewWithIdentifier:[c identifier] owner:self]; From 5d9928028f246813edf6a7cb72de0622bc146b8f Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 24 Jun 2018 18:23:25 -0400 Subject: [PATCH 145/166] Wrote the initial version of the indeterminate progressbar in tables code on GTK+. VirtualBox is giving me issues when any indeterminate progressbar (real or table-based) is up; I wonder what's going on. --- unix/table.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/unix/table.c b/unix/table.c index 9e659163..844f5925 100644 --- a/unix/table.c +++ b/unix/table.c @@ -12,6 +12,10 @@ struct uiTable { uiTableModel *model; GPtrArray *columnParams; int backgroundColumn; + // keys are struct rowcol, values are gint + // TODO document this properly + GHashTable *indeterminatePositions; + guint indeterminateTimer; }; // use the same size as GtkFileChooserWidget's treeview @@ -194,21 +198,95 @@ struct progressBarColumnParams { int modelColumn; }; +struct rowcol { + int row; + int col; +}; + +static guint rowcolHash(gconstpointer key) +{ + const struct rowcol *rc = (const struct rowcol *) key; + guint row, col; + + row = (guint) (rc->row); + col = (guint) (rc->col); + return row ^ col; +} + +static gboolean rowcolEqual(gconstpointer a, gconstpointer b) +{ + const struct rowcol *ra = (const struct rowcol *) a; + const struct rowcol *rb = (const struct rowcol *) b; + + return (ra->row == rb->row) && (ra->col == rb->col); +} + +static void pulseOne(gpointer key, gpointer value, gpointer data) +{ + uiTable *t = uiTable(data); + struct rowcol *rc = (struct rowcol *) key; + + // TODO this is bad: it produces changed handlers for every table because that's how GtkTreeModel works, yet this is per-table because that's how it works + // however, a proper fix would require decoupling progress from normal integers, which we could do... + uiTableModelRowChanged(t->model, rc->row); +} + +static gboolean indeterminatePulse(gpointer data) +{ + uiTable *t = uiTable(data); + + g_hash_table_foreach(t->indeterminatePositions, pulseOne, t); + return TRUE; +} + static void progressBarColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) { struct progressBarColumnParams *p = (struct progressBarColumnParams *) data; GValue value = G_VALUE_INIT; int pval; + struct rowcol *rc; + gint *val; + GtkTreePath *path; gtk_tree_model_get_value(m, iter, p->modelColumn, &value); pval = g_value_get_int(&value); + rc = uiprivNew(struct rowcol); + // TODO avoid the need for this + path = gtk_tree_model_get_path(GTK_TREE_MODEL(m), iter); + rc->row = gtk_tree_path_get_indices(path)[0]; + rc->col = p->modelColumn; + val = (gint *) g_hash_table_lookup(p->t->indeterminatePositions, rc); if (pval == -1) { - // TODO - } else + if (val == NULL) { + val = uiprivNew(gint); + *val = 1; + g_hash_table_insert(p->t->indeterminatePositions, rc, val); + } else { + uiprivFree(rc); + (*val)++; + if (*val == G_MAXINT) + *val = 1; + } + g_object_set(r, + "pulse", *val, + NULL); + if (p->t->indeterminateTimer == 0) + // TODO verify the timeout + p->t->indeterminateTimer = g_timeout_add(100, indeterminatePulse, p->t); + } else { + if (val != NULL) { + g_hash_table_remove(p->t->indeterminatePositions, rc); + uiprivFree(rc); + if (g_hash_table_size(p->t->indeterminatePositions) == 0) { + g_source_remove(p->t->indeterminateTimer); + p->t->indeterminateTimer = 0; + } + } g_object_set(r, "pulse", -1, "value", pval, NULL); + } g_value_unset(&value); applyBackgroundColor(p->t, m, iter, r); @@ -429,5 +507,8 @@ uiTable *uiNewTable(uiTableParams *p) // and make the tree view visible; only the scrolled window's visibility is controlled by libui gtk_widget_show(t->treeWidget); + t->indeterminatePositions = g_hash_table_new_full(rowcolHash, rowcolEqual, + uiprivFree, uiprivFree); + return t; } From bd197684aa5c7905664ae9e7851c9f9e2a68d75f Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 25 Jun 2018 09:11:07 -0400 Subject: [PATCH 146/166] Added internationalization notes in the meantime. --- _notes/i18n | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 _notes/i18n diff --git a/_notes/i18n b/_notes/i18n new file mode 100644 index 00000000..79fdfe9f --- /dev/null +++ b/_notes/i18n @@ -0,0 +1,15 @@ +https://msdn.microsoft.com/en-us/library/windows/desktop/dd319079(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/dd318103(v=vs.85).aspx +https://stackoverflow.com/questions/4663855/is-there-a-repository-for-localized-common-text-in-winforms +https://stackoverflow.com/questions/2502375/find-localized-windows-strings +https://docs.microsoft.com/en-us/windows-hardware/customize/mobile/mcsf/create-a-resource-only-dll-for-localized-strings +https://msdn.microsoft.com/en-us/library/windows/desktop/ee845043(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/cc194807.aspx +https://www.codeproject.com/Articles/10542/Easily-Load-and-Format-Strings-from-the-String-Tab +https://www.codeproject.com/Tips/431045/The-inner-working-of-FindResource-and-LoadString-W +https://mihai-nita.net/2007/05/03/how-to-localize-an-rc-file/ +https://www.microsoft.com/en-us/language +https://www.microsoft.com/en-us/language/Terminology +https://www.microsoft.com/en-us/language/LicenseAgreement +https://www.microsoft.com/en-us/language/Translations +http://www.ttt.org/oscarstandards/tbx/ From 37bd4fc5a180f15c1d29bbb5ec8403b33626f451 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 25 Jun 2018 21:25:23 -0400 Subject: [PATCH 147/166] Added a test program to demonstrate the GTK+ progress bar cell renderer timer issues. --- doc/misctests/gtkprogresstable.c | 136 +++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 doc/misctests/gtkprogresstable.c diff --git a/doc/misctests/gtkprogresstable.c b/doc/misctests/gtkprogresstable.c new file mode 100644 index 00000000..220cb370 --- /dev/null +++ b/doc/misctests/gtkprogresstable.c @@ -0,0 +1,136 @@ +// 25 june 2018 +#include + +GtkWidget *mainwin; +GtkWidget *vbox; +GtkWidget *hbox; +GtkWidget *startProgress; +GtkWidget *startTable; +GtkWidget *progressbar; +GtkWidget *scrolledWindow; +GtkListStore *model; +GtkWidget *treeview; +GtkWidget *hbox2; + +static gboolean pulseProgress(gpointer data) +{ + gtk_progress_bar_pulse(GTK_PROGRESS_BAR(progressbar)); + return TRUE; +} + +static void onStartProgressClicked(GtkButton *button, gpointer data) +{ + gtk_widget_set_sensitive(startProgress, FALSE); + g_timeout_add(100, pulseProgress, NULL); +} + +gboolean pbarStarted = FALSE; +gint pbarValue; + +static void pbarDataFunc(GtkTreeViewColumn *col, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + if (!pbarStarted) { + g_object_set(r, + "pulse", -1, + "value", 0, + NULL); + return; + } + pbarValue++; + if (pbarValue == G_MAXINT) + pbarValue = 1; + g_object_set(r, "pulse", pbarValue, NULL); +} + +static gboolean pulseTable(gpointer data) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_indices(0, -1); + gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path); + gtk_tree_model_row_changed(GTK_TREE_MODEL(model), path, &iter); + gtk_tree_path_free(path); + return TRUE; +} + +static void onStartTableClicked(GtkButton *button, gpointer data) +{ + pbarStarted = TRUE; + pbarValue = 0; + + gtk_widget_set_sensitive(startTable, FALSE); + g_timeout_add(100, pulseTable, NULL); +} + +static gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data) +{ + gtk_main_quit(); + return FALSE; +} + +int main(void) +{ + GtkTreeIter iter; + GtkTreeViewColumn *col; + GtkCellRenderer *r; + + gtk_init(NULL, NULL); + + mainwin = gtk_window_new(GTK_WINDOW_TOPLEVEL); + g_signal_connect(mainwin, "delete-event", G_CALLBACK(onClosing), NULL); + + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 12); + gtk_container_add(GTK_CONTAINER(mainwin), vbox); + + hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_widget_set_halign(hbox, GTK_ALIGN_CENTER); + gtk_container_add(GTK_CONTAINER(vbox), hbox); + + startProgress = gtk_button_new_with_label("Start Progress Bar"); + g_signal_connect(startProgress, "clicked", G_CALLBACK(onStartProgressClicked), NULL); + gtk_container_add(GTK_CONTAINER(hbox), startProgress); + + startTable = gtk_button_new_with_label("Start Table Cell Renderer"); + g_signal_connect(startTable, "clicked", G_CALLBACK(onStartTableClicked), NULL); + gtk_container_add(GTK_CONTAINER(hbox), startTable); + + progressbar = gtk_progress_bar_new(); + gtk_container_add(GTK_CONTAINER(vbox), progressbar); + + scrolledWindow = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledWindow), GTK_SHADOW_IN); + gtk_widget_set_vexpand(scrolledWindow, TRUE); + gtk_container_add(GTK_CONTAINER(vbox), scrolledWindow); + + model = gtk_list_store_new(1, G_TYPE_INT); + gtk_list_store_append(model, &iter); + gtk_list_store_set(model, &iter, + 0, 0, + -1); + + treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_container_add(GTK_CONTAINER(scrolledWindow), treeview); + + col = gtk_tree_view_column_new(); + gtk_tree_view_column_set_resizable(col, TRUE); + gtk_tree_view_column_set_title(col, "Column"); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col); + + r = gtk_cell_renderer_progress_new(); + gtk_tree_view_column_pack_start(col, r, TRUE); + gtk_tree_view_column_set_cell_data_func(col, r, pbarDataFunc, NULL, NULL); + + hbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_widget_set_halign(hbox2, GTK_ALIGN_CENTER); + gtk_container_add(GTK_CONTAINER(vbox), hbox2); + + gtk_container_add(GTK_CONTAINER(hbox2), gtk_button_new_with_label("These buttons")); + gtk_container_add(GTK_CONTAINER(hbox2), gtk_button_new_with_label("do nothing")); + gtk_container_add(GTK_CONTAINER(hbox2), gtk_button_new_with_label("when clicked")); + + gtk_widget_show_all(mainwin); + gtk_main(); + return 0; +} From ce0168e1a5faa537425a219c9cbe345a0b4d94cc Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 1 Jul 2018 17:35:34 -0400 Subject: [PATCH 148/166] I give up --- unix/progressbar.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/unix/progressbar.c b/unix/progressbar.c index b3681a6f..46f0e103 100644 --- a/unix/progressbar.c +++ b/unix/progressbar.c @@ -1,6 +1,9 @@ // 11 june 2015 #include "uipriv_unix.h" +// LONGTERM: +// - in GTK+ 3.22 at least, running both a GtkProgressBar and a GtkCellRendererProgress in pulse mode with our code will cause the former to slow down and eventually stop, and I can't tell why at all + struct uiProgressBar { uiUnixControl c; GtkWidget *widget; From 5e2b8cb6ae84716e4a61eef32ba0c07d29ae870c Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 8 Jul 2018 21:04:23 -0400 Subject: [PATCH 149/166] Removed nowintable.diff; we don't need it anymore. --- nowintable.diff | 50 ------------------------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 nowintable.diff diff --git a/nowintable.diff b/nowintable.diff deleted file mode 100644 index cfbab07d..00000000 --- a/nowintable.diff +++ /dev/null @@ -1,50 +0,0 @@ -diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt -index 7f70403..e909569 100644 ---- a/common/CMakeLists.txt -+++ b/common/CMakeLists.txt -@@ -6,7 +6,7 @@ list(APPEND _LIBUI_SOURCES - common/debug.c - common/matrix.c - common/shouldquit.c -- common/table.c -+# common/table.c - common/userbugs.c - ) - set(_LIBUI_SOURCES ${_LIBUI_SOURCES} PARENT_SCOPE) -diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt -index b753a7d..a648c64 100644 ---- a/test/CMakeLists.txt -+++ b/test/CMakeLists.txt -@@ -6,7 +6,7 @@ endif() - - _add_exec(tester - drawtests.c -- images.c -+# images.c - main.c - menus.c - page1.c -@@ -27,7 +27,7 @@ _add_exec(tester - page13.c - page14.c - page15.c -- page16.c -+# page16.c - spaced.c - ${_TEST_RESOURCES_RC} - ) -diff --git a/test/main.c b/test/main.c -index f33f30a..18774dc 100644 ---- a/test/main.c -+++ b/test/main.c -@@ -159,8 +159,8 @@ int main(int argc, char *argv[]) - innerTab = newTab(); - uiTabAppend(outerTab, "Pages 16-?", uiControl(innerTab)); - -- page16 = makePage16(); -- uiTabAppend(innerTab, "Page 16", uiControl(page16)); -+// page16 = makePage16(); -+// uiTabAppend(innerTab, "Page 16", uiControl(page16)); - - if (startspaced) - setSpaced(1); From 1700c0cceabf67dd1b562c4c8652efdb77e09c3c Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 8 Jul 2018 21:13:19 -0400 Subject: [PATCH 150/166] Reworded a TODO. --- darwin/table.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darwin/table.m b/darwin/table.m index 07df7c0f..fc203514 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -2,7 +2,7 @@ #import "uipriv_darwin.h" #import "table.h" -// TODO the initial scroll position is still wrong +// TODO is the initial scroll position still wrong? @interface uiprivTableModel : NSObject { uiTableModel *m; From 095e63d522c5f83953a7420aea4e78b6c88dd268 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 8 Jul 2018 21:20:42 -0400 Subject: [PATCH 151/166] Fixed memory leaks in the tester and a symbol name flub in OS X's image.m. --- darwin/image.m | 2 +- test/main.c | 1 + test/page16.c | 10 +++++++++- test/test.h | 1 + 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/darwin/image.m b/darwin/image.m index ae5be6d9..8824f3c6 100644 --- a/darwin/image.m +++ b/darwin/image.m @@ -18,7 +18,7 @@ uiImage *uiNewImage(double width, double height) return i; } -void Image(uiImage *i) +void uiFreeImage(uiImage *i) { NSValue *v; diff --git a/test/main.c b/test/main.c index c760f493..2f66826f 100644 --- a/test/main.c +++ b/test/main.c @@ -174,6 +174,7 @@ int main(int argc, char *argv[]) ; } printf("after uiMain()\n"); + freePage16(); uiUninit(); printf("after uiUninit()\n"); return 0; diff --git a/test/page16.c b/test/page16.c index c0f438f4..f28ba3c7 100644 --- a/test/page16.c +++ b/test/page16.c @@ -98,10 +98,11 @@ static void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, checkStates[row] = uiTableValueInt(val); } +static uiTableModel *m; + uiBox *makePage16(void) { uiBox *page16; - uiTableModel *m; uiTable *t; uiTableParams p; uiTableTextColumnOptionalParams tp; @@ -153,3 +154,10 @@ uiBox *makePage16(void) return page16; } + +void freePage16(void) +{ + uiFreeTableModel(m); + uiFreeImage(img[1]); + uiFreeImage(img[0]); +} diff --git a/test/test.h b/test/test.h index 224ef667..42ec9314 100644 --- a/test/test.h +++ b/test/test.h @@ -92,6 +92,7 @@ extern uiBox *makePage15(uiWindow *); // page16.c extern uiBox *makePage16(void); +extern void freePage16(void); // images.c extern void appendImageNamed(uiImage *img, const char *name); From a37fdadbd76dbd90ce86598dad820d5364a0d8bb Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 8 Jul 2018 21:22:55 -0400 Subject: [PATCH 152/166] Implemented uiControlDestroy() for uiTable on OS X. --- darwin/table.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/darwin/table.m b/darwin/table.m index fc203514..1bdc7f8d 100644 --- a/darwin/table.m +++ b/darwin/table.m @@ -163,7 +163,9 @@ static void uiTableDestroy(uiControl *c) { uiTable *t = uiTable(c); - // TODO + [t->m->tables removeObject:t->tv]; + uiprivScrollViewFreeData(t->sv, t->d); + [t->tv release]; [t->sv release]; uiFreeControl(uiControl(t)); } From 6812cab625cf939634bb46d117a34f5ead654a1f Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 8 Jul 2018 21:50:16 -0400 Subject: [PATCH 153/166] Implemented uiControlDestroy() for uiTable on GTK+. --- unix/table.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/unix/table.c b/unix/table.c index 844f5925..9e6fe01e 100644 --- a/unix/table.c +++ b/unix/table.c @@ -276,12 +276,12 @@ static void progressBarColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, } else { if (val != NULL) { g_hash_table_remove(p->t->indeterminatePositions, rc); - uiprivFree(rc); if (g_hash_table_size(p->t->indeterminatePositions) == 0) { g_source_remove(p->t->indeterminateTimer); p->t->indeterminateTimer = 0; } } + uiprivFree(rc); g_object_set(r, "pulse", -1, "value", pval, @@ -478,8 +478,14 @@ uiUnixControlAllDefaultsExceptDestroy(uiTable) static void uiTableDestroy(uiControl *c) { uiTable *t = uiTable(c); + guint i; - // TODO + for (i = 0; i < t->columnParams->len; i++) + uiprivFree(g_ptr_array_index(t->columnParams, i)); + g_ptr_array_free(t->columnParams, TRUE); + if (g_hash_table_size(t->indeterminatePositions) != 0) + g_source_remove(t->indeterminateTimer); + g_hash_table_destroy(t->indeterminatePositions); g_object_unref(t->widget); uiFreeControl(uiControl(t)); } From 3c44d332da2322acfd13c1fb17b5590a073c1669 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 22 Jul 2018 21:41:03 -0400 Subject: [PATCH 154/166] More notes. --- _notes/highDPI | 1 + 1 file changed, 1 insertion(+) create mode 100644 _notes/highDPI diff --git a/_notes/highDPI b/_notes/highDPI new file mode 100644 index 00000000..99e9af82 --- /dev/null +++ b/_notes/highDPI @@ -0,0 +1 @@ +High DPI Displays | Qt 5.5 http://doc.qt.io/qt-5/highdpi.html bottom of page(?) From b60953ed1c5725b23f255d17899765c84ea0c607 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 28 Jul 2018 13:57:20 -0400 Subject: [PATCH 155/166] More TODOs. --- windows/table.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/table.cpp b/windows/table.cpp index b8690a73..85a51c1a 100644 --- a/windows/table.cpp +++ b/windows/table.cpp @@ -6,6 +6,7 @@ // - should clicking on some other column of the same row, even one that doesn't edit, cancel editing? // - implement keyboard accessibility // - implement accessibility in general (Dynamic Annotations maybe?) +// - if I didn't handle these already: "drawing focus rects here, subitem navigation and activation with the keyboard" uiTableModel *uiNewTableModel(uiTableModelHandler *mh) { From 4eaf01f8401ff76d84184becb32edc971d751446 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 29 Jul 2018 13:25:53 -0400 Subject: [PATCH 156/166] Fix double-free spotted by @mischnic in #402. --- darwin/image.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/darwin/image.m b/darwin/image.m index 8824f3c6..28881ca3 100644 --- a/darwin/image.m +++ b/darwin/image.m @@ -66,11 +66,12 @@ void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, in bytesPerRow:pixelStride bitsPerPixel:32]; repsRGB = [repCalibrated bitmapImageRepByRetaggingWithColorSpace:[NSColorSpace sRGBColorSpace]]; - [repCalibrated release]; [i->i addRepresentation:repsRGB]; [repsRGB setSize:i->size]; - [repsRGB release]; + // don't release repsRGB; it may be equivalent to repCalibrated + // do release repCalibrated though; NSImage has a ref to either it or to repsRGB + [repCalibrated release]; // we need to keep swizzled alive for NSBitmapImageRep [i->swizzled addObject:[NSValue valueWithPointer:swizzled]]; From e7305349f3aa41bcf5301788a8189a4de95dbbf1 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 5 Aug 2018 17:52:15 -0400 Subject: [PATCH 157/166] Documented uiImage functions. --- uitable.h | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/uitable.h b/uitable.h index 5302edfe..8d069eb5 100644 --- a/uitable.h +++ b/uitable.h @@ -1,11 +1,47 @@ // 20 june 2016 // kept in a separate file for now +// uiImage stores an image for display on screen. +// +// Images are built from one or more representations, each with the +// same aspect ratio but a different pixel size. libui automatically +// selects the most appropriate representation for drawing the image +// when it comes time to draw the image; what this means depends +// on the pixel density of the target context. Therefore, one can use +// uiImage to draw higher-detailed images on higher-density +// displays. The typical use cases are either: +// +// - have just a single representation, at which point all screens +// use the same image, and thus uiImage acts like a simple +// bitmap image, or +// - have two images, one at normal resolution and one at 2x +// resolution; this matches the current expectations of some +// desktop systems at the time of writing (mid-2018) +// +// uiImage is very simple: it only supports non-premultiplied 32-bit +// ARGB images, and libui does not provide any image file loading +// or image format conversion utilities on top of that. typedef struct uiImage uiImage; -// TODO use const void * for const correctness +// @role uiImage constructor +// uiNewImage creates a new uiImage with the given width and +// height. This width and height should be the size in points of the +// image in the device-independent case; typically this is the 1x size. +// TODO for all uiImage functions: use const void * for const correctness _UI_EXTERN uiImage *uiNewImage(double width, double height); + +// @role uiImage destructor +// uiFreeImage frees the given image and all associated resources. _UI_EXTERN void uiFreeImage(uiImage *i); + +// uiImageAppend adds a representation to the uiImage. +// pixels should point to a byte array of non-premultiplied pixels +// stored in [A R G B] order (so ((uint8_t *) pixels)[0] is the A of the +// first pixel and [3] is the B of the first pixel). pixelWidth and +// pixelHeight is the size *in pixels* of the image, and pixelStride is +// the number *of pixels* per row of the pixels array. Therefore, +// pixels itself must be at least 4 * pixelStride * pixelHeight bytes +// long. _UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride); typedef struct uiTableValue uiTableValue; From e0ca00e55be81801827a5f3a8aef80c48a23202d Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 5 Aug 2018 18:39:29 -0400 Subject: [PATCH 158/166] Resolved confusion about the terminology of strides in uiImageAppend(). Also prevents overallocation on some platforms. Thanks to @mischnic and @msink for spotting this. Update #402. --- darwin/image.m | 25 ++++++++++++------------- uitable.h | 9 ++++----- unix/image.c | 20 +++++++++++--------- unix/table.c | 2 ++ windows/image.cpp | 6 +++--- 5 files changed, 32 insertions(+), 30 deletions(-) diff --git a/darwin/image.m b/darwin/image.m index 28881ca3..9492cd07 100644 --- a/darwin/image.m +++ b/darwin/image.m @@ -30,28 +30,27 @@ void uiFreeImage(uiImage *i) uiprivFree(i); } -void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride) +void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride) { NSBitmapImageRep *repCalibrated, *repsRGB; uint8_t *swizzled, *bp, *sp; - int x, y; + int n; unsigned char *pix[1]; // OS X demands that R and B are in the opposite order from what we expect // we must swizzle :( // LONGTERM test on a big-endian system - swizzled = (uint8_t *) uiprivAlloc((pixelStride * pixelHeight * 4) * sizeof (uint8_t), "uint8_t[]"); + swizzled = (uint8_t *) uiprivAlloc((byteStride * pixelHeight) * sizeof (uint8_t), "uint8_t[]"); bp = (uint8_t *) pixels; sp = swizzled; - for (y = 0; y < pixelHeight * pixelStride; y += pixelStride) - for (x = 0; x < pixelStride; x++) { - sp[0] = bp[2]; - sp[1] = bp[1]; - sp[2] = bp[0]; - sp[3] = bp[3]; - sp += 4; - bp += 4; - } + for (n = 0; n < byteStride * pixelHeight; n += 4) { + sp[0] = bp[2]; + sp[1] = bp[1]; + sp[2] = bp[0]; + sp[3] = bp[3]; + sp += 4; + bp += 4; + } pix[0] = (unsigned char *) swizzled; repCalibrated = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:pix @@ -63,7 +62,7 @@ void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, in isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bitmapFormat:0 - bytesPerRow:pixelStride + bytesPerRow:byteStride bitsPerPixel:32]; repsRGB = [repCalibrated bitmapImageRepByRetaggingWithColorSpace:[NSColorSpace sRGBColorSpace]]; diff --git a/uitable.h b/uitable.h index 8d069eb5..15f33ebe 100644 --- a/uitable.h +++ b/uitable.h @@ -36,13 +36,12 @@ _UI_EXTERN void uiFreeImage(uiImage *i); // uiImageAppend adds a representation to the uiImage. // pixels should point to a byte array of non-premultiplied pixels -// stored in [A R G B] order (so ((uint8_t *) pixels)[0] is the A of the +// stored in [R G B A] order (so ((uint8_t *) pixels)[0] is the A of the // first pixel and [3] is the B of the first pixel). pixelWidth and // pixelHeight is the size *in pixels* of the image, and pixelStride is -// the number *of pixels* per row of the pixels array. Therefore, -// pixels itself must be at least 4 * pixelStride * pixelHeight bytes -// long. -_UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride); +// the number *of bytes* per row of the pixels array. Therefore, +// pixels itself must be at least byteStride * pixelHeight bytes long. +_UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride); typedef struct uiTableValue uiTableValue; diff --git a/unix/image.c b/unix/image.c index 3b5db020..eecdec18 100644 --- a/unix/image.c +++ b/unix/image.c @@ -34,24 +34,26 @@ void uiFreeImage(uiImage *i) uiprivFree(i); } -void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride) +void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride) { cairo_surface_t *cs; unsigned char *buf, *p; uint8_t *src = (uint8_t *) pixels; - int cstride; - int y; + int cByteStride; + int n; - cstride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixelWidth); - buf = (unsigned char *) uiprivAlloc((cstride * pixelHeight * 4) * sizeof (unsigned char), "unsigned char[]"); + // unfortunately for optimal performance cairo expects its own stride values and we will have to reconcile them if they differ + cByteStride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixelWidth); + buf = (unsigned char *) uiprivAlloc((cByteStride * pixelHeight) * sizeof (unsigned char), "unsigned char[]"); p = buf; - for (y = 0; y < pixelStride * pixelHeight; y += pixelStride) { - memmove(p, src + y, cstride); - p += cstride; + for (n = 0; n < byteStride * pixelHeight; n += byteStride) { + memmove(p, src + n, cByteStride); + p += cByteStride; } + // also note that stride here is also in bytes cs = cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_ARGB32, pixelWidth, pixelHeight, - cstride); + cByteStride); if (cairo_surface_status(cs) != CAIRO_STATUS_SUCCESS) /* TODO */; cairo_surface_flush(cs); diff --git a/unix/table.c b/unix/table.c index 9e6fe01e..d1e6fe60 100644 --- a/unix/table.c +++ b/unix/table.c @@ -2,6 +2,8 @@ #include "uipriv_unix.h" #include "table.h" +// TODO with GDK_SCALE set to 2 the 32x32 images are scaled up to 64x64? + struct uiTable { uiUnixControl c; GtkWidget *widget; diff --git a/windows/image.cpp b/windows/image.cpp index 0058aa13..368c77fa 100644 --- a/windows/image.cpp +++ b/windows/image.cpp @@ -39,14 +39,14 @@ void uiFreeImage(uiImage *i) uiprivFree(i); } -void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride) +void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride) { IWICBitmap *b; HRESULT hr; hr = uiprivWICFactory->CreateBitmapFromMemory(pixelWidth, pixelHeight, - GUID_WICPixelFormat32bppRGBA, pixelStride, - pixelStride * pixelHeight, (BYTE *) pixels, + GUID_WICPixelFormat32bppRGBA, byteStride, + byteStride * pixelHeight, (BYTE *) pixels, &b); if (hr != S_OK) logHRESULT(L"error calling CreateBitmapFromMemory() in uiImageAppend()", hr); From c0742d3de00325a618334a62b4c5c9c4799ddd6b Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig Date: Mon, 6 Aug 2018 09:06:02 +0200 Subject: [PATCH 159/166] Mac uiImageAppend: handle stride correctly --- darwin/image.m | 20 ++++++++++++-------- uitable.h | 6 +++--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/darwin/image.m b/darwin/image.m index 9492cd07..cf2bdf13 100644 --- a/darwin/image.m +++ b/darwin/image.m @@ -34,7 +34,7 @@ void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, in { NSBitmapImageRep *repCalibrated, *repsRGB; uint8_t *swizzled, *bp, *sp; - int n; + int n, l; unsigned char *pix[1]; // OS X demands that R and B are in the opposite order from what we expect @@ -43,13 +43,17 @@ void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, in swizzled = (uint8_t *) uiprivAlloc((byteStride * pixelHeight) * sizeof (uint8_t), "uint8_t[]"); bp = (uint8_t *) pixels; sp = swizzled; - for (n = 0; n < byteStride * pixelHeight; n += 4) { - sp[0] = bp[2]; - sp[1] = bp[1]; - sp[2] = bp[0]; - sp[3] = bp[3]; - sp += 4; - bp += 4; + for (l = 0; l < pixelHeight; l++){ + for (n = 0; n < pixelWidth; n++) { + sp[0] = bp[2]; + sp[1] = bp[1]; + sp[2] = bp[0]; + sp[3] = bp[3]; + sp += 4; + bp += 4; + } + // jump over unused bytes at end of line + bp += byteStride - pixelWidth * 4; } pix[0] = (unsigned char *) swizzled; diff --git a/uitable.h b/uitable.h index 15f33ebe..17a7abe8 100644 --- a/uitable.h +++ b/uitable.h @@ -19,7 +19,7 @@ // desktop systems at the time of writing (mid-2018) // // uiImage is very simple: it only supports non-premultiplied 32-bit -// ARGB images, and libui does not provide any image file loading +// RGBA images, and libui does not provide any image file loading // or image format conversion utilities on top of that. typedef struct uiImage uiImage; @@ -36,8 +36,8 @@ _UI_EXTERN void uiFreeImage(uiImage *i); // uiImageAppend adds a representation to the uiImage. // pixels should point to a byte array of non-premultiplied pixels -// stored in [R G B A] order (so ((uint8_t *) pixels)[0] is the A of the -// first pixel and [3] is the B of the first pixel). pixelWidth and +// stored in [R G B A] order (so ((uint8_t *) pixels)[0] is the R of the +// first pixel and [3] is the A of the first pixel). pixelWidth and // pixelHeight is the size *in pixels* of the image, and pixelStride is // the number *of bytes* per row of the pixels array. Therefore, // pixels itself must be at least byteStride * pixelHeight bytes long. From 68dade3e8103c6159ed908ab9f81cc6b407094f6 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig Date: Mon, 6 Aug 2018 10:54:19 +0200 Subject: [PATCH 160/166] Rename loop variable --- darwin/image.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/darwin/image.m b/darwin/image.m index cf2bdf13..a4b322c7 100644 --- a/darwin/image.m +++ b/darwin/image.m @@ -34,7 +34,7 @@ void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, in { NSBitmapImageRep *repCalibrated, *repsRGB; uint8_t *swizzled, *bp, *sp; - int n, l; + int x, y; unsigned char *pix[1]; // OS X demands that R and B are in the opposite order from what we expect @@ -43,8 +43,8 @@ void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, in swizzled = (uint8_t *) uiprivAlloc((byteStride * pixelHeight) * sizeof (uint8_t), "uint8_t[]"); bp = (uint8_t *) pixels; sp = swizzled; - for (l = 0; l < pixelHeight; l++){ - for (n = 0; n < pixelWidth; n++) { + for (y = 0; y < pixelHeight; y++){ + for (x = 0; x < pixelWidth; x++) { sp[0] = bp[2]; sp[1] = bp[1]; sp[2] = bp[0]; From cd38f774905d60c9c50e42fb71b72058bfe7c3d7 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 6 Aug 2018 08:52:51 -0400 Subject: [PATCH 161/166] More TODOs regarding strides. --- uitable.h | 1 + 1 file changed, 1 insertion(+) diff --git a/uitable.h b/uitable.h index 17a7abe8..23e7abab 100644 --- a/uitable.h +++ b/uitable.h @@ -41,6 +41,7 @@ _UI_EXTERN void uiFreeImage(uiImage *i); // pixelHeight is the size *in pixels* of the image, and pixelStride is // the number *of bytes* per row of the pixels array. Therefore, // pixels itself must be at least byteStride * pixelHeight bytes long. +// TODO see if we either need the stride or can provide a way to get the OS-preferred stride (in cairo we do) _UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride); typedef struct uiTableValue uiTableValue; From dee35216ac3ac826260a592765bd5a42db84a8a4 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Mon, 6 Aug 2018 20:03:57 -0400 Subject: [PATCH 162/166] Added documentation for uiTableValue. Also added a @role to uiFreeAttribute(). --- ui.h | 1 + uitable.h | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/ui.h b/ui.h index 228fa755..07f22371 100644 --- a/ui.h +++ b/ui.h @@ -512,6 +512,7 @@ _UI_EXTERN void uiDrawRestore(uiDrawContext *c); // contents as necessary. typedef struct uiAttribute uiAttribute; +// @role uiAttribute destructor // uiFreeAttribute() frees a uiAttribute. You generally do not need to // call this yourself, as uiAttributedString does this for you. In fact, // it is an error to call this function on a uiAttribute that has been diff --git a/uitable.h b/uitable.h index 23e7abab..77c11add 100644 --- a/uitable.h +++ b/uitable.h @@ -44,10 +44,31 @@ _UI_EXTERN void uiFreeImage(uiImage *i); // TODO see if we either need the stride or can provide a way to get the OS-preferred stride (in cairo we do) _UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride); +// uiTableValue stores a value to be passed along uiTable and +// uiTableModel. +// +// You do not create uiTableValues directly; instead, you create a +// uiTableValue of a given type using the specialized constructor +// functions. +// +// uiTableValues are immutable and the uiTableModel and uiTable +// take ownership of the uiTableValue object once returned, copying +// its contents as necessary. typedef struct uiTableValue uiTableValue; +// @role uiTableValue destructor +// uiFreeTableValue() frees a uiTableValue. You generally do not +// need to call this yourself, as uiTable and uiTableModel do this +// for you. In fact, it is an error to call this function on a uiTableValue +// that has been given to a uiTable or uiTableModel. You can call this, +// however, if you created a uiTableValue that you aren't going to +// use later, or if you called a uiTableModelHandler method directly +// and thus never transferred ownership of the uiTableValue. _UI_EXTERN void uiFreeTableValue(uiTableValue *v); +// uiTableValueType holds the possible uiTableValue types that may +// be returned by uiTableValueGetType(). Refer to the documentation +// for each type's constructor function for details on each type. // TODO actually validate these _UI_ENUM(uiTableValueType) { uiTableValueTypeString, @@ -56,19 +77,56 @@ _UI_ENUM(uiTableValueType) { uiTableValueTypeColor, }; +// uiTableValueGetType() returns the type of v. // TODO I don't like this name _UI_EXTERN uiTableValueType uiTableValueGetType(const uiTableValue *v); +// uiNewTableValueString() returns a new uiTableValue that contains +// str. str is copied; you do not need to keep it alive after +// uiNewTableValueString() returns. _UI_EXTERN uiTableValue *uiNewTableValueString(const char *str); + +// uiTableValueString() returns the string stored in v. The returned +// string is owned by v. It is an error to call this on a uiTableValue +// that does not hold a string. _UI_EXTERN const char *uiTableValueString(const uiTableValue *v); +// uiNewTableValueImage() returns a new uiTableValue that contains +// the given uiImage. +// +// Unlike other similar constructors, uiNewTableValueImage() does +// NOT copy the image. This is because images are comparatively +// larger than the other objects in question. Therefore, you MUST +// keep the image alive as long as the returned uiTableValue is alive. +// As a general rule, if libui calls a uiTableModelHandler method, the +// uiImage is safe to free once any of your code is once again +// executed. _UI_EXTERN uiTableValue *uiNewTableValueImage(uiImage *img); + +// uiTableValueImage() returns the uiImage stored in v. As these +// images are not owned by v, you should not assume anything +// about the lifetime of the image (unless you created the image, +// and thus control its lifetime). It is an error to call this on a +// uiTableValue that does not hold an image. _UI_EXTERN uiImage *uiTableValueImage(const uiTableValue *v); +// uiNewTableValueInt() returns a uiTableValue that stores the given +// int. This can be used both for boolean values (nonzero is true, as +// in C) or progresses (in which case the valid range is -1..100 +// inclusive). _UI_EXTERN uiTableValue *uiNewTableValueInt(int i); + +// uiTableValueInt() returns the int stored in v. It is an error to call +// this on a uiTableValue that does not store an int. _UI_EXTERN int uiTableValueInt(const uiTableValue *v); +// uiNewTableValueColor() returns a uiTableValue that stores the +// given color. _UI_EXTERN uiTableValue *uiNewTableValueColor(double r, double g, double b, double a); + +// uiTableValueColor() returns the color stored in v. It is an error to +// call this on a uiTableValue that does not store a color. +// TODO define whether all this, for both uiTableValue and uiAttribute, is undefined behavior or a caught error _UI_EXTERN void uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a); typedef struct uiTableModel uiTableModel; From 9202aeee2aa5774d03745a89e1d0b8ebc1dbb427 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 7 Aug 2018 10:58:12 -0400 Subject: [PATCH 163/166] Documented uiTableModel. --- uitable.h | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/uitable.h b/uitable.h index 77c11add..36ff0ccc 100644 --- a/uitable.h +++ b/uitable.h @@ -129,22 +129,90 @@ _UI_EXTERN uiTableValue *uiNewTableValueColor(double r, double g, double b, doub // TODO define whether all this, for both uiTableValue and uiAttribute, is undefined behavior or a caught error _UI_EXTERN void uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a); +// uiTableModel is an object that provides the data for a uiTable. +// This data is returned via methods you provide in the +// uiTableModelHandler struct. +// +// uiTableModel represents data using a table, but this table does +// not map directly to uiTable itself. Instead, you can have data +// columns which provide instructions for how to render a given +// uiTable's column — for instance, one model column can be used +// to give certain rows of a uiTable a different background color. +// +// Once created, the number and data types of columns of a +// uiTableModel cannot change. +// +// Row and column numbers start at 0. typedef struct uiTableModel uiTableModel; + +// uiTableModelHandler defines the methods that uiTableModel +// calls when it needs data. Once a uiTableModel is created, these +// methods cannot change. typedef struct uiTableModelHandler uiTableModelHandler; // TODO validate ranges; validate types on each getter/setter call (? table columns only?) struct uiTableModelHandler { + // NumColumns returns the number of model columns in the + // uiTableModel. This value must remain constant through the + // lifetime of the uiTableModel. This method is not guaranteed + // to be called depending on the system. + // TODO strongly check column numbers and types on all platforms so these clauses can go away int (*NumColumns)(uiTableModelHandler *, uiTableModel *); + // ColumnType returns the value type of the data stored in + // the given model column of the uiTableModel. The returned + // values must remain constant through the lifetime of the + // uiTableModel. This method is not guaranteed to be called + // depending on the system. uiTableValueType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int); + // NumRows returns the number or rows in the uiTableModel. + // This value must be non-negative. int (*NumRows)(uiTableModelHandler *, uiTableModel *); - uiTableValue *(*CellValue)(uiTableModelHandler *, uiTableModel *, int, int); + // CellValue returns a uiTableValue corresponding to the model + // cell at (row, column). The type of the returned uiTableValue + // must match column's value type. Under some circumstances, + // NULL may be returned; refer to the various methods that add + // columns to uiTable for details. Once returned, the uiTable + // that calls CellValue will free the uiTableValue returned. + uiTableValue *(*CellValue)(uiTableModelHandler *mh, uiTableModel *m, int row, int column); + // SetCellValue changes the model cell value at (row, column) + // in the uiTableModel. Within this function, either do nothing + // to keep the current cell value or save the new cell value as + // appropriate. After SetCellValue is called, the uiTable will + // itself reload the table cell. Under certain conditions, the + // uiTableValue passed in can be NULL; refer to the various + // methods that add columns to uiTable for details. Once + // returned, the uiTable that called SetCellValue will free the + // uiTableValue passed in. void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const uiTableValue *); }; +// uiNewTableModel() creates a new uiTableModel with the given +// handler methods. _UI_EXTERN uiTableModel *uiNewTableModel(uiTableModelHandler *mh); + +// uiFreeTableModel() frees the given table model. It is an error to +// free table models currently associated with a uiTable. _UI_EXTERN void uiFreeTableModel(uiTableModel *m); + +// uiTableModelRowInserted() tells any uiTable associated with m +// that a new row has been added to m at index index. You call +// this function when the number of rows in your model has +// changed; after calling it, NumRows() should returm the new row +// count. _UI_EXTERN void uiTableModelRowInserted(uiTableModel *m, int newIndex); + +// uiTableModelRowChanged() tells any uiTable associated with m +// that the data in the row at index has changed. You do not need to +// call this in your SetCellValue() handlers, but you do need to call +// this if your data changes at some other point. _UI_EXTERN void uiTableModelRowChanged(uiTableModel *m, int index); + +// uiTableModelRowDeleted() tells any uiTable associated with m +// that the row at index index has been deleted. You call this +// function when the number of rows in your model has changed; +// after calling it, NumRows() should returm the new row +// count. +// TODO for this and Inserted: make sure the "after" part is right; clarify if it's after returning or after calling _UI_EXTERN void uiTableModelRowDeleted(uiTableModel *m, int oldIndex); // TODO reordering/moving From 1c3d7be2279b546dde9efa24a3c6e1fb35f56fcd Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Tue, 7 Aug 2018 11:18:11 -0400 Subject: [PATCH 164/166] More uiTableModel documentation. --- uitable.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/uitable.h b/uitable.h index 36ff0ccc..20054ace 100644 --- a/uitable.h +++ b/uitable.h @@ -138,11 +138,13 @@ _UI_EXTERN void uiTableValueColor(const uiTableValue *v, double *r, double *g, d // columns which provide instructions for how to render a given // uiTable's column — for instance, one model column can be used // to give certain rows of a uiTable a different background color. +// Row numbers DO match with uiTable row numbers. // // Once created, the number and data types of columns of a // uiTableModel cannot change. // -// Row and column numbers start at 0. +// Row and column numbers start at 0. A uiTableModel can be +// associated with more than one uiTable at a time. typedef struct uiTableModel uiTableModel; // uiTableModelHandler defines the methods that uiTableModel @@ -186,10 +188,12 @@ struct uiTableModelHandler { void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const uiTableValue *); }; +// @role uiTableModel constructor // uiNewTableModel() creates a new uiTableModel with the given // handler methods. _UI_EXTERN uiTableModel *uiNewTableModel(uiTableModelHandler *mh); +// @role uiTableModel destructor // uiFreeTableModel() frees the given table model. It is an error to // free table models currently associated with a uiTable. _UI_EXTERN void uiFreeTableModel(uiTableModel *m); From f47e1423cf95ad7b1001663f3381b5a819fc67b9 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 8 Aug 2018 09:17:50 -0400 Subject: [PATCH 165/166] And finished documenting uitable.h. Yay, we can merge back in now! --- uitable.h | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/uitable.h b/uitable.h index 20054ace..715fcf04 100644 --- a/uitable.h +++ b/uitable.h @@ -220,41 +220,88 @@ _UI_EXTERN void uiTableModelRowChanged(uiTableModel *m, int index); _UI_EXTERN void uiTableModelRowDeleted(uiTableModel *m, int oldIndex); // TODO reordering/moving +// uiTableModelColumnNeverEditable and +// uiTableModelColumnAlwaysEditable are the value of an editable +// model column parameter to one of the uiTable create column +// functions; if used, that jparticular uiTable colum is not editable +// by the user and always editable by the user, respectively. #define uiTableModelColumnNeverEditable (-1) #define uiTableModelColumnAlwaysEditable (-2) +// uiTableTextColumnOptionalParams are the optional parameters +// that control the appearance of the text column of a uiTable. typedef struct uiTableTextColumnOptionalParams uiTableTextColumnOptionalParams; + +// uiTableParams defines the parameters passed to uiNewTable(). typedef struct uiTableParams uiTableParams; struct uiTableTextColumnOptionalParams { + // ColorModelColumn is the model column containing the + // text color of this uiTable column's text, or -1 to use the + // default color. + // + // If CellValue() for this column for any cell returns NULL, that + // cell will also use the default text color. int ColorModelColumn; }; struct uiTableParams { + // Model is the uiTableModel to use for this uiTable. + // This parameter cannot be NULL. uiTableModel *Model; + // RowBackgroundColorModelColumn is a model column + // number that defines the background color used for the + // entire row in the uiTable, or -1 to use the default color for + // all rows. + // + // If CellValue() for this column for any row returns NULL, that + // row will also use the default background color. int RowBackgroundColorModelColumn; }; +// uiTable is a uiControl that shows tabular data, allowing users to +// manipulate rows of such data at a time. typedef struct uiTable uiTable; #define uiTable(this) ((uiTable *) (this)) + +// uiTableAppendTextColumn() appends a text column to t. +// name is displayed in the table header. +// textModelColumn is where the text comes from. +// If a row is editable according to textEditableModelColumn, +// SetCellValue() is called with textModelColumn as the column. _UI_EXTERN void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams); + +// uiTableAppendImageColumn() appends an image column to t. +// Images are drawn at icon size, appropriate to the pixel density +// of the screen showing the uiTable. _UI_EXTERN void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn); + +// uiTableAppendImageTextColumn() appends a column to t that +// shows both an image and text. _UI_EXTERN void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams); + +// uiTableAppendCheckboxColumn appends a column to t that +// contains a checkbox that the user can interact with (assuming the +// checkbox is editable). SetCellValue() will be called with +// checkboxModelColumn as the column in this case. _UI_EXTERN void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn); + +// uiTableAppendCheckboxTextColumn() appends a column to t +// that contains both a checkbox and text. _UI_EXTERN void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, @@ -262,11 +309,25 @@ _UI_EXTERN void uiTableAppendCheckboxTextColumn(uiTable *t, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams); + +// uiTableAppendProgressBarColumn() appends a column to t +// that displays a progress bar. These columns work like +// uiProgressBar: a cell value of 0..100 displays that percentage, and +// a cell value of -1 displays an indeterminate progress bar. _UI_EXTERN void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn); + +// uiTableAppendButtonColumn() appends a column to t +// that shows a button that the user can click on. When the user +// does click on the button, SetCellValue() is called with a NULL +// value and buttonModelColumn as the column. +// CellValue() on buttonModelColumn should return the text to show +// in the button. _UI_EXTERN void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn); + +// uiNewTable() creates a new uiTable with the specified parameters. _UI_EXTERN uiTable *uiNewTable(uiTableParams *params); From 1a8e5555da752a3ef8e5385f0c6d6f07a2b1a076 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 8 Aug 2018 09:24:22 -0400 Subject: [PATCH 166/166] Updated the README and merged uitable.h into ui.h. Now to merge! --- README.md | 3 + ui.h | 332 ++++++++++++++++++++++++++++++++++++++++++++++++++++- uitable.h | 333 ------------------------------------------------------ 3 files changed, 333 insertions(+), 335 deletions(-) delete mode 100644 uitable.h diff --git a/README.md b/README.md index e9bbd4e5..6fd69209 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ But libui is not dead; I am working on it whenever I can, and I hope to get it t *Note that today's entry (Eastern Time) may be updated later today.* +* **8 August 2018** + * Finally introduced an API for loading images, `uiImage`, and a new control, `uiTable`, for displaying tabular data. These provide enough basic functionality for now, but will be improved over time. You can read the documentation for the new features as they are [here](https://github.com/andlabs/libui/blob/f47e1423cf95ad7b1001663f3381b5a819fc67b9/uitable.h). Thanks to everyone who helped get to this point, in particular @bcampbell for the initial Windows code, and to everyone else for their patience! + * **30 May 2018** * Merged the previous Announcements and Updates section of this README into a single News section, and merged the respective archive files into a single NEWS.md file. diff --git a/ui.h b/ui.h index 07f22371..253cd2f0 100644 --- a/ui.h +++ b/ui.h @@ -1124,8 +1124,336 @@ _UI_EXTERN int uiGridPadded(uiGrid *g); _UI_EXTERN void uiGridSetPadded(uiGrid *g, int padded); _UI_EXTERN uiGrid *uiNewGrid(void); -// TODO merge -#include "uitable.h" +// uiImage stores an image for display on screen. +// +// Images are built from one or more representations, each with the +// same aspect ratio but a different pixel size. libui automatically +// selects the most appropriate representation for drawing the image +// when it comes time to draw the image; what this means depends +// on the pixel density of the target context. Therefore, one can use +// uiImage to draw higher-detailed images on higher-density +// displays. The typical use cases are either: +// +// - have just a single representation, at which point all screens +// use the same image, and thus uiImage acts like a simple +// bitmap image, or +// - have two images, one at normal resolution and one at 2x +// resolution; this matches the current expectations of some +// desktop systems at the time of writing (mid-2018) +// +// uiImage is very simple: it only supports non-premultiplied 32-bit +// RGBA images, and libui does not provide any image file loading +// or image format conversion utilities on top of that. +typedef struct uiImage uiImage; + +// @role uiImage constructor +// uiNewImage creates a new uiImage with the given width and +// height. This width and height should be the size in points of the +// image in the device-independent case; typically this is the 1x size. +// TODO for all uiImage functions: use const void * for const correctness +_UI_EXTERN uiImage *uiNewImage(double width, double height); + +// @role uiImage destructor +// uiFreeImage frees the given image and all associated resources. +_UI_EXTERN void uiFreeImage(uiImage *i); + +// uiImageAppend adds a representation to the uiImage. +// pixels should point to a byte array of non-premultiplied pixels +// stored in [R G B A] order (so ((uint8_t *) pixels)[0] is the R of the +// first pixel and [3] is the A of the first pixel). pixelWidth and +// pixelHeight is the size *in pixels* of the image, and pixelStride is +// the number *of bytes* per row of the pixels array. Therefore, +// pixels itself must be at least byteStride * pixelHeight bytes long. +// TODO see if we either need the stride or can provide a way to get the OS-preferred stride (in cairo we do) +_UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride); + +// uiTableValue stores a value to be passed along uiTable and +// uiTableModel. +// +// You do not create uiTableValues directly; instead, you create a +// uiTableValue of a given type using the specialized constructor +// functions. +// +// uiTableValues are immutable and the uiTableModel and uiTable +// take ownership of the uiTableValue object once returned, copying +// its contents as necessary. +typedef struct uiTableValue uiTableValue; + +// @role uiTableValue destructor +// uiFreeTableValue() frees a uiTableValue. You generally do not +// need to call this yourself, as uiTable and uiTableModel do this +// for you. In fact, it is an error to call this function on a uiTableValue +// that has been given to a uiTable or uiTableModel. You can call this, +// however, if you created a uiTableValue that you aren't going to +// use later, or if you called a uiTableModelHandler method directly +// and thus never transferred ownership of the uiTableValue. +_UI_EXTERN void uiFreeTableValue(uiTableValue *v); + +// uiTableValueType holds the possible uiTableValue types that may +// be returned by uiTableValueGetType(). Refer to the documentation +// for each type's constructor function for details on each type. +// TODO actually validate these +_UI_ENUM(uiTableValueType) { + uiTableValueTypeString, + uiTableValueTypeImage, + uiTableValueTypeInt, + uiTableValueTypeColor, +}; + +// uiTableValueGetType() returns the type of v. +// TODO I don't like this name +_UI_EXTERN uiTableValueType uiTableValueGetType(const uiTableValue *v); + +// uiNewTableValueString() returns a new uiTableValue that contains +// str. str is copied; you do not need to keep it alive after +// uiNewTableValueString() returns. +_UI_EXTERN uiTableValue *uiNewTableValueString(const char *str); + +// uiTableValueString() returns the string stored in v. The returned +// string is owned by v. It is an error to call this on a uiTableValue +// that does not hold a string. +_UI_EXTERN const char *uiTableValueString(const uiTableValue *v); + +// uiNewTableValueImage() returns a new uiTableValue that contains +// the given uiImage. +// +// Unlike other similar constructors, uiNewTableValueImage() does +// NOT copy the image. This is because images are comparatively +// larger than the other objects in question. Therefore, you MUST +// keep the image alive as long as the returned uiTableValue is alive. +// As a general rule, if libui calls a uiTableModelHandler method, the +// uiImage is safe to free once any of your code is once again +// executed. +_UI_EXTERN uiTableValue *uiNewTableValueImage(uiImage *img); + +// uiTableValueImage() returns the uiImage stored in v. As these +// images are not owned by v, you should not assume anything +// about the lifetime of the image (unless you created the image, +// and thus control its lifetime). It is an error to call this on a +// uiTableValue that does not hold an image. +_UI_EXTERN uiImage *uiTableValueImage(const uiTableValue *v); + +// uiNewTableValueInt() returns a uiTableValue that stores the given +// int. This can be used both for boolean values (nonzero is true, as +// in C) or progresses (in which case the valid range is -1..100 +// inclusive). +_UI_EXTERN uiTableValue *uiNewTableValueInt(int i); + +// uiTableValueInt() returns the int stored in v. It is an error to call +// this on a uiTableValue that does not store an int. +_UI_EXTERN int uiTableValueInt(const uiTableValue *v); + +// uiNewTableValueColor() returns a uiTableValue that stores the +// given color. +_UI_EXTERN uiTableValue *uiNewTableValueColor(double r, double g, double b, double a); + +// uiTableValueColor() returns the color stored in v. It is an error to +// call this on a uiTableValue that does not store a color. +// TODO define whether all this, for both uiTableValue and uiAttribute, is undefined behavior or a caught error +_UI_EXTERN void uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a); + +// uiTableModel is an object that provides the data for a uiTable. +// This data is returned via methods you provide in the +// uiTableModelHandler struct. +// +// uiTableModel represents data using a table, but this table does +// not map directly to uiTable itself. Instead, you can have data +// columns which provide instructions for how to render a given +// uiTable's column — for instance, one model column can be used +// to give certain rows of a uiTable a different background color. +// Row numbers DO match with uiTable row numbers. +// +// Once created, the number and data types of columns of a +// uiTableModel cannot change. +// +// Row and column numbers start at 0. A uiTableModel can be +// associated with more than one uiTable at a time. +typedef struct uiTableModel uiTableModel; + +// uiTableModelHandler defines the methods that uiTableModel +// calls when it needs data. Once a uiTableModel is created, these +// methods cannot change. +typedef struct uiTableModelHandler uiTableModelHandler; + +// TODO validate ranges; validate types on each getter/setter call (? table columns only?) +struct uiTableModelHandler { + // NumColumns returns the number of model columns in the + // uiTableModel. This value must remain constant through the + // lifetime of the uiTableModel. This method is not guaranteed + // to be called depending on the system. + // TODO strongly check column numbers and types on all platforms so these clauses can go away + int (*NumColumns)(uiTableModelHandler *, uiTableModel *); + // ColumnType returns the value type of the data stored in + // the given model column of the uiTableModel. The returned + // values must remain constant through the lifetime of the + // uiTableModel. This method is not guaranteed to be called + // depending on the system. + uiTableValueType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int); + // NumRows returns the number or rows in the uiTableModel. + // This value must be non-negative. + int (*NumRows)(uiTableModelHandler *, uiTableModel *); + // CellValue returns a uiTableValue corresponding to the model + // cell at (row, column). The type of the returned uiTableValue + // must match column's value type. Under some circumstances, + // NULL may be returned; refer to the various methods that add + // columns to uiTable for details. Once returned, the uiTable + // that calls CellValue will free the uiTableValue returned. + uiTableValue *(*CellValue)(uiTableModelHandler *mh, uiTableModel *m, int row, int column); + // SetCellValue changes the model cell value at (row, column) + // in the uiTableModel. Within this function, either do nothing + // to keep the current cell value or save the new cell value as + // appropriate. After SetCellValue is called, the uiTable will + // itself reload the table cell. Under certain conditions, the + // uiTableValue passed in can be NULL; refer to the various + // methods that add columns to uiTable for details. Once + // returned, the uiTable that called SetCellValue will free the + // uiTableValue passed in. + void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const uiTableValue *); +}; + +// @role uiTableModel constructor +// uiNewTableModel() creates a new uiTableModel with the given +// handler methods. +_UI_EXTERN uiTableModel *uiNewTableModel(uiTableModelHandler *mh); + +// @role uiTableModel destructor +// uiFreeTableModel() frees the given table model. It is an error to +// free table models currently associated with a uiTable. +_UI_EXTERN void uiFreeTableModel(uiTableModel *m); + +// uiTableModelRowInserted() tells any uiTable associated with m +// that a new row has been added to m at index index. You call +// this function when the number of rows in your model has +// changed; after calling it, NumRows() should returm the new row +// count. +_UI_EXTERN void uiTableModelRowInserted(uiTableModel *m, int newIndex); + +// uiTableModelRowChanged() tells any uiTable associated with m +// that the data in the row at index has changed. You do not need to +// call this in your SetCellValue() handlers, but you do need to call +// this if your data changes at some other point. +_UI_EXTERN void uiTableModelRowChanged(uiTableModel *m, int index); + +// uiTableModelRowDeleted() tells any uiTable associated with m +// that the row at index index has been deleted. You call this +// function when the number of rows in your model has changed; +// after calling it, NumRows() should returm the new row +// count. +// TODO for this and Inserted: make sure the "after" part is right; clarify if it's after returning or after calling +_UI_EXTERN void uiTableModelRowDeleted(uiTableModel *m, int oldIndex); +// TODO reordering/moving + +// uiTableModelColumnNeverEditable and +// uiTableModelColumnAlwaysEditable are the value of an editable +// model column parameter to one of the uiTable create column +// functions; if used, that jparticular uiTable colum is not editable +// by the user and always editable by the user, respectively. +#define uiTableModelColumnNeverEditable (-1) +#define uiTableModelColumnAlwaysEditable (-2) + +// uiTableTextColumnOptionalParams are the optional parameters +// that control the appearance of the text column of a uiTable. +typedef struct uiTableTextColumnOptionalParams uiTableTextColumnOptionalParams; + +// uiTableParams defines the parameters passed to uiNewTable(). +typedef struct uiTableParams uiTableParams; + +struct uiTableTextColumnOptionalParams { + // ColorModelColumn is the model column containing the + // text color of this uiTable column's text, or -1 to use the + // default color. + // + // If CellValue() for this column for any cell returns NULL, that + // cell will also use the default text color. + int ColorModelColumn; +}; + +struct uiTableParams { + // Model is the uiTableModel to use for this uiTable. + // This parameter cannot be NULL. + uiTableModel *Model; + // RowBackgroundColorModelColumn is a model column + // number that defines the background color used for the + // entire row in the uiTable, or -1 to use the default color for + // all rows. + // + // If CellValue() for this column for any row returns NULL, that + // row will also use the default background color. + int RowBackgroundColorModelColumn; +}; + +// uiTable is a uiControl that shows tabular data, allowing users to +// manipulate rows of such data at a time. +typedef struct uiTable uiTable; +#define uiTable(this) ((uiTable *) (this)) + +// uiTableAppendTextColumn() appends a text column to t. +// name is displayed in the table header. +// textModelColumn is where the text comes from. +// If a row is editable according to textEditableModelColumn, +// SetCellValue() is called with textModelColumn as the column. +_UI_EXTERN void uiTableAppendTextColumn(uiTable *t, + const char *name, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *textParams); + +// uiTableAppendImageColumn() appends an image column to t. +// Images are drawn at icon size, appropriate to the pixel density +// of the screen showing the uiTable. +_UI_EXTERN void uiTableAppendImageColumn(uiTable *t, + const char *name, + int imageModelColumn); + +// uiTableAppendImageTextColumn() appends a column to t that +// shows both an image and text. +_UI_EXTERN void uiTableAppendImageTextColumn(uiTable *t, + const char *name, + int imageModelColumn, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *textParams); + +// uiTableAppendCheckboxColumn appends a column to t that +// contains a checkbox that the user can interact with (assuming the +// checkbox is editable). SetCellValue() will be called with +// checkboxModelColumn as the column in this case. +_UI_EXTERN void uiTableAppendCheckboxColumn(uiTable *t, + const char *name, + int checkboxModelColumn, + int checkboxEditableModelColumn); + +// uiTableAppendCheckboxTextColumn() appends a column to t +// that contains both a checkbox and text. +_UI_EXTERN void uiTableAppendCheckboxTextColumn(uiTable *t, + const char *name, + int checkboxModelColumn, + int checkboxEditableModelColumn, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *textParams); + +// uiTableAppendProgressBarColumn() appends a column to t +// that displays a progress bar. These columns work like +// uiProgressBar: a cell value of 0..100 displays that percentage, and +// a cell value of -1 displays an indeterminate progress bar. +_UI_EXTERN void uiTableAppendProgressBarColumn(uiTable *t, + const char *name, + int progressModelColumn); + +// uiTableAppendButtonColumn() appends a column to t +// that shows a button that the user can click on. When the user +// does click on the button, SetCellValue() is called with a NULL +// value and buttonModelColumn as the column. +// CellValue() on buttonModelColumn should return the text to show +// in the button. +_UI_EXTERN void uiTableAppendButtonColumn(uiTable *t, + const char *name, + int buttonModelColumn, + int buttonClickableModelColumn); + +// uiNewTable() creates a new uiTable with the specified parameters. +_UI_EXTERN uiTable *uiNewTable(uiTableParams *params); #ifdef __cplusplus } diff --git a/uitable.h b/uitable.h deleted file mode 100644 index 715fcf04..00000000 --- a/uitable.h +++ /dev/null @@ -1,333 +0,0 @@ -// 20 june 2016 -// kept in a separate file for now - -// uiImage stores an image for display on screen. -// -// Images are built from one or more representations, each with the -// same aspect ratio but a different pixel size. libui automatically -// selects the most appropriate representation for drawing the image -// when it comes time to draw the image; what this means depends -// on the pixel density of the target context. Therefore, one can use -// uiImage to draw higher-detailed images on higher-density -// displays. The typical use cases are either: -// -// - have just a single representation, at which point all screens -// use the same image, and thus uiImage acts like a simple -// bitmap image, or -// - have two images, one at normal resolution and one at 2x -// resolution; this matches the current expectations of some -// desktop systems at the time of writing (mid-2018) -// -// uiImage is very simple: it only supports non-premultiplied 32-bit -// RGBA images, and libui does not provide any image file loading -// or image format conversion utilities on top of that. -typedef struct uiImage uiImage; - -// @role uiImage constructor -// uiNewImage creates a new uiImage with the given width and -// height. This width and height should be the size in points of the -// image in the device-independent case; typically this is the 1x size. -// TODO for all uiImage functions: use const void * for const correctness -_UI_EXTERN uiImage *uiNewImage(double width, double height); - -// @role uiImage destructor -// uiFreeImage frees the given image and all associated resources. -_UI_EXTERN void uiFreeImage(uiImage *i); - -// uiImageAppend adds a representation to the uiImage. -// pixels should point to a byte array of non-premultiplied pixels -// stored in [R G B A] order (so ((uint8_t *) pixels)[0] is the R of the -// first pixel and [3] is the A of the first pixel). pixelWidth and -// pixelHeight is the size *in pixels* of the image, and pixelStride is -// the number *of bytes* per row of the pixels array. Therefore, -// pixels itself must be at least byteStride * pixelHeight bytes long. -// TODO see if we either need the stride or can provide a way to get the OS-preferred stride (in cairo we do) -_UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride); - -// uiTableValue stores a value to be passed along uiTable and -// uiTableModel. -// -// You do not create uiTableValues directly; instead, you create a -// uiTableValue of a given type using the specialized constructor -// functions. -// -// uiTableValues are immutable and the uiTableModel and uiTable -// take ownership of the uiTableValue object once returned, copying -// its contents as necessary. -typedef struct uiTableValue uiTableValue; - -// @role uiTableValue destructor -// uiFreeTableValue() frees a uiTableValue. You generally do not -// need to call this yourself, as uiTable and uiTableModel do this -// for you. In fact, it is an error to call this function on a uiTableValue -// that has been given to a uiTable or uiTableModel. You can call this, -// however, if you created a uiTableValue that you aren't going to -// use later, or if you called a uiTableModelHandler method directly -// and thus never transferred ownership of the uiTableValue. -_UI_EXTERN void uiFreeTableValue(uiTableValue *v); - -// uiTableValueType holds the possible uiTableValue types that may -// be returned by uiTableValueGetType(). Refer to the documentation -// for each type's constructor function for details on each type. -// TODO actually validate these -_UI_ENUM(uiTableValueType) { - uiTableValueTypeString, - uiTableValueTypeImage, - uiTableValueTypeInt, - uiTableValueTypeColor, -}; - -// uiTableValueGetType() returns the type of v. -// TODO I don't like this name -_UI_EXTERN uiTableValueType uiTableValueGetType(const uiTableValue *v); - -// uiNewTableValueString() returns a new uiTableValue that contains -// str. str is copied; you do not need to keep it alive after -// uiNewTableValueString() returns. -_UI_EXTERN uiTableValue *uiNewTableValueString(const char *str); - -// uiTableValueString() returns the string stored in v. The returned -// string is owned by v. It is an error to call this on a uiTableValue -// that does not hold a string. -_UI_EXTERN const char *uiTableValueString(const uiTableValue *v); - -// uiNewTableValueImage() returns a new uiTableValue that contains -// the given uiImage. -// -// Unlike other similar constructors, uiNewTableValueImage() does -// NOT copy the image. This is because images are comparatively -// larger than the other objects in question. Therefore, you MUST -// keep the image alive as long as the returned uiTableValue is alive. -// As a general rule, if libui calls a uiTableModelHandler method, the -// uiImage is safe to free once any of your code is once again -// executed. -_UI_EXTERN uiTableValue *uiNewTableValueImage(uiImage *img); - -// uiTableValueImage() returns the uiImage stored in v. As these -// images are not owned by v, you should not assume anything -// about the lifetime of the image (unless you created the image, -// and thus control its lifetime). It is an error to call this on a -// uiTableValue that does not hold an image. -_UI_EXTERN uiImage *uiTableValueImage(const uiTableValue *v); - -// uiNewTableValueInt() returns a uiTableValue that stores the given -// int. This can be used both for boolean values (nonzero is true, as -// in C) or progresses (in which case the valid range is -1..100 -// inclusive). -_UI_EXTERN uiTableValue *uiNewTableValueInt(int i); - -// uiTableValueInt() returns the int stored in v. It is an error to call -// this on a uiTableValue that does not store an int. -_UI_EXTERN int uiTableValueInt(const uiTableValue *v); - -// uiNewTableValueColor() returns a uiTableValue that stores the -// given color. -_UI_EXTERN uiTableValue *uiNewTableValueColor(double r, double g, double b, double a); - -// uiTableValueColor() returns the color stored in v. It is an error to -// call this on a uiTableValue that does not store a color. -// TODO define whether all this, for both uiTableValue and uiAttribute, is undefined behavior or a caught error -_UI_EXTERN void uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a); - -// uiTableModel is an object that provides the data for a uiTable. -// This data is returned via methods you provide in the -// uiTableModelHandler struct. -// -// uiTableModel represents data using a table, but this table does -// not map directly to uiTable itself. Instead, you can have data -// columns which provide instructions for how to render a given -// uiTable's column — for instance, one model column can be used -// to give certain rows of a uiTable a different background color. -// Row numbers DO match with uiTable row numbers. -// -// Once created, the number and data types of columns of a -// uiTableModel cannot change. -// -// Row and column numbers start at 0. A uiTableModel can be -// associated with more than one uiTable at a time. -typedef struct uiTableModel uiTableModel; - -// uiTableModelHandler defines the methods that uiTableModel -// calls when it needs data. Once a uiTableModel is created, these -// methods cannot change. -typedef struct uiTableModelHandler uiTableModelHandler; - -// TODO validate ranges; validate types on each getter/setter call (? table columns only?) -struct uiTableModelHandler { - // NumColumns returns the number of model columns in the - // uiTableModel. This value must remain constant through the - // lifetime of the uiTableModel. This method is not guaranteed - // to be called depending on the system. - // TODO strongly check column numbers and types on all platforms so these clauses can go away - int (*NumColumns)(uiTableModelHandler *, uiTableModel *); - // ColumnType returns the value type of the data stored in - // the given model column of the uiTableModel. The returned - // values must remain constant through the lifetime of the - // uiTableModel. This method is not guaranteed to be called - // depending on the system. - uiTableValueType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int); - // NumRows returns the number or rows in the uiTableModel. - // This value must be non-negative. - int (*NumRows)(uiTableModelHandler *, uiTableModel *); - // CellValue returns a uiTableValue corresponding to the model - // cell at (row, column). The type of the returned uiTableValue - // must match column's value type. Under some circumstances, - // NULL may be returned; refer to the various methods that add - // columns to uiTable for details. Once returned, the uiTable - // that calls CellValue will free the uiTableValue returned. - uiTableValue *(*CellValue)(uiTableModelHandler *mh, uiTableModel *m, int row, int column); - // SetCellValue changes the model cell value at (row, column) - // in the uiTableModel. Within this function, either do nothing - // to keep the current cell value or save the new cell value as - // appropriate. After SetCellValue is called, the uiTable will - // itself reload the table cell. Under certain conditions, the - // uiTableValue passed in can be NULL; refer to the various - // methods that add columns to uiTable for details. Once - // returned, the uiTable that called SetCellValue will free the - // uiTableValue passed in. - void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const uiTableValue *); -}; - -// @role uiTableModel constructor -// uiNewTableModel() creates a new uiTableModel with the given -// handler methods. -_UI_EXTERN uiTableModel *uiNewTableModel(uiTableModelHandler *mh); - -// @role uiTableModel destructor -// uiFreeTableModel() frees the given table model. It is an error to -// free table models currently associated with a uiTable. -_UI_EXTERN void uiFreeTableModel(uiTableModel *m); - -// uiTableModelRowInserted() tells any uiTable associated with m -// that a new row has been added to m at index index. You call -// this function when the number of rows in your model has -// changed; after calling it, NumRows() should returm the new row -// count. -_UI_EXTERN void uiTableModelRowInserted(uiTableModel *m, int newIndex); - -// uiTableModelRowChanged() tells any uiTable associated with m -// that the data in the row at index has changed. You do not need to -// call this in your SetCellValue() handlers, but you do need to call -// this if your data changes at some other point. -_UI_EXTERN void uiTableModelRowChanged(uiTableModel *m, int index); - -// uiTableModelRowDeleted() tells any uiTable associated with m -// that the row at index index has been deleted. You call this -// function when the number of rows in your model has changed; -// after calling it, NumRows() should returm the new row -// count. -// TODO for this and Inserted: make sure the "after" part is right; clarify if it's after returning or after calling -_UI_EXTERN void uiTableModelRowDeleted(uiTableModel *m, int oldIndex); -// TODO reordering/moving - -// uiTableModelColumnNeverEditable and -// uiTableModelColumnAlwaysEditable are the value of an editable -// model column parameter to one of the uiTable create column -// functions; if used, that jparticular uiTable colum is not editable -// by the user and always editable by the user, respectively. -#define uiTableModelColumnNeverEditable (-1) -#define uiTableModelColumnAlwaysEditable (-2) - -// uiTableTextColumnOptionalParams are the optional parameters -// that control the appearance of the text column of a uiTable. -typedef struct uiTableTextColumnOptionalParams uiTableTextColumnOptionalParams; - -// uiTableParams defines the parameters passed to uiNewTable(). -typedef struct uiTableParams uiTableParams; - -struct uiTableTextColumnOptionalParams { - // ColorModelColumn is the model column containing the - // text color of this uiTable column's text, or -1 to use the - // default color. - // - // If CellValue() for this column for any cell returns NULL, that - // cell will also use the default text color. - int ColorModelColumn; -}; - -struct uiTableParams { - // Model is the uiTableModel to use for this uiTable. - // This parameter cannot be NULL. - uiTableModel *Model; - // RowBackgroundColorModelColumn is a model column - // number that defines the background color used for the - // entire row in the uiTable, or -1 to use the default color for - // all rows. - // - // If CellValue() for this column for any row returns NULL, that - // row will also use the default background color. - int RowBackgroundColorModelColumn; -}; - -// uiTable is a uiControl that shows tabular data, allowing users to -// manipulate rows of such data at a time. -typedef struct uiTable uiTable; -#define uiTable(this) ((uiTable *) (this)) - -// uiTableAppendTextColumn() appends a text column to t. -// name is displayed in the table header. -// textModelColumn is where the text comes from. -// If a row is editable according to textEditableModelColumn, -// SetCellValue() is called with textModelColumn as the column. -_UI_EXTERN void uiTableAppendTextColumn(uiTable *t, - const char *name, - int textModelColumn, - int textEditableModelColumn, - uiTableTextColumnOptionalParams *textParams); - -// uiTableAppendImageColumn() appends an image column to t. -// Images are drawn at icon size, appropriate to the pixel density -// of the screen showing the uiTable. -_UI_EXTERN void uiTableAppendImageColumn(uiTable *t, - const char *name, - int imageModelColumn); - -// uiTableAppendImageTextColumn() appends a column to t that -// shows both an image and text. -_UI_EXTERN void uiTableAppendImageTextColumn(uiTable *t, - const char *name, - int imageModelColumn, - int textModelColumn, - int textEditableModelColumn, - uiTableTextColumnOptionalParams *textParams); - -// uiTableAppendCheckboxColumn appends a column to t that -// contains a checkbox that the user can interact with (assuming the -// checkbox is editable). SetCellValue() will be called with -// checkboxModelColumn as the column in this case. -_UI_EXTERN void uiTableAppendCheckboxColumn(uiTable *t, - const char *name, - int checkboxModelColumn, - int checkboxEditableModelColumn); - -// uiTableAppendCheckboxTextColumn() appends a column to t -// that contains both a checkbox and text. -_UI_EXTERN void uiTableAppendCheckboxTextColumn(uiTable *t, - const char *name, - int checkboxModelColumn, - int checkboxEditableModelColumn, - int textModelColumn, - int textEditableModelColumn, - uiTableTextColumnOptionalParams *textParams); - -// uiTableAppendProgressBarColumn() appends a column to t -// that displays a progress bar. These columns work like -// uiProgressBar: a cell value of 0..100 displays that percentage, and -// a cell value of -1 displays an indeterminate progress bar. -_UI_EXTERN void uiTableAppendProgressBarColumn(uiTable *t, - const char *name, - int progressModelColumn); - -// uiTableAppendButtonColumn() appends a column to t -// that shows a button that the user can click on. When the user -// does click on the button, SetCellValue() is called with a NULL -// value and buttonModelColumn as the column. -// CellValue() on buttonModelColumn should return the text to show -// in the button. -_UI_EXTERN void uiTableAppendButtonColumn(uiTable *t, - const char *name, - int buttonModelColumn, - int buttonClickableModelColumn); - -// uiNewTable() creates a new uiTable with the specified parameters. -_UI_EXTERN uiTable *uiNewTable(uiTableParams *params);