Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add minimal uiTable implementation for windows #361

Merged
merged 5 commits into from
May 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions test/page16.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ uiBox *makePage16(void)

uiTableSetRowBackgroundColorModelColumn(t, 3);

uiTableAppendTextColumn(t, "Numbers", 8);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned in another comment, the integer column type is for progress-bar parts. not for text strings. I don't remember what happens with other platforms though, so keep this around and I'll remove it if it causes issues.

However, this does raise the question of whether to provide helpers for displaying numbers as text. That might be outside the scope of libui, but it's still an interesting question...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that the type of the cell used in the table display is independent from datatype of the column in the model. While most mappings between the data and display types are clearly pretty bonkers (eg colour => progress bar doesn't sound too useful), it doesn't seem unreasonable for the table to have a good stab at the sane ones (one?). Particularly the numeric => text mapping.
It's a pretty common case to want to display numeric data in a table, so forcing the client app to present it's numeric data as text when the model already has perfectly good support for numeric data seems like a recipe for confusion...


tc = uiTableAppendColumn(t, "Buttons");
uiTableColumnAppendCheckboxPart(tc, 7, 0);
uiTableColumnAppendButtonPart(tc, 6, 1);
Expand Down
2 changes: 2 additions & 0 deletions windows/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
31 changes: 31 additions & 0 deletions windows/image.cpp
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Owner

@andlabs andlabs May 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a review comment, just a bit of information: I don't think we'll be able to use HIMAGELIST here, since that deals with a set of visually distinct images that have the same pixel sizes, while uiImage deals with a set of visually identical images that has different pixel sizes but the same physical size (for DPI independence). Don't worry about changing anything; I'll work on it later if you don't already have another PR with it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I agree. Wasn't planning to go anywhere further with HIMAGELIST, Just stubbing out enough to get the tables compiling. and threw in the comment while I was in listview mode. I didn't spot the multi-size vs different-images distinction.

};

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
}

280 changes: 280 additions & 0 deletions windows/table.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
#include "uipriv_windows.hpp"

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)
{
LVITEMW item;

ZeroMemory(&item, sizeof (LVITEMW));
item.mask = 0;
item.iItem = newIndex;
item.iSubItem = 0;
for (auto t : m->tables)
if (SendMessageW(t->hwnd, LVM_INSERTITEM, 0, (LPARAM) (&item)) == (LRESULT) (-1))
logLastError(L"error calling LVM_INSERTITEM in uiTableModelRowInserted()");
}

void uiTableModelRowChanged(uiTableModel *m, int index)
{
for (auto t : m->tables)
if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) index, 0) == (LRESULT) (-1))
logLastError(L"error calling LVM_UPDATE in uiTableModelRowChanged()");
}

void uiTableModelRowDeleted(uiTableModel *m, int oldIndex)
{
for (auto t : m->tables)
if (SendMessageW(t->hwnd, LVM_DELETEITEM, (WPARAM) oldIndex, 0) == (LRESULT) (-1))
logLastError(L"error calling LVM_DELETEITEM in uiTableModelRowDeleted()");
}

void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand)
{
uiTable *t = c->t;
int lvIndex = 0;
LVCOLUMNW lvc;

if (c->modelColumn >= 0)
return; // multiple parts not implemented
c->modelColumn = modelColumn;

// work out appropriate listview index for the column
for (auto candidate : t->columns) {
if (candidate == c)
break;
if (candidate->modelColumn >= 0)
lvIndex++;
}

ZeroMemory(&lvc, sizeof (LVCOLUMNW));
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; /* | LVCF_SUBITEM; */
lvc.fmt = LVCFMT_LEFT;
lvc.cx = 120; // TODO
lvc.pszText = c->name;
if (SendMessageW(c->t->hwnd, LVM_INSERTCOLUMN, (WPARAM) lvIndex, (LPARAM) (&lvc)) == (LRESULT) (-1))
logLastError(L"error calling LVM_INSERTCOLUMN in uiTableColumnPartSetTextPart()");
}

void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand)
{
// not implemented
}

