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:
Pietro Gagliardi 2018-06-16 11:59:17 -04:00
parent 4bfd950caa
commit b9289c93a6
2 changed files with 138 additions and 212 deletions

View File

@ -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;

View File

@ -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