More image and checkbox fixups. Next: themed checkboxes.

This commit is contained in:
Pietro Gagliardi 2018-06-10 10:43:29 -04:00
parent a3feb425a1
commit c22f643df7
3 changed files with 156 additions and 90 deletions

View File

@ -86,6 +86,7 @@ static LRESULT onLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm)
HBITMAP b; HBITMAP b;
int checked; int checked;
bool queueUpdated = false; bool queueUpdated = false;
HRESULT hr;
wstr = t->dispinfoStrings->front(); wstr = t->dispinfoStrings->front();
if (wstr != NULL) if (wstr != NULL)
@ -315,7 +316,7 @@ void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModel
void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)
{ {
struct columnParams *p; uiprivTableColumnParams *p;
p = appendColumn(t, name, LVCFMT_LEFT); p = appendColumn(t, name, LVCFMT_LEFT);
p->textModelColumn = textModelColumn; p->textModelColumn = textModelColumn;
@ -343,78 +344,12 @@ void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn)
// TODO redraw? // 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;
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 * nCheckboxImages;
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);
r.left = 0;
r.top = 0;
r.right = cx;
r.bottom = cy;
for (i = 0; i < nCheckboxImages; 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()");
if (ImageList_Replace(t->smallImages, 0, b, NULL) == 0)
logLastError(L"error calling ImageList_Replace() in mkCheckboxesUnthemed()");
// TODO error check
DeleteObject(b);
}
uiTable *uiNewTable(uiTableModel *model) uiTable *uiNewTable(uiTableModel *model)
{ {
uiTable *t; uiTable *t;
int n; int n;
int i; int i;
HRESULT hr;
uiWindowsNewControl(uiTable, t); uiWindowsNewControl(uiTable, t);
@ -443,18 +378,10 @@ uiTable *uiNewTable(uiTableModel *model)
for (i = 0; i < uiprivNumLVN_GETDISPINFOSkip; i++) for (i = 0; i < uiprivNumLVN_GETDISPINFOSkip; i++)
t->dispinfoStrings->push(NULL); t->dispinfoStrings->push(NULL);
// TODO update these when the DPI changes hr = uiprivTableSetupImagesCheckboxes(t);
// TODO handle errors if (hr != S_OK) {
t->smallImages = ImageList_Create( // TODO
GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), }
ILC_COLOR32,
nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip, nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip);
if (t->smallImages == NULL)
logLastError(L"error calling ImageList_Create() in uiNewTable()");
// 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);
return t; return t;
} }

View File

@ -34,7 +34,8 @@ struct uiTable {
// MSDN says we have to keep LVN_GETDISPINFO strings we allocate around at least until "two additional LVN_GETDISPINFO messages have been sent". // MSDN says we have to keep LVN_GETDISPINFO strings we allocate around at least until "two additional LVN_GETDISPINFO messages have been sent".
// we'll use this queue to do so; the "two additional" part is encoded in the initial state of the queue // 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; std::queue<WCHAR *> *dispinfoStrings;
// likewise here, though the docs aren't as clear
// tableimages.cpp
// TODO make sure what we're doing is even allowed // TODO make sure what we're doing is even allowed
HIMAGELIST smallImages; HIMAGELIST smallImages;
int smallIndex; int smallIndex;
@ -43,5 +44,6 @@ struct uiTable {
COLORREF clrItemText; COLORREF clrItemText;
}; };
// tableimage.cpp // tableimages.cpp
extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p); extern HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p);
extern HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t);

View File