void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand)
{
// not implemented
}

void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand)
{
// not implemented
}

void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand)
{
// not implemented
}

void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable)
{
// TODO
}

void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn)
{
// not implemented
}

// uiTable implementation

uiWindowsControlAllDefaultsExceptDestroy(uiTable)

uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name)
{
uiTableColumn *c;

c = uiprivNew(uiTableColumn);
c->name = toUTF16(name);
c->t = t;
c->modelColumn = -1; // -1 = unassigned
// we defer the actual ListView_InsertColumn call until a part is added...
t->columns.push_back(c);
return c;
}

void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn)
{
// not implemented
}

static void uiTableDestroy(uiControl *c)
{
uiTable *t = uiTable(c);
uiTableModel *model = t->model;
std::vector<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));
}

// suggested listview sizing from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing:
// "columns widths that avoid truncated data x an integral number of items"
// Don't think that'll cut it when some cells have overlong data (eg
// stupidly long URLs). So for now, just hardcode a minimum.
// TODO: Investigate using LVM_GETHEADER/HDM_LAYOUT here...
#define tableMinWidth 107 /* in line with other controls */
#define tableMinHeight (14*3) /* header + 2 lines (roughly) */

static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height)
{
uiTable *t = uiTable(c);
uiWindowsSizing sizing;
int x, y;

x = tableMinWidth;
y = tableMinHeight;
uiWindowsGetSizing(t->hwnd, &sizing);
uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);
*width = x;
*height = y;
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style consistency nit applied to everything: use only a single blank line between functions. not two.

static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult)
{
uiTable *t = uiTable(c);
uiTableModelHandler *mh = t->model->mh;
BOOL ret = FALSE;

switch (nmhdr->code) {
case LVN_GETDISPINFO:
{
NMLVDISPINFOW *di;
LVITEMW *item;
int row, col;
uiTableColumn *tc;
int mcol;
uiTableModelColumnType typ;

di = (NMLVDISPINFOW *)nmhdr;
item = &(di->item);
if (!(item->mask & LVIF_TEXT))
break;
row = item->iItem;
col = item->iSubItem;
if (col < 0 || col >= (int)t->columns.size())
break;
tc = (uiTableColumn *)t->columns[col];
mcol = tc->modelColumn;
typ = (*mh->ColumnType)(mh, t->model, mcol);

if (typ == uiTableModelColumnString) {
void* data;
int n;

data = (*(mh->CellValue))(mh, t->model, row, mcol);
n = MultiByteToWideChar(CP_UTF8, 0, (const char *)data, -1, item->pszText, item->cchTextMax);
// make sure clipped strings are nul-terminated
if (n >= item->cchTextMax)
item->pszText[item->cchTextMax-1] = L'\0';
} else if (typ == uiTableModelColumnInt) {
char buf[32];
intptr_t data;
int n;

data = (intptr_t)(*(mh->CellValue))(mh, t->model, row, mcol);
sprintf(buf, "%d", (int)data);
n = MultiByteToWideChar(CP_UTF8, 0, buf, -1, item->pszText, item->cchTextMax);
// make sure clipped strings are nul-terminated
if (n >= item->cchTextMax)
item->pszText[item->cchTextMax-1] = L'\0';
} else
item->pszText[0] = L'\0';
break;
}
}
*lResult = 0;
return ret;
}

uiTable *uiNewTable(uiTableModel *model)
{
uiTable *t;
int n;

uiWindowsNewControl(uiTable, t);
new(&t->columns) std::vector<uiTableColumn *>(); // (initialising in place)
t->model = model;
t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE,
WC_LISTVIEW, L"",
LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL | WS_TABSTOP | WS_HSCROLL | WS_VSCROLL,
hInstance, NULL,
TRUE);
model->tables.push_back(t);
uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t));

// TODO: try LVS_EX_AUTOSIZECOLUMNS
SendMessageW(t->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE,
(WPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP),
(LPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP));
n = (*(model->mh->NumRows))(model->mh, model);
if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0)
logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()");
return t;
}