commit
075d5efb61
|
@ -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);
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
High DPI Displays | Qt 5.5 http://doc.qt.io/qt-5/highdpi.html bottom of page(?)
|
|
@ -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/
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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]];
|
||||
|
|
|
@ -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
|
|
@ -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<NSTableViewDataSource, NSTableViewDelegate> {
|
||||
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;
|
||||
}
|
|
@ -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];
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
// 25 june 2018
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -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);
|
|
@ -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}
|
||||
)
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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]);
|
||||
}
|
|
@ -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);
|
||||
|
|
332
ui.h
332
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
20
unix/image.c
20
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<IWICBitmap *> *bitmaps;
|
||||
};
|
||||
|
||||
uiImage *uiNewImage(double width, double height)
|
||||
{
|
||||
uiImage *i;
|
||||
|
||||
i = uiprivNew(uiImage);
|
||||
i->width = width;
|
||||
i->height = height;
|
||||
i->bitmaps = new std::vector<IWICBitmap *>;
|
||||
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;
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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<uiTable *>;
|
||||
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<int, int> p;
|
||||
std::map<std::pair<int, int>, 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<uiTable *>::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<uiprivTableColumnParams *>;
|
||||
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<std::pair<int, int>, LONG>;
|
||||
if (SetWindowSubclass(t->hwnd, tableSubProc, 0, (DWORD_PTR) t) == FALSE)
|
||||
logLastError(L"SetWindowSubclass()");
|
||||
|
||||
return t;
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// 10 june 2018
|
||||
#include "../common/table.h"
|
||||
|
||||
// table.cpp
|
||||
#define uiprivNumLVN_GETDISPINFOSkip 3
|
||||
struct uiTableModel {
|
||||
uiTableModelHandler *mh;
|
||||
std::vector<uiTable *> *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<uiprivTableColumnParams *> *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<std::pair<int, int>, 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);
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -37,11 +37,14 @@
|
|||
#ifndef RC_INVOKED
|
||||
#include <commctrl.h>
|
||||
#include <uxtheme.h>
|
||||
#include <vsstyle.h>
|
||||
#include <vssym32.h>
|
||||
#include <windowsx.h>
|
||||
#include <shobjidl.h>
|
||||
#include <d2d1.h>
|
||||
#include <d2d1helper.h>
|
||||
#include <dwrite.h>
|
||||
#include <wincodec.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
@ -57,4 +60,5 @@
|
|||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue