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:
parent
31090442be
commit
fc2ea17bb8
|
@ -132,6 +132,8 @@ uiBox *makePage16(void)
|
||||||
|
|
||||||
uiTableSetRowBackgroundColorModelColumn(t, 3);
|
uiTableSetRowBackgroundColorModelColumn(t, 3);
|
||||||
|
|
||||||
|
uiTableAppendTextColumn(t, "Numbers", 8);
|
||||||
|
|
||||||
tc = uiTableAppendColumn(t, "Buttons");
|
tc = uiTableAppendColumn(t, "Buttons");
|
||||||
uiTableColumnAppendCheckboxPart(tc, 7, 0);
|
uiTableColumnAppendCheckboxPart(tc, 7, 0);
|
||||||
uiTableColumnAppendButtonPart(tc, 6, 1);
|
uiTableColumnAppendButtonPart(tc, 6, 1);
|
||||||
|
|
|
@ -34,6 +34,7 @@ list(APPEND _LIBUI_SOURCES
|
||||||
windows/graphemes.cpp
|
windows/graphemes.cpp
|
||||||
windows/grid.cpp
|
windows/grid.cpp
|
||||||
windows/group.cpp
|
windows/group.cpp
|
||||||
|
windows/image.cpp
|
||||||
windows/init.cpp
|
windows/init.cpp
|
||||||
windows/label.cpp
|
windows/label.cpp
|
||||||
windows/main.cpp
|
windows/main.cpp
|
||||||
|
@ -49,6 +50,7 @@ list(APPEND _LIBUI_SOURCES
|
||||||
windows/spinbox.cpp
|
windows/spinbox.cpp
|
||||||
windows/stddialogs.cpp
|
windows/stddialogs.cpp
|
||||||
windows/tab.cpp
|
windows/tab.cpp
|
||||||
|
windows/table.cpp
|
||||||
windows/tabpage.cpp
|
windows/tabpage.cpp
|
||||||
windows/text.cpp
|
windows/text.cpp
|
||||||
windows/utf16.cpp
|
windows/utf16.cpp
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include "uipriv_windows.hpp"
|
||||||
|
// stubbed out windows image list implementation.
|
||||||
|
// Required for uiTable control, but windows implemenation
|
||||||
|
// doesn't currently have image support.
|
||||||
|
|
||||||
|
struct uiImage {
|
||||||
|
double width;
|
||||||
|
double height;
|
||||||
|
// HIMAGELIST images;
|
||||||
|
};
|
||||||
|
|
||||||
|
uiImage *uiNewImage(double width, double height)
|
||||||
|
{
|
||||||
|
uiImage *i;
|
||||||
|
|
||||||
|
i = uiprivNew(uiImage);
|
||||||
|
i->width = width;
|
||||||
|
i->height = height;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiFreeImage(uiImage *i)
|
||||||
|
{
|
||||||
|
uiprivFree(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride)
|
||||||
|
{
|
||||||
|
// not implemented
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue