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/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/_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(?) 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/ diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 2d7d08d9..0e1360d8 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -7,10 +7,11 @@ 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/tablemodel.c + common/tablevalue.c common/userbugs.c common/utf.c ) diff --git a/common/OLD_table.c b/common/OLD_table.c new file mode 100644 index 00000000..3726883a --- /dev/null +++ b/common/OLD_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/common/table.h b/common/table.h new file mode 100644 index 00000000..f83205df --- /dev/null +++ b/common/table.h @@ -0,0 +1,20 @@ +// 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); +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 +} +#endif diff --git a/common/tablemodel.c b/common/tablemodel.c new file mode 100644 index 00000000..dbda4a81 --- /dev/null +++ b/common/tablemodel.c @@ -0,0 +1,79 @@ +// 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); +} + +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; +} + +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/common/tablevalue.c b/common/tablevalue.c new file mode 100644 index 00000000..10043de6 --- /dev/null +++ b/common/tablevalue.c @@ -0,0 +1,106 @@ +// 3 june 2018 +#include "../ui.h" +#include "uipriv.h" +#include "table.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/CMakeLists.txt b/darwin/CMakeLists.txt index bbab3c9a..bd7d576a 100644 --- a/darwin/CMakeLists.txt +++ b/darwin/CMakeLists.txt @@ -43,6 +43,8 @@ list(APPEND _LIBUI_SOURCES darwin/spinbox.m 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/OLD_table.m b/darwin/OLD_table.m new file mode 100644 index 00000000..18231f55 --- /dev/null +++ b/darwin/OLD_table.m @@ -0,0 +1,39 @@ +// 21 june 2016 +#import "uipriv_darwin.h" + +// TODOs +// - header cell seems off +// - background color shows up for a line or two below selection +// - editable NSTextFields have no intrinsic width +// - is the Y position of checkbox cells correct? + +@implementation tablePart + +- (NSView *)mkView:(uiTableModel *)m row:(int)row +{ + // if stretchy, don't hug, otherwise hug forcibly + if (self.expand) + [view setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal]; + else + [view setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; +} + +@end + +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; +} diff --git a/darwin/image.m b/darwin/image.m index ae5be6d9..a4b322c7 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; @@ -30,7 +30,7 @@ void Image(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; @@ -40,11 +40,11 @@ void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, in // 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++) { + for (y = 0; y < pixelHeight; y++){ + for (x = 0; x < pixelWidth; x++) { sp[0] = bp[2]; sp[1] = bp[1]; sp[2] = bp[0]; @@ -52,6 +52,9 @@ void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, in sp += 4; bp += 4; } + // jump over unused bytes at end of line + bp += byteStride - pixelWidth * 4; + } pix[0] = (unsigned char *) swizzled; repCalibrated = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:pix @@ -63,14 +66,15 @@ 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]]; - [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]]; diff --git a/darwin/table.h b/darwin/table.h new file mode 100644 index 00000000..4146ab71 --- /dev/null +++ b/darwin/table.h @@ -0,0 +1,27 @@ +// 3 june 2018 +#import "../common/table.h" + +// 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; + uiTableModel *m; +}; + +// tablecolumn.m +@interface uiprivTableCellView : NSTableCellView +- (void)uiprivUpdate:(NSInteger)row; +@end +@interface uiprivTableColumn : NSTableColumn +- (uiprivTableCellView *)uiprivMakeCellView; +@end diff --git a/darwin/table.m b/darwin/table.m new file mode 100644 index 00000000..1bdc7f8d --- /dev/null +++ b/darwin/table.m @@ -0,0 +1,218 @@ +// 3 june 2018 +#import "uipriv_darwin.h" +#import "table.h" + +// TODO is the initial scroll position still wrong? + +@interface uiprivTableModel : NSObject { + uiTableModel *m; +} +- (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) +{ + NSColor *color; + double r, g, b, a; + + if (t->uiprivT->backgroundColumn == -1) + 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 { + 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 +{ + self = [super init]; + if (self) + self->m = model; + return self; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tv +{ + return uiprivTableModelNumRows(self->m); +} + + - (NSView *)tableView:(NSTableView *)tv viewForTableColumn:(NSTableColumn *)cc row:(NSInteger)row +{ + uiprivTableColumn *c = (uiprivTableColumn *) cc; + uiprivTableCellView *cv; + + cv = (uiprivTableCellView *) [tv makeViewWithIdentifier:[c identifier] owner:self]; + if (cv == nil) + cv = [c uiprivMakeCellView]; + [cv uiprivUpdate:row]; + return cv; +} + +- (void)tableView:(NSTableView *)tv didAddRowView:(NSTableRowView *)rv forRow:(NSInteger)row +{ + setBackgroundColor((uiprivTableView *) tv, rv, row); +} + +@end + +uiTableModel *uiNewTableModel(uiTableModelHandler *mh) +{ + uiTableModel *m; + + m = uiprivNew(uiTableModel); + m->mh = mh; + m->m = [[uiprivTableModel alloc] initWithModel:m]; + m->tables = [NSMutableArray new]; + return m; +} + +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) +{ + uiprivTableView *tv; + NSTableRowView *rv; + NSUInteger i, n; + uiprivTableCellView *cv; + + for (tv in m->tables) { + rv = [tv rowViewAtRow:index makeIfNecessary:NO]; + 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]; + if (cv != nil) + [cv uiprivUpdate:index]; + } + } +} + +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 +} + +uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m) +{ + return m->mh; +} + +uiDarwinControlAllDefaultsExceptDestroy(uiTable, sv) + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + + [t->m->tables removeObject:t->tv]; + uiprivScrollViewFreeData(t->sv, t->d); + [t->tv release]; + [t->sv release]; + uiFreeControl(uiControl(t)); +} + +uiTable *uiNewTable(uiTableParams *p) +{ + uiTable *t; + uiprivScrollViewCreateParams sp; + + uiDarwinNewControl(uiTable, t); + t->m = p->Model; + t->backgroundColumn = p->RowBackgroundColorModelColumn; + + t->tv = [[uiprivTableView alloc] initWithFrame:NSZeroRect uiprivT:t uiprivM:t->m]; + + [t->tv setDataSource:t->m->m]; + [t->tv setDelegate:t->m->m]; + [t->tv reloadData]; + [t->m->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(&sp, 0, sizeof (uiprivScrollViewCreateParams)); + sp.DocumentView = t->tv; + // this is what Interface Builder sets it to + // TODO verify + 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]; + + return t; +} diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m new file mode 100644 index 00000000..5038cc6b --- /dev/null +++ b/darwin/tablecolumn.m @@ -0,0 +1,720 @@ +// 3 june 2018 +#import "uipriv_darwin.h" +#import "table.h" + +// values from interface builder +#define textColumnLeading 2 +#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 +#define progressBarColumnLeading imageColumnLeading +#define progressBarColumnTrailing progressBarColumnLeading +#define buttonColumnLeading imageColumnLeading +#define buttonColumnTrailing buttonColumnLeading + +@implementation uiprivTableCellView + +- (void)uiprivUpdate:(NSInteger)row +{ + [self doesNotRecognizeSelector:_cmd]; +} + +@end + +@implementation uiprivTableColumn + +- (uiprivTableCellView *)uiprivMakeCellView +{ + [self doesNotRecognizeSelector:_cmd]; + return nil; // appease compiler +} + +@end + +struct textColumnCreateParams { + uiTable *t; + uiTableModel *m; + + BOOL makeTextField; + int textModelColumn; + int textEditableModelColumn; + uiTableTextColumnOptionalParams textParams; + + BOOL makeImageView; + int imageModelColumn; + + BOOL makeCheckbox; + int checkboxModelColumn; + int checkboxEditableModelColumn; +}; + +@interface uiprivTextImageCheckboxTableCellView : uiprivTableCellView { + uiTable *t; + uiTableModel *m; + + NSTextField *tf; + int textModelColumn; + int textEditableModelColumn; + uiTableTextColumnOptionalParams textParams; + + NSImageView *iv; + int imageModelColumn; + + NSButton *cb; + int checkboxModelColumn; + int checkboxEditableModelColumn; +} +- (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p; +- (IBAction)uiprivOnTextFieldAction:(id)sender; +- (IBAction)uiprivOnCheckboxAction:(id)sender; +@end + +@implementation uiprivTextImageCheckboxTableCellView + +- (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p +{ + self = [super initWithFrame:r]; + if (self) { + NSMutableArray *constraints; + + self->t = p->t; + self->m = p->m; + constraints = [NSMutableArray new]; + + self->tf = nil; + if (p->makeTextField) { + self->textModelColumn = p->textModelColumn; + self->textEditableModelColumn = p->textEditableModelColumn; + self->textParams = p->textParams; + + self->tf = uiprivNewLabel(@""); + // TODO set wrap and ellipsize modes? + [self->tf setTarget:self]; + [self->tf setAction:@selector(uiprivOnTextFieldAction:)]; + [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, + 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; + if (p->makeImageView) { + self->imageModelColumn = p->imageModelColumn; + + 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 setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:self->iv]; + + [constraints addObject:uiprivMkConstraint(self->iv, NSLayoutAttributeWidth, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeHeight, + 1, 0, + @"uiTable image squareness constraint")]; + 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; + if (p->makeCheckbox) { + self->checkboxModelColumn = p->checkboxModelColumn; + self->checkboxEditableModelColumn = p->checkboxEditableModelColumn; + + 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->cb setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:self->cb]; + + 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")]; + } + + [self addConstraints:constraints]; + + // take advantage of NSTableCellView-provided accessibility features + 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; + } + [super dealloc]; +} + +- (void)uiprivUpdate:(NSInteger)row +{ + uiTableValue *value; + + if (self->tf != nil) { + NSString *str; + NSColor *color; + double r, g, b, a; + + value = uiprivTableModelCellValue(self->m, row, self->textModelColumn); + str = uiprivToNSString(uiTableValueString(value)); + uiFreeTableValue(value); + [self->tf setStringValue:str]; + + [self->tf setEditable:uiprivTableModelCellEditable(self->m, row, self->textEditableModelColumn)]; + + 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 + } + if (self->iv != nil) { + uiImage *img; + + value = uiprivTableModelCellValue(self->m, row, self->imageModelColumn); + img = uiTableValueImage(value); + uiFreeTableValue(value); + [self->iv setImage:uiprivImageNSImage(img)]; + } + if (self->cb != nil) { + value = uiprivTableModelCellValue(self->m, row, self->checkboxModelColumn); + if (uiTableValueInt(value) != 0) + [self->cb setState:NSOnState]; + else + [self->cb setState:NSOffState]; + uiFreeTableValue(value); + + [self->cb setEnabled:uiprivTableModelCellEditable(self->m, row, self->checkboxEditableModelColumn)]; + } +} + +- (IBAction)uiprivOnTextFieldAction:(id)sender +{ + NSInteger row; + uiTableValue *value; + + row = [self->t->tv rowForView:self->tf]; + value = uiNewTableValueString([[self->tf stringValue] UTF8String]); + 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...) + [self uiprivUpdate:row]; +} + +- (IBAction)uiprivOnCheckboxAction:(id)sender +{ + NSInteger row; + uiTableValue *value; + + row = [self->t->tv rowForView:self->cb]; + value = uiNewTableValueInt([self->cb state] != NSOffState); + uiprivTableModelSetCellValue(self->m, row, self->checkboxModelColumn, value); + uiFreeTableValue(value); + // always refresh the value in case the model rejected it + [self uiprivUpdate:row]; +} + +@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; +} + +- (uiprivTableCellView *)uiprivMakeCellView +{ + uiprivTableCellView *cv; + + cv = [[uiprivTextImageCheckboxTableCellView alloc] initWithFrame:NSZeroRect params:&(self->params)]; + [cv setIdentifier:[self identifier]]; + return cv; +} + +@end + +@interface uiprivProgressBarTableCellView : uiprivTableCellView { + uiTable *t; + uiTableModel *m; + NSProgressIndicator *p; + int modelColumn; +} +- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc; +@end + +@implementation uiprivProgressBarTableCellView + +- (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]; + [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; +} + +- (void)dealloc +{ + [self->p release]; + self->p = nil; + [super dealloc]; +} + +- (void)uiprivUpdate:(NSInteger)row +{ + uiTableValue *value; + int progress; + + value = uiprivTableModelCellValue(self->m, row, self->modelColumn); + progress = uiTableValueInt(value); + uiFreeTableValue(value); + if (progress == -1) { + [self->p setIndeterminate:YES]; + [self->p startAnimation:self->p]; + } else if (progress == 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:(progress + 1)]; + [self->p setDoubleValue:progress]; + } +} + +@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 table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc; +@end + +@implementation uiprivProgressBarTableColumn + +- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc +{ + self = [super initWithIdentifier:ident]; + if (self) { + self->t = table; + self->m = model; + self->modelColumn = mc; + } + return self; +} + +- (uiprivTableCellView *)uiprivMakeCellView +{ + uiprivTableCellView *cv; + + cv = [[uiprivProgressBarTableCellView alloc] initWithFrame:NSZeroRect table:self->t model:self->m modelColumn:self->modelColumn]; + [cv setIdentifier:[self identifier]]; + return cv; +} + +@end + +@interface uiprivButtonTableCellView : uiprivTableCellView { + 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 uiprivButtonTableCellView + +- (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:)]; + [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; +} + +- (void)dealloc +{ + [self->b release]; + self->b = nil; + [super dealloc]; +} + +- (void)uiprivUpdate:(NSInteger)row +{ + uiTableValue *value; + NSString *str; + + value = uiprivTableModelCellValue(self->m, row, self->modelColumn); + str = uiprivToNSString(uiTableValueString(value)); + uiFreeTableValue(value); + [self->b setTitle:str]; + + [self->b setEnabled:uiprivTableModelCellEditable(self->m, row, self->editableColumn)]; +} + +- (IBAction)uiprivOnClicked:(id)sender +{ + NSInteger row; + + 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 + +@interface uiprivButtonTableColumn : uiprivTableColumn { + uiTable *t; + uiTableModel *m; + int modelColumn; + int editableColumn; +} +- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec; +@end + +@implementation uiprivButtonTableColumn + +- (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; +} + +- (uiprivTableCellView *)uiprivMakeCellView +{ + uiprivTableCellView *cv; + + cv = [[uiprivButtonTableCellView alloc] initWithFrame:NSZeroRect table:self->t model:self->m modelColumn:self->modelColumn editableColumn:self->editableColumn]; + [cv setIdentifier:[self identifier]]; + return cv; +} + +@end + +void uiTableAppendTextColumn(uiTable *t, const char *name, 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 (textParams != NULL) + p.textParams = *textParams; + else + p.textParams = uiprivDefaultTextColumnOptionalParams; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + [t->tv addTableColumn: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.makeImageView = YES; + p.imageModelColumn = imageModelColumn; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + [t->tv addTableColumn: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 (textParams != NULL) + p.textParams = *textParams; + else + p.textParams = uiprivDefaultTextColumnOptionalParams; + + p.makeImageView = YES; + p.imageModelColumn = imageModelColumn; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + [t->tv addTableColumn: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.checkboxEditableModelColumn = checkboxEditableModelColumn; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + [t->tv addTableColumn: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 (textParams != NULL) + p.textParams = *textParams; + else + p.textParams = uiprivDefaultTextColumnOptionalParams; + + p.makeCheckbox = YES; + p.checkboxModelColumn = checkboxModelColumn; + p.checkboxEditableModelColumn = checkboxEditableModelColumn; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + [t->tv addTableColumn:col]; +} + +void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) +{ + uiprivTableColumn *col; + NSString *str; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivProgressBarTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:progressModelColumn]; + [col setTitle:str]; + [t->tv addTableColumn:col]; +} + +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:buttonModelColumn editableColumn:buttonClickableModelColumn]; + [col setTitle:str]; + [t->tv addTableColumn:col]; +} 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/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; +} 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); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c2f9c1a9..621b43e9 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/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/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/main.c b/test/main.c index aa38784f..2f66826f 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); @@ -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 new file mode 100644 index 00000000..f28ba3c7 --- /dev/null +++ b/test/page16.c @@ -0,0 +1,163 @@ +// 21 june 2016 +#include "test.h" + +static uiTableModelHandler mh; + +static int modelNumColumns(uiTableModelHandler *mh, uiTableModel *m) +{ + return 9; +} + +static uiTableValueType modelColumnType(uiTableModelHandler *mh, uiTableModel *m, int column) +{ + if (column == 3 || column == 4) + return uiTableValueTypeColor; + if (column == 5) + return uiTableValueTypeImage; + if (column == 7 || column == 8) + return uiTableValueTypeInt; + return uiTableValueTypeString; +} + +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 uiTableValue *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col) +{ + char buf[256]; + + if (col == 3) { + if (row == yellowRow) + return uiNewTableValueColor(1, 1, 0, 1); + if (row == 3) + return uiNewTableValueColor(1, 0, 0, 1); + if (row == 11) + return uiNewTableValueColor(0, 0.5, 1, 0.5); + return NULL; + } + if (col == 4) { + if ((row % 2) == 1) + return uiNewTableValueColor(0.5, 0, 0.75, 1); + return NULL; + } + if (col == 5) { + if (row < 8) + return uiNewTableValueImage(img[0]); + return uiNewTableValueImage(img[1]); + } + if (col == 7) + return uiNewTableValueInt(checkStates[row]); + if (col == 8) { + if (row == 0) + return uiNewTableValueInt(0); + if (row == 13) + return uiNewTableValueInt(100); + if (row == 14) + return uiNewTableValueInt(-1); + return uiNewTableValueInt(50); + } + switch (col) { + case 0: + sprintf(buf, "Row %d", row); + break; + case 2: + if (row == 9) + return uiNewTableValueString(row9text); + // fall through + case 1: + strcpy(buf, "Part"); + break; + case 6: + strcpy(buf, "Make Yellow"); + break; + } + return uiNewTableValueString(buf); +} + +static void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col, const uiTableValue *val) +{ + if (row == 9 && col == 2) + strcpy(row9text, uiTableValueString(val)); + if (col == 6) { + int prevYellowRow; + + prevYellowRow = yellowRow; + yellowRow = row; + if (prevYellowRow != -1) + uiTableModelRowChanged(m, prevYellowRow); + uiTableModelRowChanged(m, yellowRow); + } + if (col == 7) + checkStates[row] = uiTableValueInt(val); +} + +static uiTableModel *m; + +uiBox *makePage16(void) +{ + uiBox *page16; + uiTable *t; + uiTableParams p; + uiTableTextColumnOptionalParams tp; + + 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); + + 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(&tp, 0, sizeof (uiTableTextColumnOptionalParams)); + tp.ColorModelColumn = 4; + uiTableAppendImageTextColumn(t, "Column 2", + 5, + 1, uiTableModelColumnNeverEditable, &tp); + uiTableAppendTextColumn(t, "Editable", + 2, uiTableModelColumnAlwaysEditable, NULL); + + uiTableAppendCheckboxColumn(t, "Checkboxes", + 7, uiTableModelColumnAlwaysEditable); + uiTableAppendButtonColumn(t, "Buttons", + 6, uiTableModelColumnAlwaysEditable); + + uiTableAppendProgressBarColumn(t, "Progress Bar", + 8); + + return page16; +} + +void freePage16(void) +{ + uiFreeTableModel(m); + uiFreeImage(img[1]); + uiFreeImage(img[0]); +} diff --git a/test/test.h b/test/test.h index 66b1baa7..42ec9314 100644 --- a/test/test.h +++ b/test/test.h @@ -89,3 +89,10 @@ extern uiTab *makePage14(void); // page15.c extern uiBox *makePage15(uiWindow *); + +// page16.c +extern uiBox *makePage16(void); +extern void freePage16(void); + +// images.c +extern void appendImageNamed(uiImage *img, const char *name); diff --git a/ui.h b/ui.h index b5fb9a27..253cd2f0 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 @@ -1123,6 +1124,337 @@ _UI_EXTERN int uiGridPadded(uiGrid *g); _UI_EXTERN void uiGridSetPadded(uiGrid *g, int padded); _UI_EXTERN uiGrid *uiNewGrid(void); +// 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 } #endif diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 78873146..5c9425d2 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -43,6 +43,8 @@ list(APPEND _LIBUI_SOURCES unix/spinbox.c 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/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/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; 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/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; diff --git a/unix/table.c b/unix/table.c new file mode 100644 index 00000000..d1e6fe60 --- /dev/null +++ b/unix/table.c @@ -0,0 +1,522 @@ +// 26 june 2016 +#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; + GtkContainer *scontainer; + GtkScrolledWindow *sw; + GtkWidget *treeWidget; + GtkTreeView *tv; + 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 +// 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 *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(m, 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 setEditable(uiTableModel *m, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop) +{ + GtkTreePath *path; + int row; + gboolean editable; + + // 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); +} + +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(uiTableModel *m, int column, const char *pathstr, const uiTableValue *tvalue, GtkTreeIter *iter) +{ + GtkTreePath *path; + int row; + + path = gtk_tree_path_new_from_string(pathstr); + row = gtk_tree_path_get_indices(path)[0]; + if (iter != NULL) + gtk_tree_model_get_iter(GTK_TREE_MODEL(m), iter, path); + gtk_tree_path_free(path); + uiprivTableModelSetCellValue(m, row, column, tvalue); +} + +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; + + 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(p->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 *r, gchar *path, gchar *newText, gpointer data) +{ + struct textColumnParams *p = (struct textColumnParams *) data; + uiTableValue *tvalue; + GtkTreeIter iter; + + 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); +} + +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(p->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; + uiTableValue *tvalue; + GtkTreePath *path; + GtkTreeIter iter; + + 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); + 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); +} + +struct progressBarColumnParams { + uiTable *t; + 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) { + 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); + 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, + NULL); + } + g_value_unset(&value); + + applyBackgroundColor(p->t, m, iter, r); +} + +struct buttonColumnParams { + uiTable *t; + uiTableModel *m; + int modelColumn; + int clickableColumn; +}; + +static void buttonColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + struct buttonColumnParams *p = (struct buttonColumnParams *) data; + GValue value = G_VALUE_INIT; + const gchar *str; + + 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(p->m, iter, p->clickableColumn, r, "sensitive"); + + applyBackgroundColor(p->t, m, iter, r); +} + +// TODO wrong type here +static void buttonColumnClicked(GtkCellRenderer *r, gchar *pathstr, gpointer data) +{ + struct buttonColumnParams *p = (struct buttonColumnParams *) data; + + onEdited(p->m, p->modelColumn, pathstr, 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 *textParams) +{ + struct textColumnParams *p; + GtkCellRenderer *r; + + p = uiprivNew(struct textColumnParams); + p->t = t; + // 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 (textParams != NULL) + p->params = *textParams; + else + p->params = uiprivDefaultTextColumnOptionalParams; + + r = gtk_cell_renderer_text_new(); + 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(t->columnParams, p); +} + +// TODO rename modelCOlumn and params everywhere +void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + GtkTreeViewColumn *c; + + c = addColumn(t, name); + addTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams); +} + +static void addImageColumn(uiTable *t, GtkTreeViewColumn *c, int imageModelColumn) +{ + struct imageColumnParams *p; + GtkCellRenderer *r; + + p = uiprivNew(struct imageColumnParams); + p->t = t; + p->modelColumn = imageModelColumn; + + 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(t->columnParams, p); +} + +void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) +{ + 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 addCheckboxColumn(uiTable *t, GtkTreeViewColumn *c, int checkboxModelColumn, int checkboxEditableModelColumn) +{ + struct checkboxColumnParams *p; + GtkCellRenderer *r; + + p = uiprivNew(struct checkboxColumnParams); + p->t = t; + 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), p); + g_ptr_array_add(t->columnParams, p); +} + +void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) +{ + GtkTreeViewColumn *c; + + c = addColumn(t, name); + addCheckboxColumn(t, c, checkboxModelColumn, checkboxEditableModelColumn); +} + +void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + GtkTreeViewColumn *c; + + c = addColumn(t, name); + addCheckboxColumn(t, c, checkboxModelColumn, checkboxEditableModelColumn); + addTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams); +} + +void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) +{ + GtkTreeViewColumn *c; + struct progressBarColumnParams *p; + GtkCellRenderer *r; + + 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(t->columnParams, p); +} + +void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn) +{ + GtkTreeViewColumn *c; + struct buttonColumnParams *p; + GtkCellRenderer *r; + + c = addColumn(t, name); + + p = uiprivNew(struct buttonColumnParams); + p->t = t; + p->m = t->model; + 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(t->columnParams, p); +} + +uiUnixControlAllDefaultsExceptDestroy(uiTable) + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + guint i; + + 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)); +} + +uiTable *uiNewTable(uiTableParams *p) +{ + uiTable *t; + + uiUnixNewControl(uiTable, t); + + t->model = p->Model; + t->columnParams = g_ptr_array_new(); + t->backgroundColumn = p->RowBackgroundColorModelColumn; + + 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); + + t->indeterminatePositions = g_hash_table_new_full(rowcolHash, rowcolEqual, + uiprivFree, uiprivFree); + + return t; +} diff --git a/unix/table.h b/unix/table.h new file mode 100644 index 00000000..af7e954a --- /dev/null +++ b/unix/table.h @@ -0,0 +1,19 @@ +// 4 june 2018 +#include "../common/table.h" + +// 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..ee957b58 --- /dev/null +++ b/unix/tablemodel.c @@ -0,0 +1,288 @@ +// 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 uiprivTableModelNumColumns(m); +} + +static GType uiTableModel_get_column_type(GtkTreeModel *mm, gint index) +{ + uiTableModel *m = uiTableModel(mm); + + switch (uiprivTableModelColumnType(m, index)) { + case uiTableValueTypeString: + return G_TYPE_STRING; + case uiTableValueTypeImage: + return G_TYPE_POINTER; + case uiTableValueTypeInt: + return G_TYPE_INT; + case uiTableValueTypeColor: + 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 >= uiprivTableModelNumRows(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; + uiTableValue *tvalue; + double r, g, b, a; + GdkRGBA rgba; + + if (iter->stamp != STAMP_GOOD) + return; + row = GPOINTER_TO_INT(iter->user_data); + 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)); + uiFreeTableValue(tvalue); + return; + case uiTableValueTypeImage: + g_value_init(value, G_TYPE_POINTER); + g_value_set_pointer(value, uiTableValueImage(tvalue)); + uiFreeTableValue(tvalue); + return; + case uiTableValueTypeInt: + g_value_init(value, G_TYPE_INT); + g_value_set_int(value, uiTableValueInt(tvalue)); + uiFreeTableValue(tvalue); + return; + case uiTableValueTypeColor: + g_value_init(value, GDK_TYPE_RGBA); + if (tvalue == NULL) { + g_value_set_boxed(value, NULL); + return; + } + uiTableValueColor(tvalue, &r, &g, &b, &a); + uiFreeTableValue(tvalue); + 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 >= uiprivTableModelNumRows(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 uiprivTableModelNumRows(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 >= uiprivTableModelNumRows(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); +} + +uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m) +{ + return m->mh; +} 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 diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 16beefa7..ac9af411 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,11 @@ list(APPEND _LIBUI_SOURCES windows/spinbox.cpp windows/stddialogs.cpp windows/tab.cpp + windows/table.cpp + windows/tabledispinfo.cpp + windows/tabledraw.cpp + windows/tableediting.cpp + windows/tablemetrics.cpp windows/tabpage.cpp windows/text.cpp windows/utf16.cpp @@ -76,7 +82,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 new file mode 100644 index 00000000..368c77fa --- /dev/null +++ b/windows/image.cpp @@ -0,0 +1,226 @@ +#include "uipriv_windows.hpp" + +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; + std::vector *bitmaps; +}; + +uiImage *uiNewImage(double width, double height) +{ + uiImage *i; + + 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 byteStride) +{ + IWICBitmap *b; + HRESULT hr; + + hr = uiprivWICFactory->CreateBitmapFromMemory(pixelWidth, pixelHeight, + GUID_WICPixelFormat32bppRGBA, byteStride, + byteStride * 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; + // 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); + return m.best; +} + +// 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; + IWICBitmapSource *src; + BITMAPINFO bmi; + VOID *bits; + 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; + WICPixelFormatGUID guid; + IWICFormatConverter *conv; + + 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; + } + + // 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)); + bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + 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"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 + // TODO fill in the error returns here too + if (GetObject(*hb, sizeof (BITMAP), &bmp) == 0) + logLastError(L"error calling GetObject() in uiprivWICToGDI()"); + hr = src->CopyPixels(NULL, bmp.bmWidthBytes, + bmp.bmWidthBytes * bmp.bmHeight, (BYTE *) bits); + +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/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 new file mode 100644 index 00000000..85a51c1a --- /dev/null +++ b/windows/table.cpp @@ -0,0 +1,522 @@ +#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? +// - 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) +{ + uiTableModel *m; + + m = uiprivNew(uiTableModel); + m->mh = mh; + m->tables = new std::vector; + return m; +} + +void uiFreeTableModel(uiTableModel *m) +{ + delete m->tables; + 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 = uiprivTableModelNumRows(m); + ZeroMemory(&item, sizeof (LVITEMW)); + item.mask = 0; + item.iItem = newIndex; + item.iSubItem = 0; + 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() to update selection state"); + } +} + +// TODO compare LVM_UPDATE and LVM_REDRAWITEMS +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()"); +} + +// 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) +{ + int newCount; + + newCount = uiprivTableModelNumRows(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() 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()"); + } +} + +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) +{ + 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))) { + t->inDoubleClickTimer = FALSE; + // TODO check errors + KillTimer(hwnd, wParam); + return 0; + } + if (wParam != (WPARAM) t) + break; + // 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); + } + return 0; + case WM_LBUTTONDOWN: + t->inLButtonDown = TRUE; + 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 + hr = uiprivTableResizeWhileEditing(t); + if (hr != S_OK) { + // TODO + } + break; + } + // 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); +} + +int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos) +{ + uiTableValue *value; + int progress; + std::pair p; + std::map, LONG>::iterator iter; + bool startTimer = false; + bool stopTimer = false; + + value = uiprivTableModelCellValue(t->model, item, modelColumn); + progress = uiTableValueInt(value); + uiFreeTableValue(value); + + 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()"); + + return progress; +} + +// TODO properly integrate compound statements +static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) +{ + uiTable *t = uiTable(c); + HRESULT hr; + + switch (nmhdr->code) { + case LVN_GETDISPINFO: + 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); + if (hr != S_OK) { + // TODO + return FALSE; + } + return TRUE; + case NM_CLICK: +#if 0 + { + 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); + } + } + *lResult = 0; + return TRUE; +#else + hr = uiprivTableHandleNM_CLICK(t, (NMITEMACTIVATE *) nmhdr, lResult); + if (hr != S_OK) { + // TODO + return FALSE; + } + return TRUE; +#endif + case LVN_ITEMCHANGED: + { + NMLISTVIEW *nm = (NMLISTVIEW *) nmhdr; + UINT oldSelected, newSelected; + HRESULT hr; + + // 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 (t->inLButtonDown && oldSelected == 0 && newSelected != 0) { + t->inDoubleClickTimer = TRUE; + // TODO check error + SetTimer(t->hwnd, (UINT_PTR) (&(t->inDoubleClickTimer)), + GetDoubleClickTime(), NULL); + *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; +} + +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 + 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); + delete t->columns; + // t->imagelist will be automatically destroyed + delete t->indeterminatePositions; + 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 +// TODO investigate using LVM_APPROXIMATEVIEWRECT 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 uiprivTableColumnParams *appendColumn(uiTable *t, const char *name, int colfmt) +{ + WCHAR *wstr; + LVCOLUMNW lvc; + uiprivTableColumnParams *p; + + ZeroMemory(&lvc, sizeof (LVCOLUMNW)); + 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(uiprivTableColumnParams); + p->textModelColumn = -1; + p->textEditableModelColumn = -1; + p->textParams = uiprivDefaultTextColumnOptionalParams; + p->imageModelColumn = -1; + p->checkboxModelColumn = -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 *textParams) +{ + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->textModelColumn = textModelColumn; + p->textEditableModelColumn = textEditableModelColumn; + if (textParams != NULL) + p->textParams = *textParams; +} + +void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) +{ + 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) +{ + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->textModelColumn = textModelColumn; + p->textEditableModelColumn = textEditableModelColumn; + if (textParams != NULL) + p->textParams = *textParams; + p->imageModelColumn = imageModelColumn; +} + +void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) +{ + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->checkboxModelColumn = checkboxModelColumn; + p->checkboxEditableModelColumn = checkboxEditableModelColumn; +} + +void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->textModelColumn = textModelColumn; + p->textEditableModelColumn = textEditableModelColumn; + if (textParams != NULL) + p->textParams = *textParams; + p->checkboxModelColumn = checkboxModelColumn; + p->checkboxEditableModelColumn = checkboxEditableModelColumn; +} + +void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) +{ + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->progressBarModelColumn = progressModelColumn; +} + +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 = buttonModelColumn; + p->buttonClickableModelColumn = buttonClickableModelColumn; +} + +uiTable *uiNewTable(uiTableParams *p) +{ + uiTable *t; + int n; + HRESULT hr; + + uiWindowsNewControl(uiTable, t); + + t->columns = new std::vector; + 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); + t->model->tables->push_back(t); + 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)); + n = uiprivTableModelNumRows(t->model); + if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0) + logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()"); + + hr = uiprivUpdateImageListSize(t); + if (hr != S_OK) { + // 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 new file mode 100644 index 00000000..71946d62 --- /dev/null +++ b/windows/table.hpp @@ -0,0 +1,81 @@ +// 10 june 2018 +#include "../common/table.h" + +// table.cpp +#define uiprivNumLVN_GETDISPINFOSkip 3 +struct uiTableModel { + uiTableModelHandler *mh; + std::vector *tables; +}; +typedef struct uiprivTableColumnParams uiprivTableColumnParams; +struct uiprivTableColumnParams { + int textModelColumn; + int textEditableModelColumn; + uiTableTextColumnOptionalParams textParams; + + int imageModelColumn; + + int checkboxModelColumn; + int checkboxEditableModelColumn; + + int progressBarModelColumn; + + int buttonModelColumn; + int buttonClickableModelColumn; +}; +struct uiTable { + uiWindowsControl c; + uiTableModel *model; + HWND hwnd; + std::vector *columns; + WPARAM nColumns; + int backgroundColumn; + // TODO make sure replacing images while selected in the listview is even allowed + HIMAGELIST imagelist; + // 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; + int editedSubitem; +}; +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); + +// tabledraw.cpp +extern HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult); +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); + +// 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/tabledispinfo.cpp b/windows/tabledispinfo.cpp new file mode 100644 index 00000000..4198a1a2 --- /dev/null +++ b/windows/tabledispinfo.cpp @@ -0,0 +1,99 @@ +// 13 june 2018 +#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; + uiTableValue *value; + WCHAR *wstr; + int progress; + HRESULT hr; + + if ((nm->item.mask & LVIF_TEXT) == 0) + return S_OK; + + strcol = -1; + if (p->textModelColumn != -1) + strcol = p->textModelColumn; + else if (p->buttonModelColumn != -1) + strcol = p->buttonModelColumn; + if (strcol != -1) { + value = uiprivTableModelCellValue(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 + // 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) { + 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 + // 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; +} + +static HRESULT handleLVIF_IMAGE(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) +{ + uiTableValue *value; + 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 value + 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/tabledraw.cpp b/windows/tabledraw.cpp new file mode 100644 index 00000000..eb50f28a --- /dev/null +++ b/windows/tabledraw.cpp @@ -0,0 +1,739 @@ +// 14 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +// TODOs: +// - properly hide selection when not focused (or switch on LVS_SHOWSELALWAYS and draw that state) + +// TODO maybe split this into item and subitem structs? +struct drawState { + uiTable *t; + uiTableModel *model; + uiprivTableColumnParams *p; + + HDC dc; + int iItem; + int iSubItem; + + uiprivTableMetrics *m; + + COLORREF bgColor; + HBRUSH bgBrush; + BOOL freeBgBrush; + COLORREF textColor; + HBRUSH textBrush; + BOOL freeTextBrush; +}; + +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"); + return E_FAIL; + } + if (FillRect(s->dc, &(s->m->realTextBackground), s->bgBrush) == 0) { + logLastError(L"FillRect()"); + return E_FAIL; + } + return S_OK; +} + +static void centerImageRect(RECT *image, RECT *space) +{ + 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; +} + +static HRESULT drawImagePart(HRESULT hr, struct drawState *s) +{ + uiTableValue *value; + IWICBitmap *wb; + HBITMAP b; + RECT r; + UINT fStyle; + + if (hr != S_OK) + return hr; + if (s->p->imageModelColumn == -1) + return S_OK; + + value = uiprivTableModelCellValue(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) + return hr; + // TODO rewrite this condition to make more sense; possibly swap the if and else blocks too + // TODO proper cleanup + 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(s->t->imagelist, b, NULL) == -1) { + logLastError(L"ImageList_Add()"); + return E_FAIL; + } + // TODO error check + DeleteObject(b); + + 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->m->selected) + fStyle = ILD_SELECTED; + if (ImageList_Draw(s->t->imagelist, 0, + s->dc, r.left, r.top, fStyle) == 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 HRESULT drawUnthemedCheckbox(struct drawState *s, int checked, int enabled) +{ + RECT r; + UINT state; + + 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); + 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->m->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->m->subitemIcon; + r.right = r.left + size.cx; + r.bottom = r.top + size.cy; + + centerImageRect(&r, &(s->m->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(HRESULT hr, struct drawState *s) +{ + uiTableValue *value; + int checked, enabled; + HTHEME theme; + + if (hr != S_OK) + return hr; + if (s->p->checkboxModelColumn == -1) + return S_OK; + + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxModelColumn); + checked = uiTableValueInt(value); + uiFreeTableValue(value); + enabled = uiprivTableModelCellEditable(s->model, s->iItem, s->p->checkboxEditableModelColumn); + + 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(HRESULT hr, struct drawState *s) +{ + COLORREF prevText; + int prevMode; + RECT r; + uiTableValue *value; + WCHAR *wstr; + + if (hr != S_OK) + return hr; + 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) { + logLastError(L"SetTextColor()"); + return E_FAIL; + } + prevMode = SetBkMode(s->dc, TRANSPARENT); + if (prevMode == 0) { + logLastError(L"SetBkMode()"); + return E_FAIL; + } + + 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: + // 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->m->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(s->dc, prevMode) != TRANSPARENT) { + logLastError(L"SetBkMode() prev"); + return E_FAIL; + } + if (SetTextColor(s->dc, prevText) != s->textColor) { + logLastError(L"SetTextColor() prev"); + return E_FAIL; + } + return S_OK; +} + +// much of this is to imitate what shell32.dll's CDrawProgressBar does +#define indeterminateSegments 8 + +static HRESULT drawProgressBarPart(HRESULT hr, struct drawState *s) +{ + int progress; + LONG indeterminatePos; + HTHEME theme; + RECT r; + RECT rBorder, rFill[2]; + int i, nFill; + TEXTMETRICW tm; + int sysColor; + + if (hr != S_OK) + return hr; + 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"); + + if (GetTextMetricsW(s->dc, &tm) == 0) { + logLastError(L"GetTextMetricsW()"); + hr = E_FAIL; + goto fail; + } + 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; + + // TODO check errors + rBorder = r; + InflateRect(&rBorder, -1, -1); + if (theme != NULL) { + 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; + + sysColor = COLOR_HIGHLIGHT; + if (s->m->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); + } + + nFill = 1; + rFill[0] = r; + // TODO check error + 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 + // 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; + rFill[0].left += indeterminatePos % 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) { + 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 + if (theme != NULL) + CloseThemeData(theme); + return hr; +} + +static HRESULT drawButtonPart(HRESULT hr, struct drawState *s) +{ + uiTableValue *value; + WCHAR *wstr; + bool enabled; + HTHEME theme; + RECT r; + TEXTMETRICW tm; + + if (hr != S_OK) + return hr; + if (s->p->buttonModelColumn == -1) + return S_OK; + + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->buttonModelColumn); + wstr = toUTF16(uiTableValueString(value)); + uiFreeTableValue(value); + enabled = uiprivTableModelCellEditable(s->model, s->iItem, s->p->buttonClickableModelColumn); + + theme = OpenThemeData(s->t->hwnd, L"button"); + + if (GetTextMetricsW(s->dc, &tm) == 0) { + logLastError(L"GetTextMetricsW()"); + hr = E_FAIL; + goto fail; + } + r = s->m->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; + + hrret = S_OK; + if (s->m != NULL) { + uiprivFree(s->m); + s->m = NULL; + } + if (s->freeTextBrush) { + if (DeleteObject(s->textBrush) == 0) { + logLastError(L"DeleteObject()"); + hrret = E_FAIL; + // continue cleaning up anyway + } + s->freeTextBrush = FALSE; + } + if (s->freeBgBrush) { + if (DeleteObject(s->bgBrush) == 0) { + logLastError(L"DeleteObject()"); + hrret = E_FAIL; + // continue cleaning up anyway + } + s->freeBgBrush = FALSE; + } + return hrret; +} + +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; + HWND header; + HRESULT hr; + + ZeroMemory(s, sizeof (struct drawState)); + s->t = t; + s->model = t->model; + s->p = p; + + s->dc = nm->nmcd.hdc; + s->iItem = nm->nmcd.dwItemSpec; + s->iSubItem = nm->iSubItem; + + hr = uiprivTableGetMetrics(t, s->iItem, s->iSubItem, &(s->m)); + if (hr != S_OK) + goto fail; + + if (s->m->selected) { + s->bgColor = GetSysColor(COLOR_HIGHLIGHT); + s->bgBrush = GetSysColorBrush(COLOR_HIGHLIGHT); + s->textColor = GetSysColor(COLOR_HIGHLIGHTTEXT); + s->textBrush = GetSysColorBrush(COLOR_HIGHLIGHTTEXT); + } else { + double r, g, b, a; + + s->bgColor = GetSysColor(COLOR_WINDOW); + s->bgBrush = GetSysColorBrush(COLOR_WINDOW); + 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 (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; + } + } + + return S_OK; +fail: + // ignore the error; we need to return the one we got above + freeDrawState(s); + return hr; +} + +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 + // TODO only call this once + state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, (WPARAM) iItem, (LRESULT) (LVIS_FOCUSED)); + if ((state & LVIS_FOCUSED) == 0) + return S_OK; + + if (realTextBackground != NULL) + if (*first) { + *focus = *realTextBackground; + *first = false; + return S_OK; + } else if (focus->right == realTextBackground->left) { + focus->right = realTextBackground->right; + return S_OK; + } + if (DrawFocusRect(dc, focus) == 0) { + logLastError(L"DrawFocusRect()"); + return E_FAIL; + } + if (realTextBackground != NULL) + *focus = *realTextBackground; + 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? +HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult) +{ + struct drawState s; + uiprivTableColumnParams *p; + NMLVCUSTOMDRAW b; + size_t i, n; + RECT focus; + bool focusFirst; + HRESULT hr; + + switch (nm->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + *lResult = CDRF_NOTIFYITEMDRAW; + return S_OK; + case CDDS_ITEMPREPAINT: + break; + default: + *lResult = CDRF_DODEFAULT; + return S_OK; + } + + n = t->columns->size(); + b = *nm; + 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_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); + if (hr != S_OK) // TODO really error out here? + return hr; + } + // and draw the last focus rect + hr = updateAndDrawFocusRects(hr, t, nm->nmcd.hdc, nm->nmcd.dwItemSpec, NULL, &focus, &focusFirst); + if (hr != S_OK) + 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; +} + +// 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; + hr = CloseThemeData(theme); + if (hr != S_OK) { + logHRESULT(L"CloseThemeData()", hr); + return hr; + } + } + + // TODO handle errors + t->imagelist = ImageList_Create(cxList, cyList, + ILC_COLOR32, + 1, 1); + 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->imagelist)); + + if (ReleaseDC(t->hwnd, dc) == 0) { + logLastError(L"ReleaseDC()"); + return E_FAIL; + } + return S_OK; +} diff --git a/windows/tableediting.cpp b/windows/tableediting.cpp new file mode 100644 index 00000000..7bbb450c --- /dev/null +++ b/windows/tableediting.cpp @@ -0,0 +1,262 @@ +// 17 june 2018 +#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) +{ + uiprivTableMetrics *m; + RECT r; + HDC dc; + HFONT prevFont; + TEXTMETRICW tm; + SIZE textSize; + RECT editRect, clientRect; + 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 + 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; + + // 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, + 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) +{ + 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) +{ + uiTableValue *value; + WCHAR *wstr; + 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; + + // the real list view creates the edit control with the string + value = uiprivTableModelCellValue(t->model, iItem, p->textModelColumn); + wstr = toUTF16(uiTableValueString(value)); + uiFreeTableValue(value); + // 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; + } + SendMessageW(t->edit, WM_SETFONT, (WPARAM) hMessageFont, (LPARAM) TRUE); + // TODO check errors + SetWindowSubclass(t->edit, editSubProc, 0, (DWORD_PTR) t); + + hr = resizeEdit(t, wstr, iItem, iSubItem); + 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; + t->editedSubitem = iSubItem; + 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) +{ + uiprivTableColumnParams *p; + uiTableValue *value; + char *text; + + if (t->edit == NULL) + return S_OK; + text = uiWindowsWindowText(t->edit); + value = uiNewTableValueString(text); + uiFreeText(text); + p = (*(t->columns))[t->editedSubitem]; + 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)) { + logLastError(L"LVM_UPDATE"); + return E_FAIL; + } + return uiprivTableAbortEditingText(t); +} + +HRESULT uiprivTableAbortEditingText(uiTable *t) +{ + HWND edit; + + if (t->edit == NULL) + return S_OK; + // 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; + } + return S_OK; +} + +HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult) +{ + LVHITTESTINFO ht; + uiprivTableColumnParams *p; + int modelColumn, editableColumn; + bool text, checkbox; + uiTableValue *value; + int checked, editable; + HRESULT hr; + + 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; + text = false; + checkbox = false; + p = (*(t->columns))[ht.iSubItem]; + if (p->textModelColumn != -1) { + modelColumn = p->textModelColumn; + editableColumn = p->textEditableModelColumn; + text = true; + } else if (p->checkboxModelColumn != -1) { + modelColumn = p->checkboxModelColumn; + editableColumn = p->checkboxEditableModelColumn; + checkbox = true; + } else if (p->buttonModelColumn != -1) { + modelColumn = p->buttonModelColumn; + editableColumn = p->buttonClickableModelColumn; + } + 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; + + if (!uiprivTableModelCellEditable(t->model, ht.iItem, editableColumn)) + goto done; + + if (text) { + 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; + value = uiprivTableModelCellValue(t->model, ht.iItem, modelColumn); + checked = uiTableValueInt(value); + uiFreeTableValue(value); + value = uiNewTableValueInt(!checked); + uiprivTableModelSetCellValue(t->model, ht.iItem, modelColumn, value); + uiFreeTableValue(value); + } else + 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"); + return E_FAIL; + } + +done: + *lResult = 0; + return S_OK; +} diff --git a/windows/tablemetrics.cpp b/windows/tablemetrics.cpp new file mode 100644 index 00000000..64236b9f --- /dev/null +++ b/windows/tablemetrics.cpp @@ -0,0 +1,105 @@ +// 14 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +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; + uiprivTableColumnParams *p; + LRESULT state; + HWND header; + RECT r; + HRESULT hr; + + if (mout == NULL) + return E_POINTER; + + m = uiprivNew(uiprivTableMetrics); + + 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, iItem, + LVIR_BOUNDS, 0, FALSE, &(m->itemBounds)); + hr = itemRect(hr, t, LVM_GETITEMRECT, iItem, + LVIR_ICON, 0, FALSE, &(m->itemIcon)); + hr = itemRect(hr, t, LVM_GETITEMRECT, iItem, + LVIR_LABEL, 0, FALSE, &(m->itemLabel)); + 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 + // 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 = 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. + // 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; +} diff --git a/windows/uipriv_windows.hpp b/windows/uipriv_windows.hpp index 77982322..a1c2bc29 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 HRESULT uiprivWICToGDI(IWICBitmap *b, HDC dc, int width, int height, HBITMAP *hb); diff --git a/windows/winapi.hpp b/windows/winapi.hpp index 4f24f607..92c08230 100644 --- a/windows/winapi.hpp +++ b/windows/winapi.hpp @@ -37,11 +37,14 @@ #ifndef RC_INVOKED #include #include +#include +#include #include #include #include #include #include +#include #include #include @@ -57,4 +60,5 @@ #include #include #include +#include #endif