@ -13,13 +13,17 @@ TODO will this affect accessibility?
We'll use the small image list. For this, the first few items will be reserved for checkboxes, and the last few for cell images. We'll use the small image list. For this, the first few items will be reserved for checkboxes, and the last few for cell images.
*/ */
// checkboxes TODOs:
// - see if we need to get rid of the extra margin in subitems
// - see if we need to get rid of the glow effect
#define nCheckboxImages 4 #define nCheckboxImages 4
static HRESULT setCellImage(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p, uiTableData *data) static HRESULT setCellImage(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p, uiTableData *data)
{ {
int index; int index;
HDC dc; HDC dc;
IWICImage *wb; IWICBitmap *wb;
HBITMAP b; HBITMAP b;
index = t->smallIndex + nCheckboxImages; index = t->smallIndex + nCheckboxImages;
@ -35,10 +39,17 @@ static HRESULT setCellImage(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnPara
wb = uiprivImageAppropriateForDC(uiTableDataImage(data), dc); wb = uiprivImageAppropriateForDC(uiTableDataImage(data), dc);
b = uiprivWICToGDI(wb, dc); b = uiprivWICToGDI(wb, dc);
if (ImageList_Replace(t->smallImages, index, b, NULL) == 0) { // TODO rewrite this condition to make more sense; possibly swap the if and else blocks too
logLastError(L"ImageList_Replace()"); if (ImageList_GetImageCount(t->smallImages) > index) {
return E_FAIL; if (ImageList_Replace(t->smallImages, index, b, NULL) == 0) {
} logLastError(L"ImageList_Replace()");
return E_FAIL;
}
} else
if (ImageList_Add(t->smallImages, b, NULL) == -1) {
logLastError(L"ImageList_Add()");
return E_FAIL;
}
if (ReleaseDC(t->hwnd, dc) == 0) { if (ReleaseDC(t->hwnd, dc) == 0) {
logLastError(L"ReleaseDC()"); logLastError(L"ReleaseDC()");
@ -54,6 +65,7 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip
HRESULT hr; HRESULT hr;
if ((nm->item.mask & LVIF_IMAGE) == 0) if ((nm->item.mask & LVIF_IMAGE) == 0)
// TODO we actually need to do the first column fix here too...
return S_OK; // nothing to do here return S_OK; // nothing to do here
if (p->imageModelColumn != -1) { if (p->imageModelColumn != -1) {
@ -63,8 +75,8 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip
return hr; return hr;
} }
#if 0
if (p->checkboxModelColumn != -1) { if (p->checkboxModelColumn != -1) {
#if 0
// TODO handle enabled // TODO handle enabled
data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn); data = (*(t->model->mh->CellValue))(t->model->mh, t->model, nm->item.iItem, p->textModelColumn);
checked = uiTableDataInt(data) != 0; checked = uiTableDataInt(data) != 0;
@ -73,17 +85,142 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip
if (checked) if (checked)
nm->item.iImage = 1; nm->item.iImage = 1;
nm->item.mask |= LVIF_IMAGE; nm->item.mask |= LVIF_IMAGE;
}
#endif #endif
nm->item.mask |= LVIF_IMAGE;
nm->item.iImage = 1;
return S_OK;
}
// if we got here, there's no image in this cell // if we got here, there's no image in this cell
nm->item.iImage = 0; nm->item.mask &= ~LVIF_IMAGE;
// having an image list always leaves space for an image on the main item :| // having an image list always leaves space for an image on the main item :|
// other places on the internet imply that you should be able to do this but that it shouldn't work // other places on the internet imply that you should be able to do this but that it shouldn't work
// but it works perfectly (and pixel-perfectly too) for me, so... // but it works perfectly (and pixel-perfectly too) for me, so...
// TODO it doesn't work anymore...
if (nm->item.iSubItem == 0) { if (nm->item.iSubItem == 0) {
nm->item.mask |= LVIF_INDENT; nm->item.mask |= LVIF_INDENT;
nm->item.iIndent = -1; nm->item.iIndent = -1;
} }
return S_OK; return S_OK;
} }
// 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
// TODO properly clean up on failure
static HRESULT mkCheckboxesUnthemed(uiTable *t, int cx, int cy)
{
HDC dc;
BITMAPINFO bmi;
HBITMAP b;
VOID *bits;
HDC cdc;
HBITMAP prevBitmap;
RECT r;
int i;
dc = GetDC(t->hwnd);
if (dc == NULL) {
logLastError(L"GetDC()");
return E_FAIL;
}
ZeroMemory(&bmi, sizeof (BITMAPINFO));
bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = cx * nCheckboxImages;
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"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
r.left = 0;
r.top = 0;
r.right = cx * nCheckboxImages;
r.bottom = cy;
FillRect(cdc, &r, GetSysColorBrush(COLOR_WINDOW));
r.left = 0;
r.top = 0;
r.right = cx;
r.bottom = cy;
// 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 += cx;
r.right += cx;
}
if (SelectObject(cdc, prevBitmap) != ((HGDIOBJ) b)) {
logLastError(L"SelectObject() prev");
return E_FAIL;
}
if (DeleteDC(cdc) == 0) {
logLastError(L"DeleteDC()");
return E_FAIL;
}
if (ReleaseDC(t->hwnd, dc) == 0) {
logLastError(L"ReleaseDC()");
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;
}
// TODO run again when the DPI changes
HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t)
{
int cx, cy;
cx = GetSystemMetrics(SM_CXSMICON);
cy = GetSystemMetrics(SM_CYSMICON);
// TODO handle errors
t->smallImages = ImageList_Create(cx, cy,
ILC_COLOR32,
nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip, nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip);
if (t->smallImages == NULL) {
logLastError(L"ImageList_Create()");
return E_FAIL;
}
// TODO will this return NULL here because it's an initial state?
SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages));
return mkCheckboxesUnthemed(t, cx, cy);
}