Add minimal uiTable implementation for windows

This uses the win32 common controls listview to implement uiTable.
There are limitations:
 - It supports only a single TextPart per column.
 - ImagePart, CheckboxPart and ProgessBarPart are not implemented.
 - There is no support for cell coloring.
 - Cell editing is not implemented.
Some of these will be very hard to support using the standard
common control listview, and probably require an entire custom
listview.
This commit is contained in:
Ben Campbell 2018-05-05 22:45:01 +12:00
parent 31090442be
commit fc2ea17bb8
4 changed files with 312 additions and 0 deletions

View File

@ -132,6 +132,8 @@ uiBox *makePage16(void)
uiTableSetRowBackgroundColorModelColumn(t, 3);
uiTableAppendTextColumn(t, "Numbers", 8);
tc = uiTableAppendColumn(t, "Buttons");
uiTableColumnAppendCheckboxPart(tc, 7, 0);
uiTableColumnAppendButtonPart(tc, 6, 1);

View File

@ -34,6 +34,7 @@ list(APPEND _LIBUI_SOURCES
windows/graphemes.cpp
windows/grid.cpp
windows/group.cpp
windows/image.cpp
windows/init.cpp
windows/label.cpp
windows/main.cpp
@ -49,6 +50,7 @@ list(APPEND _LIBUI_SOURCES
windows/spinbox.cpp
windows/stddialogs.cpp
windows/tab.cpp
windows/table.cpp
windows/tabpage.cpp
windows/text.cpp
windows/utf16.cpp

31
windows/image.cpp Normal file
View File

@ -0,0 +1,31 @@
#include "uipriv_windows.hpp"
// stubbed out windows image list implementation.
// Required for uiTable control, but windows implemenation
// doesn't currently have image support.
struct uiImage {
double width;
double height;
// HIMAGELIST images;
};
uiImage *uiNewImage(double width, double height)
{
uiImage *i;
i = uiprivNew(uiImage);
i->width = width;
i->height = height;
return i;
}
void uiFreeImage(uiImage *i)
{
uiprivFree(i);
}
void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride)
{
// not implemented
}

277
windows/table.cpp Normal file
View File

@ -0,0 +1,277 @@
#include "uipriv_windows.hpp"
#include <vector>
static void uiTableDestroy(uiControl *c);
static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height);
static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nm, LRESULT *lResult);
struct uiTable;
struct uiTableModel {
uiTableModelHandler *mh;
std::vector<uiTable*> tables;
};
struct uiTableColumn {
uiTable *t;
WCHAR *name;
// don't really support parts (but this would part=>column mappings if we did)
int modelColumn; // -1 = none
};
struct uiTable {
uiWindowsControl c;
uiTableModel *model;
HWND hwnd;
std::vector<uiTableColumn*> columns;
};
void *uiTableModelStrdup(const char *str)
{
return strdup(str);
}
void *uiTableModelGiveColor(double r, double g, double b, double a)
{
return 0; // not implemented
}
uiTableModel *uiNewTableModel(uiTableModelHandler *mh)
{
uiTableModel *m;
m = new uiTableModel();
m->mh = mh;
return m;
}
void uiFreeTableModel(uiTableModel *m)
{
delete m;
}
void uiTableModelRowInserted(uiTableModel *m, int newIndex)
{
LVITEM item;
item.mask = 0;
item.iItem = newIndex;
item.iSubItem = 0; //?
for (auto t : m->tables) {
ListView_InsertItem( t->hwnd, &item );
}
}
void uiTableModelRowChanged(uiTableModel *m, int index)
{
for (auto t : m->tables) {
ListView_Update( t->hwnd, index );
}
}
void uiTableModelRowDeleted(uiTableModel *m, int oldIndex)
{
for (auto t : m->tables) {
ListView_DeleteItem( t->hwnd, oldIndex );
}
}
void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand)
{
uiTable *t = c->t;
int lvIndex = 0;
if (c->modelColumn >=0) {
return; // multiple parts not implemented
}
c->modelColumn = modelColumn;
// work out appropriate listview index for the column
for (auto candidate : t->columns) {
if (candidate == c) {
break;
}
if (candidate->modelColumn >= 0) {
++lvIndex;
}
}
LV_COLUMN lvc;
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; /* | LVCF_SUBITEM; */
lvc.fmt = LVCFMT_LEFT;
lvc.cx = 120; // TODO
lvc.pszText = c->name;
ListView_InsertColumn(c->t->hwnd, lvIndex, &lvc);
}
void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand)
{
// not implemented
}
void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand)
{
// not implemented
}
void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand)
{
// not implemented
}
void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand)
{
// not implemented
}
void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable)
{
// TODO
}
void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn)
{
// not implemented
}
// uiTable implementation
uiWindowsControlAllDefaultsExceptDestroy(uiTable)
uiTable *uiNewTable(uiTableModel *model)
{
uiTable *t;
int winStyle = WS_CHILD | LVS_AUTOARRANGE | LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL;
uiWindowsNewControl(uiTable, t);
new(&t->columns) std::vector<uiTableColumn*>(); // (initialising in place)
t->model = model;
t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE,
WC_LISTVIEW,
L"",
winStyle,
hInstance,
NULL,
TRUE);
model->tables.push_back(t);
uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t));
ListView_SetExtendedListViewStyle(t->hwnd, LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP);
// TODO: try LVS_EX_AUTOSIZECOLUMNS
int n = (*(model->mh->NumRows))(model->mh, model);
ListView_SetItemCountEx(t->hwnd, n, 0);
return t;
}
uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name)
{
uiTableColumn *c = uiprivNew(uiTableColumn);
c->name = toUTF16(name);
c->t = t;
c->modelColumn = -1; // -1 = unassigned
// we defer the actual ListView_InsertColumn call until a part is added...
t->columns.push_back(c);
return c;
}
void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn)
{
// not implemented
}
static void uiTableDestroy(uiControl *c)
{
uiTable *t = uiTable(c);
uiTableModel *model = t->model;
std::vector<uiTable*>::iterator it;
uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd);
uiWindowsEnsureDestroyWindow(t->hwnd);
// detach table from model
for (it = model->tables.begin(); it != model->tables.end(); ++it) {
if (*it == t) {
model->tables.erase(it);
break;
}
}
// free the columns
for (auto col: t->columns) {
uiprivFree(col->name);
uiprivFree(col);
}
t->columns.~vector<uiTableColumn*>(); // (created with placement new, so just call dtor directly)
uiFreeControl(uiControl(t));
}
static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height)
{
uiTable *t = uiTable(c);
uiWindowsSizing sizing;
int x, y;
// suggested listview sizing from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing:
// "columns widths that avoid truncated data x an integral number of items"
// Don't think that'll cut it when some cells have overlong data (eg
// stupidly long URLs). So for now, just hardcode a minimum:
x = 107; // in line with other controls
y = 14*3; // header + 2 lines (roughly)
uiWindowsGetSizing(t->hwnd, &sizing);
uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);
*width = x;
*height = y;
}
static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nm, LRESULT *lResult)
{
uiTable *t = uiTable(c);
uiTableModelHandler *mh = t->model->mh;
BOOL ret = FALSE;
switch (nm->code) {
case LVN_GETDISPINFO:
{
NMLVDISPINFO* di = (NMLVDISPINFO*)nm;
LVITEM* item = &di->item;
if (!(item->mask & LVIF_TEXT)) {
break;
}
int row = item->iItem;
int col = item->iSubItem;
if (col<0 || col>=(int)t->columns.size()) {
break;
}
uiTableColumn *tc = (uiTableColumn*)t->columns[col];
int mcol = tc->modelColumn;
uiTableModelColumnType typ = (*mh->ColumnType)(mh,t->model,mcol);
if (typ == uiTableModelColumnString) {
void* data = (*(mh->CellValue))(mh, t->model, row, mcol);
int n = MultiByteToWideChar(CP_UTF8, 0, (const char*)data, -1, item->pszText, item->cchTextMax);
// make sure clipped strings are nul-terminated
if (n>=item->cchTextMax) {
item->pszText[item->cchTextMax-1] = L'\0';
}
} else if (typ == uiTableModelColumnInt) {
char buf[32];
intptr_t data = (intptr_t)(*(mh->CellValue))(mh, t->model, row, mcol);
sprintf(buf, "%d", (int)data);
int n = MultiByteToWideChar(CP_UTF8, 0, buf, -1, item->pszText, item->cchTextMax);
// make sure clipped strings are nul-terminated
if (n>=item->cchTextMax) {
item->pszText[item->cchTextMax-1] = L'\0';
}
} else {
item->pszText[0] = L'\0';
}
break;
}
default:
break;
}
*lResult = 0;
return ret;
}