// 14 june 2018
#include "uipriv_windows.hpp"
#include "table.hpp"

// TODOs:
// - properly hide selection when not focused (or switch on LVS_SHOWSELALWAYS and draw that state)

// TODO maybe split this into item and subitem structs?
struct drawState {
	uiTable *t;
	uiTableModel *model;
	uiprivTableColumnParams *p;

	HDC dc;
	int iItem;
	int iSubItem;

	uiprivTableMetrics *m;

	COLORREF bgColor;
	HBRUSH bgBrush;
	BOOL freeBgBrush;
	COLORREF textColor;
	HBRUSH textBrush;
	BOOL freeTextBrush;
};

static HRESULT drawBackgrounds(HRESULT hr, struct drawState *s)
{
	if (hr != S_OK)
		return hr;
	if (s->m->hasImage)
		if (FillRect(s->dc, &(s->m->subitemIcon), GetSysColorBrush(COLOR_WINDOW)) == 0) {
			logLastError(L"FillRect() icon");
			return E_FAIL;
		}
	if (FillRect(s->dc, &(s->m->realTextBackground), s->bgBrush) == 0) {
		logLastError(L"FillRect()");
		return E_FAIL;
	}
	return S_OK;
}

static void centerImageRect(RECT *image, RECT *space)
{
	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;
}

