diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp index 6887bb54..ad8b4b46 100644 --- a/windows/tabledraw.cpp +++ b/windows/tabledraw.cpp @@ -79,10 +79,22 @@ static HRESULT computeOtherRectsAndDrawBackgrounds(struct drawState *s) 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; + image->left += xoff; image->top += yoff; + image->right += xoff; image->bottom += yoff; } @@ -135,6 +147,121 @@ static HRESULT drawImagePart(struct drawState *s) 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) { COLORREF prevText; @@ -374,6 +501,9 @@ HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT * if (hr != S_OK) goto fail; hr = drawImagePart(&s); + if (hr != S_OK) + goto fail; + hr = drawCheckboxPart(&s); if (hr != S_OK) goto fail; hr = drawTextPart(&s); @@ -426,6 +556,11 @@ HRESULT uiprivUpdateImageListSize(uiTable *t) cxList = sizeCheck.cx; if (cyList < sizeCheck.cy) cyList = sizeCheck.cy; + hr = CloseThemeData(theme); + if (hr != S_OK) { + logHRESULT(L"CloseThemeData()", hr); + return hr; + } } // TODO handle errors @@ -439,11 +574,6 @@ HRESULT uiprivUpdateImageListSize(uiTable *t) // TODO will this return NULL here because it's an initial state? 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) { logLastError(L"ReleaseDC()"); return E_FAIL; diff --git a/windows/tableimages.cpp b/windows/tableimages.cpp index b7fed3bd..7f163ec0 100644 --- a/windows/tableimages.cpp +++ b/windows/tableimages.cpp @@ -20,39 +20,6 @@ We'll use the small image list. For this, the first few items will be reserved f #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) { uiTableData *data; @@ -75,10 +42,8 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip return S_OK; if (p->imageModelColumn != -1) { - data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->imageModelColumn); - hr = setCellImage(t, nm, p, data); - uiFreeTableData(data); - return hr; + nm->item.iImage = 0; + return S_OK; } if (p->checkboxModelColumn != -1) { @@ -101,172 +66,3 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip nm->item.iImage = -1; 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