248 lines
6.8 KiB
C
248 lines
6.8 KiB
C
// 16 august 2014
|
|
|
|
enum {
|
|
checkboxStateChecked = 1 << 0,
|
|
checkboxStateHot = 1 << 1,
|
|
checkboxStatePushed = 1 << 2,
|
|
checkboxnStates = 1 << 3,
|
|
};
|
|
|
|
// TODO actually make this
|
|
#define panichresult(a, b) panic(a)
|
|
|
|
static UINT dfcState(int cbstate)
|
|
{
|
|
UINT ret;
|
|
|
|
ret = DFCS_BUTTONCHECK;
|
|
if ((cbstate & checkboxStateChecked) != 0)
|
|
ret |= DFCS_CHECKED;
|
|
if ((cbstate & checkboxStateHot) != 0)
|
|
ret |= DFCS_HOT;
|
|
if ((cbstate & checkboxStatePushed) != 0)
|
|
ret |= DFCS_PUSHED;
|
|
return ret;
|
|
}
|
|
|
|
static void drawFrameControlCheckbox(HDC dc, RECT *r, int cbState)
|
|
{
|
|
if (DrawFrameControl(dc, r, DFC_BUTTON, dfcState(cbState)) == 0)
|
|
panic("error drawing Table checkbox image with DrawFrameControl()");
|
|
}
|
|
|
|
static void getFrameControlCheckboxSize(HDC dc, int *width, int *height)
|
|
{
|
|
// there's no real metric around
|
|
// let's use SM_CX/YSMICON and hope for the best
|
|
*width = GetSystemMetrics(SM_CXSMICON);
|
|
*height = GetSystemMetrics(SM_CYSMICON);
|
|
}
|
|
|
|
static int themestates[checkboxnStates] = {
|
|
CBS_UNCHECKEDNORMAL, // 0
|
|
CBS_CHECKEDNORMAL, // checked
|
|
CBS_UNCHECKEDHOT, // hot
|
|
CBS_CHECKEDHOT, // checked | hot
|
|
CBS_UNCHECKEDPRESSED, // pushed
|
|
CBS_CHECKEDPRESSED, // checked | pushed
|
|
CBS_UNCHECKEDPRESSED, // hot | pushed
|
|
CBS_CHECKEDPRESSED, // checked | hot | pushed
|
|
};
|
|
|
|
static SIZE getStateSize(HDC dc, int cbState, HTHEME theme)
|
|
{
|
|
SIZE s;
|
|
HRESULT res;
|
|
|
|
res = GetThemePartSize(theme, dc, BP_CHECKBOX, themestates[cbState], NULL, TS_DRAW, &s);
|
|
if (res != S_OK)
|
|
panichresult("error getting theme part size for Table checkboxes", res);
|
|
return s;
|
|
}
|
|
|
|
static void drawThemeCheckbox(HDC dc, RECT *r, int cbState, HTHEME theme)
|
|
{
|
|
HRESULT res;
|
|
|
|
res = DrawThemeBackground(theme, dc, BP_CHECKBOX, themestates[cbState], r, NULL);
|
|
if (res != S_OK)
|
|
panichresult("error drawing Table checkbox image from theme", res);
|
|
}
|
|
|
|
static void getThemeCheckboxSize(HDC dc, int *width, int *height, HTHEME theme)
|
|
{
|
|
SIZE size;
|
|
int cbState;
|
|
|
|
size = getStateSize(dc, 0, theme);
|
|
for (cbState = 1; cbState < checkboxnStates; cbState++) {
|
|
SIZE against;
|
|
|
|
against = getStateSize(dc, cbState, theme);
|
|
if (size.cx != against.cx || size.cy != against.cy)
|
|
// TODO make this use a no-information (or two ints) panic()
|
|
panic("size mismatch in Table checkbox states");
|
|
}
|
|
*width = (int) size.cx;
|
|
*height = (int) size.cy;
|
|
}
|
|
|
|
static void drawCheckbox(struct table *t, HDC dc, RECT *r, int cbState)
|
|
{
|
|
if (t->theme != NULL) {
|
|
drawThemeCheckbox(dc, r, cbState, t->theme);
|
|
return;
|
|
}
|
|
drawFrameControlCheckbox(dc, r, cbState);
|
|
}
|
|
|
|
static void freeCheckboxThemeData(struct table *t)
|
|
{
|
|
if (t->theme != NULL) {
|
|
HRESULT res;
|
|
|
|
res = CloseThemeData(t->theme);
|
|
if (res != S_OK)
|
|
panichresult("error closing Table checkbox theme", res);
|
|
t->theme = NULL;
|
|
}
|
|
}
|
|
|
|
static void loadCheckboxThemeData(struct table *t)
|
|
{
|
|
HDC dc;
|
|
|
|
freeCheckboxThemeData(t);
|
|
dc = GetDC(t->hwnd);
|
|
if (dc == NULL)
|
|
panic("error getting Table DC for loading checkbox theme data");
|
|
// ignore error; if it can't be done, we can fall back to DrawFrameControl()
|
|
if (t->theme == NULL) // try to open the theme
|
|
t->theme = OpenThemeData(t->hwnd, L"button");
|
|
if (t->theme != NULL) // use the theme
|
|
getThemeCheckboxSize(dc, &(t->checkboxWidth), &(t->checkboxHeight), t->theme);
|
|
else // couldn't open; fall back
|
|
getFrameControlCheckboxSize(dc, &(t->checkboxWidth), &(t->checkboxHeight));
|
|
if (ReleaseDC(t->hwnd, dc) == 0)
|
|
panic("error releasing Table DC for loading checkbox theme data");
|
|
}
|
|
|
|
static void redrawCheckboxRect(struct table *t, LPARAM lParam)
|
|
{
|
|
struct rowcol rc;
|
|
RECT r;
|
|
|
|
rc = lParamToRowColumn(t, lParam);
|
|
if (rc.row == -1 && rc.column == -1)
|
|
return;
|
|
if (t->columnTypes[rc.column] != tableColumnCheckbox)
|
|
return;
|
|
if (!rowColumnToClientRect(t, rc, &r))
|
|
return;
|
|
// TODO only the checkbox rect?
|
|
if (InvalidateRect(t->hwnd, &r, TRUE) == 0)
|
|
panic("error redrawing Table checkbox rect for mouse events");
|
|
}
|
|
|
|
HANDLER(checkboxMouseMoveHandler)
|
|
{
|
|
// don't actually check to see if the mouse is in the checkbox rect
|
|
// if there's scrolling without mouse movement, that will change
|
|
// instead, drawCell() will handle it
|
|
if (!t->checkboxMouseOverLast) {
|
|
t->checkboxMouseOverLast = TRUE;
|
|
retrack(t);
|
|
} else
|
|
redrawCheckboxRect(t, t->checkboxMouseOverLastPoint);
|
|
t->checkboxMouseOverLastPoint = lParam;
|
|
redrawCheckboxRect(t, t->checkboxMouseOverLastPoint);
|
|
*lResult = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
HANDLER(checkboxMouseLeaveHandler)
|
|
{
|
|
if (t->checkboxMouseOverLast)
|
|
redrawCheckboxRect(t, t->checkboxMouseOverLastPoint);
|
|
// TODO remember what I wanted to do here in the case of a held mouse button
|
|
t->checkboxMouseOverLast = FALSE;
|
|
*lResult = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
HANDLER(checkboxMouseDownHandler)
|
|
{
|
|
struct rowcol rc;
|
|
RECT r;
|
|
POINT pt;
|
|
|
|
rc = lParamToRowColumn(t, lParam);
|
|
if (rc.row == -1 || rc.column == -1)
|
|
return FALSE;
|
|
if (t->columnTypes[rc.column] != tableColumnCheckbox)
|
|
return FALSE;
|
|
if (!rowColumnToClientRect(t, rc, &r))
|
|
return FALSE;
|
|
toCheckboxRect(t, &r, 0);
|
|
pt.x = GET_X_LPARAM(lParam);
|
|
pt.y = GET_Y_LPARAM(lParam);
|
|
if (PtInRect(&r, pt) == 0)
|
|
return FALSE;
|
|
t->checkboxMouseDown = TRUE;
|
|
t->checkboxMouseDownRow = rc.row;
|
|
t->checkboxMouseDownColumn = rc.column;
|
|
// TODO redraw the whole cell?
|
|
if (InvalidateRect(t->hwnd, &r, TRUE) == 0)
|
|
panic("error redrawing Table checkbox after mouse down");
|
|
*lResult = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
HANDLER(checkboxMouseUpHandler)
|
|
{
|
|
struct rowcol rc;
|
|
RECT r;
|
|
POINT pt;
|
|
|
|
if (!t->checkboxMouseDown)
|
|
return FALSE;
|
|
// the logic behind goto wrongUp is that the mouse must be released on the same checkbox
|
|
rc = lParamToRowColumn(t, lParam);
|
|
if (rc.row == -1 || rc.column == -1)
|
|
goto wrongUp;
|
|
if (rc.row != t->checkboxMouseDownRow || rc.column != t->checkboxMouseDownColumn)
|
|
goto wrongUp;
|
|
if (t->columnTypes[rc.column] != tableColumnCheckbox)
|
|
goto wrongUp;
|
|
if (!rowColumnToClientRect(t, rc, &r))
|
|
goto wrongUp;
|
|
toCheckboxRect(t, &r, 0);
|
|
pt.x = GET_X_LPARAM(lParam);
|
|
pt.y = GET_Y_LPARAM(lParam);
|
|
if (PtInRect(&r, pt) == 0)
|
|
goto wrongUp;
|
|
notify(t, tableNotificationCellCheckboxToggled, rc.row, rc.column, 0);
|
|
t->checkboxMouseDown = FALSE;
|
|
// TODO redraw the whole cell?
|
|
if (InvalidateRect(t->hwnd, &r, TRUE) == 0)
|
|
panic("error redrawing Table checkbox after mouse up");
|
|
// TODO really only the row? no way to specify column too?
|
|
NotifyWinEvent(EVENT_OBJECT_STATECHANGE, t->hwnd, OBJID_CLIENT, rc.row);
|
|
*lResult = 0;
|
|
return TRUE;
|
|
wrongUp:
|
|
if (t->checkboxMouseDown) {
|
|
rc.row = t->checkboxMouseDownRow;
|
|
rc.column = t->checkboxMouseDownColumn;
|
|
if (rowColumnToClientRect(t, rc, &r))
|
|
// TODO only the checkbox rect?
|
|
if (InvalidateRect(t->hwnd, &r, TRUE) == 0)
|
|
panic("error redrawing Table checkbox rect for aborted mouse up event");
|
|
}
|
|
// if we landed on another checkbox, be sure to draw that one too
|
|
if (t->checkboxMouseOverLast)
|
|
redrawCheckboxRect(t, t->checkboxMouseOverLastPoint);
|
|
t->checkboxMouseDown = FALSE;
|
|
return FALSE; // TODO really?
|
|
}
|