And drew checkboxes. We can FINALLY move on to other data types! ...almost. First we have to consolidate LVN_GETDISPINFO handlers.
This commit is contained in:
parent
4bfd950caa
commit
b9289c93a6
|
@ -79,10 +79,22 @@ static HRESULT computeOtherRectsAndDrawBackgrounds(struct drawState *s)
|
||||||
|
|
||||||
static void centerImageRect(RECT *image, RECT *space)
|
static void centerImageRect(RECT *image, RECT *space)
|
||||||
{
|
{
|
||||||
LONG yoff;
|
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;
|
yoff = ((space->bottom - space->top) - (image->bottom - image->top)) / 2;
|
||||||
|
image->left += xoff;
|
||||||
image->top += yoff;
|
image->top += yoff;
|
||||||
|
image->right += xoff;
|
||||||
image->bottom += yoff;
|
image->bottom += yoff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +147,121 @@ static HRESULT drawImagePart(struct drawState *s)
|
||||||
return S_OK;
|
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->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->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->subitemIcon;
|
||||||
|
r.right = r.left + size.cx;
|
||||||
|
r.bottom = r.top + size.cy;
|
||||||
|
|
||||||
|
centerImageRect(&r, &(s->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(struct drawState *s)
|
||||||
|
{
|
||||||
|
uiTableData *data;
|
||||||
|
int checked, enabled;
|
||||||
|
HTHEME theme;
|
||||||
|
HRESULT hr;
|
||||||
|
|
||||||
|
if (s->p->checkboxModelColumn == -1)
|
||||||
|
return S_OK;
|
||||||
|
|
||||||
|
data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->checkboxModelColumn);
|
||||||
|
checked = uiTableDataInt(data);
|
||||||
|
uiFreeTableData(data);
|
||||||
|
switch (s->p->checkboxEditableColumn) {
|
||||||
|
case uiTableModelColumnNeverEditable:
|
||||||
|
enabled = 0;
|
||||||
|
break;
|
||||||
|
case uiTableModelColumnAlwaysEditable:
|
||||||
|
enabled = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->checkboxEditableColumn);
|
||||||
|
enabled = uiTableDataInt(data);
|
||||||
|
uiFreeTableData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(struct drawState *s)
|
static HRESULT drawTextPart(struct drawState *s)
|
||||||
{
|
{
|
||||||
COLORREF prevText;
|
COLORREF prevText;
|
||||||
|
@ -374,6 +501,9 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *
|
||||||
if (hr != S_OK)
|
if (hr != S_OK)
|
||||||
goto fail;
|
goto fail;
|
||||||
hr = drawImagePart(&s);
|
hr = drawImagePart(&s);
|
||||||
|
if (hr != S_OK)
|
||||||
|
goto fail;
|
||||||
|
hr = drawCheckboxPart(&s);
|
||||||
if (hr != S_OK)
|
if (hr != S_OK)
|
||||||
goto fail;
|
goto fail;
|
||||||
hr = drawTextPart(&s);
|
hr = drawTextPart(&s);
|
||||||
|
@ -426,6 +556,11 @@ HRESULT uiprivUpdateImageListSize(uiTable *t)
|
||||||
cxList = sizeCheck.cx;
|
cxList = sizeCheck.cx;
|
||||||
if (cyList < sizeCheck.cy)
|
if (cyList < sizeCheck.cy)
|
||||||
cyList = sizeCheck.cy;
|
cyList = sizeCheck.cy;
|
||||||
|
hr = CloseThemeData(theme);
|
||||||
|
if (hr != S_OK) {
|
||||||
|
logHRESULT(L"CloseThemeData()", hr);
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle errors
|
// TODO handle errors
|
||||||
|
@ -439,11 +574,6 @@ HRESULT uiprivUpdateImageListSize(uiTable *t)
|
||||||
// TODO will this return NULL here because it's an initial state?
|
// TODO will this return NULL here because it's an initial state?
|
||||||
SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->imagelist));
|
SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->imagelist));
|
||||||
|
|
||||||
hr = CloseThemeData(theme);
|
|
||||||
if (hr != S_OK) {
|
|
||||||
logHRESULT(L"CloseThemeData()", hr);
|
|
||||||
return hr;
|
|
||||||
}
|
|
||||||
if (ReleaseDC(t->hwnd, dc) == 0) {
|
if (ReleaseDC(t->hwnd, dc) == 0) {
|
||||||
logLastError(L"ReleaseDC()");
|
logLastError(L"ReleaseDC()");
|
||||||
return E_FAIL;
|
return E_FAIL;
|
||||||
|
|
|
@ -20,39 +20,6 @@ We'll use the small image list. For this, the first few items will be reserved f
|
||||||
|
|
||||||
#define nCheckboxImages 4
|
#define nCheckboxImages 4
|
||||||
|
|
||||||
static HRESULT setCellImage(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p, uiTableData *data)
|
|
||||||
{return E_NOTIMPL;/*TODO*/}
|
|
||||||
|
|
||||||
#define stateUnchecked 0
|
|
||||||
#define stateChecked 1
|
|
||||||
#define stateDisabled 2
|
|
||||||
|
|
||||||
static int checkboxIndex(uiTableModel *m, int row, int checkboxModelColumn, int checkboxEditableColumn)
|
|
||||||
{
|
|
||||||
uiTableData *data;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = stateUnchecked;
|
|
||||||
data = (*(m->mh->CellValue))(m->mh, m, row, checkboxModelColumn);
|
|
||||||
if (uiTableDataInt(data) != 0)
|
|
||||||
ret = stateChecked;
|
|
||||||
uiFreeTableData(data);
|
|
||||||
|
|
||||||
switch (checkboxEditableColumn) {
|
|
||||||
case uiTableModelColumnNeverEditable:
|
|
||||||
ret += stateDisabled;
|
|
||||||
break;
|
|
||||||
case uiTableModelColumnAlwaysEditable:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
data = (*(m->mh->CellValue))(m->mh, m, row, checkboxEditableColumn);
|
|
||||||
if (uiTableDataInt(data) != 0)
|
|
||||||
ret += stateDisabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p)
|
HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p)
|
||||||
{
|
{
|
||||||
uiTableData *data;
|
uiTableData *data;
|
||||||
|
@ -75,10 +42,8 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip
|
||||||
return S_OK;
|
return S_OK;
|
||||||
|
|
||||||
if (p->imageModelColumn != -1) {
|
if (p->imageModelColumn != -1) {
|
||||||
data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->imageModelColumn);
|
nm->item.iImage = 0;
|
||||||
hr = setCellImage(t, nm, p, data);
|
return S_OK;
|
||||||
uiFreeTableData(data);
|
|
||||||
return hr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p->checkboxModelColumn != -1) {
|
if (p->checkboxModelColumn != -1) {
|
||||||
|
@ -101,172 +66,3 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip
|
||||||
nm->item.iImage = -1;
|
nm->item.iImage = -1;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
|
||||||
|
|
||||||
// in order to properly look like checkboxes, we need to exclude them from being colored in by the selection rect
|
|
||||||
// however, there seems to be no way to do this natively, so we have to draw the icons ourselves
|
|
||||||
// see also https://www.codeproject.com/Articles/79/Neat-Stuff-to-Do-in-List-Controls-Using-Custom-Dra (and while this uses postpaint to draw over the existing icon, we are drawing everything ourselves, so we only draw once)
|
|
||||||
HRESULT uiprivNM_CUSTOMDRAWImagesCheckboxes(uiTable *t, NMLVCUSTOMDRAW *nm, uiprivSubitemDrawParams *dp)
|
|
||||||
{
|
|
||||||
uiprivTableColumnParams *p;
|
|
||||||
int index;
|
|
||||||
RECT r;
|
|
||||||
int cxIcon, cyIcon;
|
|
||||||
LONG yoff;
|
|
||||||
|
|
||||||
if (nm->nmcd.dwDrawStage != (CDDS_SUBITEM | CDDS_ITEMPREPAINT))
|
|
||||||
return S_OK;
|
|
||||||
|
|
||||||
// only draw over checkboxes
|
|
||||||
p = (*(t->columns))[nm->iSubItem];
|
|
||||||
if (p->checkboxModelColumn == -1)
|
|
||||||
return S_OK;
|
|
||||||
|
|
||||||
index = checkboxIndex(t->model, nm->nmcd.dwItemSpec,
|
|
||||||
p->checkboxModelColumn, p->checkboxEditableColumn);
|
|
||||||
r = dp->icon;
|
|
||||||
// the real listview also does this :|
|
|
||||||
if (ImageList_GetIconSize(t->smallImages, &cxIcon, &cyIcon) == 0) {
|
|
||||||
logLastError(L"LVM_GETSUBITEMRECT cell");
|
|
||||||
return E_FAIL;
|
|
||||||
}
|
|
||||||
yoff = ((r.bottom - r.top) - cyIcon) / 2;
|
|
||||||
r.top += yoff;
|
|
||||||
r.bottom += yoff;
|
|
||||||
if ((nm->nmcd.dwItemSpec%2)==0)
|
|
||||||
if (ImageList_Draw(t->smallImages, index, nm->nmcd.hdc,
|
|
||||||
r.left, r.top, ILD_NORMAL) == 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 UINT unthemedStates[] = {
|
|
||||||
0,
|
|
||||||
DFCS_CHECKED,
|
|
||||||
DFCS_INACTIVE,
|
|
||||||
DFCS_CHECKED | DFCS_INACTIVE,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int themedStates[] = {
|
|
||||||
CBS_UNCHECKEDNORMAL,
|
|
||||||
CBS_CHECKEDNORMAL,
|
|
||||||
CBS_UNCHECKEDDISABLED,
|
|
||||||
CBS_CHECKEDDISABLED,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO properly clean up on failure
|
|
||||||
static HRESULT mkCheckboxes(uiTable *t, HTHEME theme, HDC dc, int cxList, int cyList, int cxCheck, int cyCheck)
|
|
||||||
{
|
|
||||||
BITMAPINFO bmi;
|
|
||||||
HBITMAP b;
|
|
||||||
VOID *bits;
|
|
||||||
HDC cdc;
|
|
||||||
HBITMAP prevBitmap;
|
|
||||||
RECT r;
|
|
||||||
int i;
|
|
||||||
HRESULT hr;
|
|
||||||
|
|
||||||
ZeroMemory(&bmi, sizeof (BITMAPINFO));
|
|
||||||
bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
|
|
||||||
bmi.bmiHeader.biWidth = cxList * nCheckboxImages;
|
|
||||||
bmi.bmiHeader.biHeight = cyList;
|
|
||||||
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"CreateDIBSection()");
|
|
||||||
return E_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
cdc = CreateCompatibleDC(dc);
|
|
||||||
if (cdc == NULL) {
|
|
||||||
logLastError(L"CreateCompatibleDC()");
|
|
||||||
return E_FAIL;
|
|
||||||
}
|
|
||||||
prevBitmap = (HBITMAP) SelectObject(cdc, b);
|
|
||||||
if (prevBitmap == NULL) {
|
|
||||||
logLastError(L"SelectObject() b");
|
|
||||||
return E_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the actual list view LVS_EX_CHECKBOXES code does this to ensure the entire image is valid, not just the parts that are drawn after resizing
|
|
||||||
// TODO find a better, alpha-friendly way to do this
|
|
||||||
// note that the actual list view does this only if unthemed, but it can get away with that since its image lists only contain checkmarks
|
|
||||||
// ours don't, so we have to compromise until the above TODO is resolved so we don't draw alpha stuff on top of garbage
|
|
||||||
if (theme == NULL || cxList != cxCheck || cyList != cyCheck) {
|
|
||||||
r.left = 0;
|
|
||||||
r.top = 0;
|
|
||||||
r.right = cxList * nCheckboxImages;
|
|
||||||
r.bottom = cyList;
|
|
||||||
FillRect(cdc, &r, GetSysColorBrush(COLOR_WINDOW));
|
|
||||||
}
|
|
||||||
|
|
||||||
r.left = 0;
|
|
||||||
r.top = 0;
|
|
||||||
r.right = cxCheck;
|
|
||||||
r.bottom = cyCheck;
|
|
||||||
if (theme != NULL) {
|
|
||||||
// because we're not making an image list exactly the correct size, we'll need to manually position the checkbox correctly
|
|
||||||
// let's just center it for now
|
|
||||||
// TODO make sure this is correct...
|
|
||||||
r.left = (cxList - cxCheck) / 2;
|
|
||||||
r.top = (cyList - cyCheck) / 2;
|
|
||||||
r.right += r.left;
|
|
||||||
r.bottom += r.top;
|
|
||||||
for (i = 0; i < nCheckboxImages; i++) {
|
|
||||||
hr = DrawThemeBackground(theme, cdc,
|
|
||||||
BP_CHECKBOX, themedStates[i],
|
|
||||||
&r, NULL);
|
|
||||||
if (hr != S_OK) {
|
|
||||||
logHRESULT(L"DrawThemeBackground()", hr);
|
|
||||||
return hr;
|
|
||||||
}
|
|
||||||
r.left += cxList;
|
|
||||||
r.right += cxList;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// this is what the actual list view LVS_EX_CHECKBOXES code does to more correctly size the checkboxes
|
|
||||||
// TODO check errors
|
|
||||||
InflateRect(&r, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE));
|
|
||||||
r.right++;
|
|
||||||
r.bottom++;
|
|
||||||
for (i = 0; i < nCheckboxImages; i++) {
|
|
||||||
if (DrawFrameControl(cdc, &r,
|
|
||||||
DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | unthemedStates[i]) == 0) {
|
|
||||||
logLastError(L"DrawFrameControl()");
|
|
||||||
return E_FAIL;
|
|
||||||
}
|
|
||||||
r.left += cxList;
|
|
||||||
r.right += cxList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SelectObject(cdc, prevBitmap) != ((HGDIOBJ) b)) {
|
|
||||||
logLastError(L"SelectObject() prev");
|
|
||||||
return E_FAIL;
|
|
||||||
}
|
|
||||||
if (DeleteDC(cdc) == 0) {
|
|
||||||
logLastError(L"DeleteDC()");
|
|
||||||
return E_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImageList_Add(t->smallImages, b, NULL) == -1) {
|
|
||||||
logLastError(L"ImageList_Add()");
|
|
||||||
return E_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO error check
|
|
||||||
DeleteObject(b);
|
|
||||||
return S_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
Loading…
Reference in New Issue