static HRESULT drawImagePart(HRESULT hr, struct drawState *s)
{
	uiTableValue *value;
	IWICBitmap *wb;
	HBITMAP b;
	RECT r;
	UINT fStyle;

	if (hr != S_OK)
		return hr;
	if (s->p->imageModelColumn == -1)
		return S_OK;

	value = uiprivTableModelCellValue(s->model, s->iItem, s->p->imageModelColumn);
	wb = uiprivImageAppropriateForDC(uiTableValueImage(value), s->dc);
	uiFreeTableValue(value);

	hr = uiprivWICToGDI(wb, s->dc, s->m->cxIcon, s->m->cyIcon, &b);
	if (hr != S_OK)
		return hr;
	// TODO rewrite this condition to make more sense; possibly swap the if and else blocks too
	// TODO proper cleanup
	if (ImageList_GetImageCount(s->t->imagelist) > 1) {
		if (ImageList_Replace(s->t->imagelist, 0, b, NULL) == 0) {
			logLastError(L"ImageList_Replace()");
			return E_FAIL;
		}
	} else
		if (ImageList_Add(s->t->imagelist, b, NULL) == -1) {
			logLastError(L"ImageList_Add()");
			return E_FAIL;
		}
	// TODO error check
	DeleteObject(b);

	r = s->m->subitemIcon;
	r.right = r.left + s->m->cxIcon;
	r.bottom = r.top + s->m->cyIcon;
	centerImageRect(&r, &(s->m->subitemIcon));
	fStyle = ILD_NORMAL;
	if (s->m->selected)
		fStyle = ILD_SELECTED;
	if (ImageList_Draw(s->t->imagelist, 0,
		s->dc, r.left, r.top, fStyle) == 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 HRESULT drawUnthemedCheckbox(struct drawState *s, int checked, int enabled)
{
	RECT r;
	UINT state;

	r = s->m->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->m->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->m->subitemIcon;
	r.right = r.left + size.cx;
	r.bottom = r.top + size.cy;

	centerImageRect(&r, &(s->m->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(HRESULT hr, struct drawState *s)
{
	uiTableValue *value;
	int checked, enabled;
	HTHEME theme;

	if (hr != S_OK)
		return hr;
	if (s->p->checkboxModelColumn == -1)
		return S_OK;

	value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxModelColumn);
	checked = uiTableValueInt(value);
	uiFreeTableValue(value);
	enabled = uiprivTableModelCellEditable(s->model, s->iItem, s->p->checkboxEditableModelColumn);

	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(HRESULT hr, struct drawState *s)
{
	COLORREF prevText;
	int prevMode;
	RECT r;
	uiTableValue *value;
	WCHAR *wstr;

	if (hr != S_OK)
		return hr;
	if (!s->m->hasText)
		return S_OK;
	// don't draw the text underneath an edit control
	if (s->t->edit != NULL && s->t->editedItem == s->iItem && s->t->editedSubitem == s->iSubItem)
		return S_OK;

	prevText = SetTextColor(s->dc, s->textColor);
	if (prevText == CLR_INVALID) {
		logLastError(L"SetTextColor()");
		return E_FAIL;
	}
	prevMode = SetBkMode(s->dc, TRANSPARENT);
	if (prevMode == 0) {
		logLastError(L"SetBkMode()");
		return E_FAIL;
	}

	value = uiprivTableModelCellValue(s->model, s->iItem, s->p->textModelColumn);
	wstr = toUTF16(uiTableValueString(value));
	uiFreeTableValue(value);
	// These flags are a menagerie of flags from various sources:
	// guessing, the Windows 2000 source leak, various custom
	// draw examples on the web, etc.
	// TODO find the real correct flags
	if (DrawTextW(s->dc, wstr, -1, &(s->m->realTextRect), DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL) == 0) {
		uiprivFree(wstr);
		logLastError(L"DrawTextW()");
		return E_FAIL;
	}
	uiprivFree(wstr);

	// TODO decide once and for all what to compare to here and with SelectObject()
	if (SetBkMode(s->dc, prevMode) != TRANSPARENT) {
		logLastError(L"SetBkMode() prev");
		return E_FAIL;
	}
	if (SetTextColor(s->dc, prevText) != s->textColor) {
		logLastError(L"SetTextColor() prev");
		return E_FAIL;
	}
	return S_OK;
}

// much of this is to imitate what shell32.dll's CDrawProgressBar does
#define indeterminateSegments 8

static HRESULT drawProgressBarPart(HRESULT hr, struct drawState *s)
{
	int progress;
	LONG indeterminatePos;
	HTHEME theme;
	RECT r;
	RECT rBorder, rFill[2];
	int i, nFill;
	TEXTMETRICW tm;
	int sysColor;

	if (hr != S_OK)
		return hr;
	if (s->p->progressBarModelColumn == -1)
		return S_OK;

	progress = uiprivTableProgress(s->t, s->iItem, s->iSubItem, s->p->progressBarModelColumn, &indeterminatePos);

	theme = OpenThemeData(s->t->hwnd, L"PROGRESS");

	if (GetTextMetricsW(s->dc, &tm) == 0) {
		logLastError(L"GetTextMetricsW()");
		hr = E_FAIL;
		goto fail;
	}
	r = s->m->subitemBounds;
	// this sets the height of the progressbar and vertically centers it in one fell swoop
	r.top += (r.bottom - tm.tmHeight - r.top) / 2;
	r.bottom = r.top + tm.tmHeight;

	// TODO check errors
	rBorder = r;
	InflateRect(&rBorder, -1, -1);
	if (theme != NULL) {
		RECT crect;

		hr = GetThemeBackgroundContentRect(theme, s->dc,
			PP_TRANSPARENTBAR, PBBS_NORMAL,
			&rBorder, &crect);
		if (hr != S_OK) {
			logHRESULT(L"GetThemeBackgroundContentRect()", hr);
			goto fail;
		}
		hr = DrawThemeBackground(theme, s->dc,
			PP_TRANSPARENTBAR, PBBS_NORMAL,
			&crect, NULL);
		if (hr != S_OK) {
			logHRESULT(L"DrawThemeBackground() border", hr);
			goto fail;
		}
	} else {
		HPEN pen, prevPen;
		HBRUSH brush, prevBrush;

		sysColor = COLOR_HIGHLIGHT;
		if (s->m->selected)
			sysColor = COLOR_HIGHLIGHTTEXT;

		// TODO check errors everywhere
		pen = CreatePen(PS_SOLID, 1, GetSysColor(sysColor));
		prevPen = (HPEN) SelectObject(s->dc, pen);
		brush = (HBRUSH) GetStockObject(NULL_BRUSH);
		prevBrush = (HBRUSH) SelectObject(s->dc, brush);
		Rectangle(s->dc, rBorder.left, rBorder.top, rBorder.right, rBorder.bottom);
		SelectObject(s->dc, prevBrush);
		SelectObject(s->dc, prevPen);
		DeleteObject(pen);
	}

	nFill = 1;
	rFill[0] = r;
	// TODO check error
	InflateRect(&rFill[0], -1, -1);
	if (progress != -1)
		rFill[0].right -= (rFill[0].right - rFill[0].left) * (100 - progress) / 100;
	else {
		LONG barWidth;
		LONG pieceWidth;

		// TODO explain all this
		// TODO this should really start the progressbar scrolling into view instead of already on screen when first set
		rFill[1] = rFill[0];		// save in case we need it
		barWidth = rFill[0].right - rFill[0].left;
		pieceWidth = barWidth / indeterminateSegments;
		rFill[0].left += indeterminatePos % barWidth;
		if ((rFill[0].left + pieceWidth) >= rFill[0].right) {
			// make this piece wrap back around
			nFill++;
			rFill[1].right = rFill[1].left + (pieceWidth - (rFill[0].right - rFill[0].left));
		} else
			rFill[0].right = rFill[0].left + pieceWidth;
	}
	for (i = 0; i < nFill; i++)
		if (theme != NULL) {
			hr = DrawThemeBackground(theme, s->dc,
				PP_FILL, PBFS_NORMAL,
				&rFill[i], NULL);
			if (hr != S_OK) {
				logHRESULT(L"DrawThemeBackground() fill", hr);
				goto fail;
			}
		} else
			// TODO check errors
			FillRect(s->dc, &rFill[i], GetSysColorBrush(sysColor));

	hr = S_OK;
fail:
	// TODO check errors
	if (theme != NULL)
		CloseThemeData(theme);
	return hr;
}

static HRESULT drawButtonPart(HRESULT hr, struct drawState *s)
{
	uiTableValue *value;
	WCHAR *wstr;
	bool enabled;
	HTHEME theme;
	RECT r;
	TEXTMETRICW tm;

	if (hr != S_OK)
		return hr;
	if (s->p->buttonModelColumn == -1)
		return S_OK;

	value = uiprivTableModelCellValue(s->model, s->iItem, s->p->buttonModelColumn);
	wstr = toUTF16(uiTableValueString(value));
	uiFreeTableValue(value);
	enabled = uiprivTableModelCellEditable(s->model, s->iItem, s->p->buttonClickableModelColumn);

	theme = OpenThemeData(s->t->hwnd, L"button");

	if (GetTextMetricsW(s->dc, &tm) == 0) {
		logLastError(L"GetTextMetricsW()");
		hr = E_FAIL;
		goto fail;
	}
	r = s->m->subitemBounds;

	if (theme != NULL) {
		int state;

		state = PBS_NORMAL;
		if (!enabled)
			state = PBS_DISABLED;
		hr = DrawThemeBackground(theme, s->dc,
			BP_PUSHBUTTON, state,
			&r, NULL);
		if (hr != S_OK) {
			logHRESULT(L"DrawThemeBackground()", hr);
			goto fail;
		}
		// TODO DT_EDITCONTROL?
		// TODO DT_PATH_ELLIPSIS DT_WORD_ELLIPSIS instead of DT_END_ELLIPSIS? a middle-ellipsis option would be ideal here
		// TODO is there a theme property we can get instead of hardcoding these flags? if not, make these flags a macro
		hr = DrawThemeText(theme, s->dc,
			BP_PUSHBUTTON, state,
			wstr, -1,
			DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX, 0,
			&r);
		if (hr != S_OK) {
			logHRESULT(L"DrawThemeText()", hr);
			goto fail;
		}
	} else {
		UINT state;
		HBRUSH color, prevColor;
		int prevBkMode;

		// TODO check errors
		// TODO explain why we're not doing this in the themed case (it has to do with extra transparent pixels)
		InflateRect(&r, -1, -1);
		state = DFCS_BUTTONPUSH;
		if (!enabled)
			state |= DFCS_INACTIVE;
		if (DrawFrameControl(s->dc, &r, DFC_BUTTON, state) == 0) {
			logLastError(L"DrawFrameControl()");
			hr = E_FAIL;
			goto fail;
		}
		color = GetSysColorBrush(COLOR_BTNTEXT);
		// TODO check errors for these two
		prevColor = (HBRUSH) SelectObject(s->dc, color);
		prevBkMode = SetBkMode(s->dc, TRANSPARENT);
		// TODO DT_EDITCONTROL?
		// TODO DT_PATH_ELLIPSIS DT_WORD_ELLIPSIS instead of DT_END_ELLIPSIS? a middle-ellipsis option would be ideal here
		if (DrawTextW(s->dc, wstr, -1, &r, DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX) == 0) {
			logLastError(L"DrawTextW()");
			hr = E_FAIL;
			goto fail;
		}
		// TODO check errors for these two
		SetBkMode(s->dc, prevBkMode);
		SelectObject(s->dc, prevColor);
	}

	hr = S_OK;
fail:
	// TODO check errors
	if (theme != NULL)
		CloseThemeData(theme);
	uiprivFree(wstr);
	return hr;
}

static HRESULT freeDrawState(struct drawState *s)
{
	HRESULT hr, hrret;

	hrret = S_OK;
	if (s->m != NULL) {
		uiprivFree(s->m);
		s->m = NULL;
	}
	if (s->freeTextBrush) {
		if (DeleteObject(s->textBrush) == 0) {
			logLastError(L"DeleteObject()");
			hrret = E_FAIL;
			// continue cleaning up anyway
		}
		s->freeTextBrush = FALSE;
	}
	if (s->freeBgBrush) {
		if (DeleteObject(s->bgBrush) == 0) {
			logLastError(L"DeleteObject()");
			hrret = E_FAIL;
			// continue cleaning up anyway
		}
		s->freeBgBrush = FALSE;
	}
	return hrret;
}

static COLORREF blend(COLORREF base, double r, double g, double b, double a)
{
	double br, bg, bb;

	br = ((double) GetRValue(base)) / 255.0;
	bg = ((double) GetGValue(base)) / 255.0;
	bb = ((double) GetBValue(base)) / 255.0;

	br = (r * a) + (br * (1.0 - a));
	bg = (g * a) + (bg * (1.0 - a));
	bb = (b * a) + (bb * (1.0 - a));
	return RGB((BYTE) (br * 255),
		(BYTE) (bg * 255),
		(BYTE) (bb * 255));
}

static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColumnParams *p)
{
	LRESULT state;
	HWND header;
	HRESULT hr;

	ZeroMemory(s, sizeof (struct drawState));
	s->t = t;
	s->model = t->model;
	s->p = p;

	s->dc = nm->nmcd.hdc;
	s->iItem = nm->nmcd.dwItemSpec;
	s->iSubItem = nm->iSubItem;

	hr = uiprivTableGetMetrics(t, s->iItem, s->iSubItem, &(s->m));
	if (hr != S_OK)
		goto fail;

	if (s->m->selected) {
		s->bgColor = GetSysColor(COLOR_HIGHLIGHT);
		s->bgBrush = GetSysColorBrush(COLOR_HIGHLIGHT);
		s->textColor = GetSysColor(COLOR_HIGHLIGHTTEXT);
		s->textBrush = GetSysColorBrush(COLOR_HIGHLIGHTTEXT);
	} else {
		double r, g, b, a;

		s->bgColor = GetSysColor(COLOR_WINDOW);
		s->bgBrush = GetSysColorBrush(COLOR_WINDOW);
		if (uiprivTableModelColorIfProvided(s->model, s->iItem, t->backgroundColumn, &r, &g, &b, &a)) {
			s->bgColor = blend(s->bgColor, r, g, b, a);
			s->bgBrush = CreateSolidBrush(s->bgColor);
			if (s->bgBrush == NULL) {
				logLastError(L"CreateSolidBrush()");
				hr = E_FAIL;
				goto fail;
			}
			s->freeBgBrush = TRUE;
		}
		s->textColor = GetSysColor(COLOR_WINDOWTEXT);
		s->textBrush = GetSysColorBrush(COLOR_WINDOWTEXT);
		if (uiprivTableModelColorIfProvided(s->model, s->iItem, p->textParams.ColorModelColumn, &r, &g, &b, &a)) {
			s->textColor = blend(s->bgColor, r, g, b, a);
			s->textBrush = CreateSolidBrush(s->textColor);
			if (s->textBrush == NULL) {
				logLastError(L"CreateSolidBrush()");
				hr = E_FAIL;
				goto fail;
			}
			s->freeTextBrush = TRUE;
		}
	}

	return S_OK;
fail:
	// ignore the error; we need to return the one we got above
	freeDrawState(s);
	return hr;
}

static HRESULT updateAndDrawFocusRects(HRESULT hr, uiTable *t, HDC dc, int iItem, RECT *realTextBackground, RECT *focus, bool *first)
{
	LRESULT state;

	if (hr != S_OK)
		return hr;
	if (GetFocus() != t->hwnd)
		return S_OK;
	// uItemState CDIS_FOCUS doesn't quite work right because of bugs in the Windows list view that causes spurious redraws without the flag while we hover over the focused item
	// TODO only call this once
	state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, (WPARAM) iItem, (LRESULT) (LVIS_FOCUSED));
	if ((state & LVIS_FOCUSED) == 0)
		return S_OK;

	if (realTextBackground != NULL)
		if (*first) {
			*focus = *realTextBackground;
			*first = false;
			return S_OK;
		} else if (focus->right == realTextBackground->left) {
			focus->right = realTextBackground->right;
			return S_OK;
		}
	if (DrawFocusRect(dc, focus) == 0) {
		logLastError(L"DrawFocusRect()");
		return E_FAIL;
	}
	if (realTextBackground != NULL)
		*focus = *realTextBackground;
	return S_OK;
}

// normally we would only draw stuff in subitem stages
// this broke when we tried drawing focus rects in postpaint; the drawing either kept getting wiped or overdrawn, mouse hovering had something to do with it, and none of the "solutions" to this or similar problems on the internet worked
// so now we do everything in the item prepaint stage
// TODO consider switching to full-on owner draw
// TODO only compute the background brushes once?
HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult)
{
	struct drawState s;
	uiprivTableColumnParams *p;
	NMLVCUSTOMDRAW b;
	size_t i, n;
	RECT focus;
	bool focusFirst;
	HRESULT hr;

	switch (nm->nmcd.dwDrawStage) {
	case CDDS_PREPAINT:
		*lResult = CDRF_NOTIFYITEMDRAW;
		return S_OK;
	case CDDS_ITEMPREPAINT:
		break;
	default:
		*lResult = CDRF_DODEFAULT;
		return S_OK;
	}

	n = t->columns->size();
	b = *nm;
	focusFirst = true;
	for (i = 0; i < n; i++) {
		b.iSubItem = i;
		p = (*(t->columns))[i];
		hr = fillDrawState(&s, t, &b, p);
		if (hr != S_OK)
			return hr;
		hr = drawBackgrounds(S_OK, &s);
		hr = drawImagePart(hr, &s);
		hr = drawCheckboxPart(hr, &s);
		hr = drawTextPart(hr, &s);
		hr = drawProgressBarPart(hr, &s);
		hr = drawButtonPart(hr, &s);
		hr = updateAndDrawFocusRects(hr, s.t, s.dc, nm->nmcd.dwItemSpec, &(s.m->realTextBackground), &focus, &focusFirst);
		if (hr != S_OK)
			goto fail;
		hr = freeDrawState(&s);
		if (hr != S_OK)		// TODO really error out here?
			return hr;
	}
	// and draw the last focus rect
	hr = updateAndDrawFocusRects(hr, t, nm->nmcd.hdc, nm->nmcd.dwItemSpec, NULL, &focus, &focusFirst);
	if (hr != S_OK)
		return hr;
	*lResult = CDRF_SKIPDEFAULT;
	return S_OK;
fail:
	// ignore error here
	// TODO this is awkward cleanup placement for something that only really exists in a for loop
	freeDrawState(&s);
	return hr;
}

// TODO run again when the DPI or the theme changes
// TODO properly clean things up here
// TODO properly destroy the old lists here too
HRESULT uiprivUpdateImageListSize(uiTable *t)
{
	HDC dc;
	int cxList, cyList;
	HTHEME theme;
	SIZE sizeCheck;
	HRESULT hr;

	dc = GetDC(t->hwnd);
	if (dc == NULL) {
		logLastError(L"GetDC()");
		return E_FAIL;
	}

	cxList = GetSystemMetrics(SM_CXSMICON);
	cyList = GetSystemMetrics(SM_CYSMICON);
	sizeCheck.cx = cxList;
	sizeCheck.cy = cyList;
	theme = OpenThemeData(t->hwnd, L"button");
	if (theme != NULL) {
		hr = GetThemePartSize(theme, dc,
			BP_CHECKBOX, CBS_UNCHECKEDNORMAL,
			NULL, TS_DRAW, &sizeCheck);
		if (hr != S_OK) {
			logHRESULT(L"GetThemePartSize()", hr);
			return hr;			// TODO fall back?
		}
		// make sure these checkmarks fit
		// unthemed checkmarks will by the code above be smaller than cxList/cyList here
		if (cxList < sizeCheck.cx)
			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
	t->imagelist = ImageList_Create(cxList, cyList,
		ILC_COLOR32,
		1, 1);
	if (t->imagelist == 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->imagelist));

	if (ReleaseDC(t->hwnd, dc) == 0) {
		logLastError(L"ReleaseDC()");
		return E_FAIL;
	}
	return S_OK;
}