1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
|
// 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;
POINT pt;
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");
*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?
}
|