Started an implementation of checkboxes in table.cpp. List View simply does not seem to support state images in subitems, so we'll have to improvise. Hopefully this won't screw accessibility.

This commit is contained in:
Pietro Gagliardi 2018-06-09 23:02:37 -04:00
parent 0f59bf7399
commit 4c5f0961fe
1 changed files with 112 additions and 8 deletions

View File

@ -38,11 +38,15 @@ struct uiTable {
// we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue
std::queue<WCHAR *> *dispinfoStrings;
// likewise here, though the docs aren't as clear
// TODO make sure what we're doing is even allowed
HIMAGELIST smallImages;
int smallIndex;
// custom draw state
COLORREF clrItemText;
// checkbox
HIMAGELIST checkboxImages;
};
uiTableModel *uiNewTableModel(uiTableModelHandler *mh)
@ -124,6 +128,7 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm)
HDC dc;
IWICBitmap *wb;
HBITMAP b;
int checked;
bool queueUpdated = false;
wstr = t->dispinfoStrings->front();
@ -132,7 +137,7 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm)
t->dispinfoStrings->pop();
p = (*(t->columns))[nm->item.iSubItem];
// TODO is this condition ever not going to be true for libui?
nm->item.pszText=L"abcdefg";
if ((nm->item.mask & LVIF_TEXT) != 0)
if (p->textModelColumn != -1) {
data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn);
@ -175,6 +180,21 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm)
nm->item.iIndent = -1;
}
if ((nm->item.mask & LVIF_STATE) != 0)
if (p->checkboxModelColumn != -1) {
// TODO handle enabled
data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn);
checked = uiTableDataInt(data) != 0;
uiFreeTableData(data);
nm->item.state = INDEXTOSTATEIMAGEMASK(1);
if (checked)
nm->item.state = INDEXTOSTATEIMAGEMASK(2);
nm->item.stateMask = LVIS_STATEIMAGEMASK;
} else {
nm->item.state = INDEXTOSTATEIMAGEMASK(0);
nm->item.stateMask = LVIS_STATEIMAGEMASK;
}
// we don't want to pop from an empty queue, so if nothing updated the queue (no info was filled in above), just push NULL
if (!queueUpdated)
t->dispinfoStrings->push(NULL);
@ -285,6 +305,7 @@ static void uiTableDestroy(uiControl *c)
uiprivFree(col);
delete t->columns;
// t->smallImages will be automatically destroyed
// TODO will t->checkboxImages?
uiFreeControl(uiControl(t));
}
@ -331,8 +352,11 @@ static struct columnParams *appendColumn(uiTable *t, const char *name, int colfm
p = uiprivNew(struct columnParams);
p->textModelColumn = -1;
p->textEditableColumn = -1;
p->textParams = defaultTextColumnOptionalParams;
p->imageModelColumn = -1;
p->checkboxModelColumn = -1;
p->checkboxEditableColumn = -1;
p->progressBarModelColumn = -1;
p->buttonModelColumn = -1;
t->columns->push_back(p);
@ -348,8 +372,6 @@ void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn,
p->textEditableColumn = textEditableModelColumn;
if (params != NULL)
p->textParams = *params;
else
p->textParams = defaultTextColumnOptionalParams;
}
void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn)
@ -366,14 +388,16 @@ void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelCo
p->textEditableColumn = textEditableModelColumn;
if (textParams != NULL)
p->textParams = *textParams;
else
p->textParams = defaultTextColumnOptionalParams;
p->imageModelColumn = imageModelColumn;
}
void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn)
{
// TODO
struct columnParams *p;
p = appendColumn(t, name, LVCFMT_LEFT);
p->checkboxModelColumn = checkboxModelColumn;
p->checkboxEditableColumn = checkboxEditableModelColumn;
}
void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)
@ -385,8 +409,6 @@ void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxM
p->textEditableColumn = textEditableModelColumn;
if (textParams != NULL)
p->textParams = *textParams;
else
p->textParams = defaultTextColumnOptionalParams;
p->checkboxModelColumn = checkboxModelColumn;
p->checkboxEditableColumn = checkboxEditableModelColumn;
}
@ -408,6 +430,83 @@ void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn)
// TODO redraw?
}
// see https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485
static UINT unthemedStates[] = {
0,
DFCS_CHECKED,
DFCS_INACTIVE,
DFCS_CHECKED | DFCS_INACTIVE,
};
// TODO call this whenever either theme, system colors (maybe? TODO), or DPI change
static void mkCheckboxesUnthemed(uiTable *t, int cx, int cy)
{
HDC dc;
BITMAPINFO bmi;
HBITMAP b;
VOID *bits;
HDC cdc;
HBITMAP prevBitmap;
RECT r;
int i, n;
// + 1 because we can't actually use image index 0 — that index is used to mean "no state image" in LVITEMW
n = ARRAYSIZE(unthemedStates) + 1;
dc = GetDC(t->hwnd);
if (dc == NULL)
logLastError(L"error calling GetDC() in mkCheckboxesUnthemed()");
ZeroMemory(&bmi, sizeof (BITMAPINFO));
bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = cx * n;
bmi.bmiHeader.biHeight = cy;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
b = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS,
&bits, NULL, 0);
if (b == NULL)
logLastError(L"error calling CreateDIBSection() in mkCheckboxesUnthemed()");
cdc = CreateCompatibleDC(dc);
if (cdc == NULL)
logLastError(L"error calling CreateCompatibleDC() in mkCheckboxesUnthemed()");
// TODO error check
prevBitmap = (HBITMAP) SelectObject(cdc, b);
// note we start at cx to start at index 1
r.left = cx;
r.top = 0;
r.right = cx * 2;
r.bottom = cy;
for (i = 0; i < ARRAYSIZE(unthemedStates); i++) {
if (DrawFrameControl(cdc, &r,
DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | unthemedStates[i]) == 0)
logLastError(L"error calling DrawFrameControl() in mkCheckboxesUnthemed()");
r.left += cx;
r.right += cx;
}
// TODO error check
SelectObject(cdc, prevBitmap);
if (DeleteDC(cdc) == 0)
logLastError(L"error calling DeleteDC() in mkCheckboxesUnthemed()");
if (ReleaseDC(t->hwnd, dc) == 0)
logLastError(L"error calling ReleaseDC() in mkCheckboxesUnthemed()");
t->checkboxImages = ImageList_Create(cx, cy, ILC_COLOR32,
n, n);
if (t->checkboxImages == NULL)
logLastError(L"error calling ImageList_Create() in mkCheckboxesUnthemed()");
if (ImageList_Add(t->checkboxImages, b, NULL) == -1)
logLastError(L"error calling ImageList_Add() in mkCheckboxesUnthemed()");
// TODO will this return NULL here because it's an initial state?
SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_STATE, (LPARAM) (t->checkboxImages));
// TODO error check
DeleteObject(b);
}
uiTable *uiNewTable(uiTableModel *model)
{
uiTable *t;
@ -453,5 +552,10 @@ uiTable *uiNewTable(uiTableModel *model)
// TODO will this return NULL here because it's an initial state?
SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages));
mkCheckboxesUnthemed(t, 16, 16);
// and we need to enable LVN_GETDISPINFO for states
if (SendMessageW(t->hwnd, LVM_SETCALLBACKMASK, LVIS_STATEIMAGEMASK, 0) == FALSE)
logLastError(L"error calling LVM_SETCALLBACKMASK in uiTableAdd()");
return t;
}