andlabs-ui/wintable/checkboxes.h

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?
}