]> cygwin.com Git - cygwin-apps/setup.git/blob - ListView.cc
Custom draw checkboxes in ListView control
[cygwin-apps/setup.git] / ListView.cc
1 /*
2 * Copyright (c) 2016 Jon Turney
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * A copy of the GNU General Public License can be found at
10 * http://www.gnu.org/
11 *
12 */
13
14 #include "ListView.h"
15 #include "LogSingleton.h"
16
17 #include <commctrl.h>
18
19 // ---------------------------------------------------------------------------
20 // implements class ListView
21 //
22 // ListView Common Control
23 // ---------------------------------------------------------------------------
24
25 void
26 ListView::init(HWND parent, int id, HeaderList headers)
27 {
28 hWndParent = parent;
29
30 // locate the listview control
31 hWndListView = ::GetDlgItem(parent, id);
32
33 // configure the listview control
34 SendMessage(hWndListView, CCM_SETVERSION, 6, 0);
35
36 ListView_SetExtendedListViewStyle(hWndListView,
37 LVS_EX_COLUMNSNAPPOINTS | // use cxMin
38 LVS_EX_FULLROWSELECT |
39 LVS_EX_GRIDLINES |
40 LVS_EX_HEADERDRAGDROP); // headers can be re-ordered
41
42 // give the header control a border
43 HWND hWndHeader = ListView_GetHeader(hWndListView);
44 SetWindowLongPtr(hWndHeader, GWL_STYLE,
45 GetWindowLongPtr(hWndHeader, GWL_STYLE) | WS_BORDER);
46
47 // ensure an initial item exists for width calculations...
48 LVITEM lvi;
49 lvi.mask = LVIF_TEXT;
50 lvi.iItem = 0;
51 lvi.iSubItem = 0;
52 lvi.pszText = const_cast <char *> ("Working...");
53 ListView_InsertItem(hWndListView, &lvi);
54
55 // populate with columns
56 initColumns(headers);
57 }
58
59 void
60 ListView::initColumns(HeaderList headers_)
61 {
62 // store HeaderList for later use
63 headers = headers_;
64
65 // create the columns
66 LVCOLUMN lvc;
67 lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
68
69 int i;
70 for (i = 0; headers[i].text != 0; i++)
71 {
72 lvc.iSubItem = i;
73 lvc.pszText = const_cast <char *> (headers[i].text);
74 lvc.cx = 100;
75 lvc.fmt = headers[i].fmt;
76
77 ListView_InsertColumn(hWndListView, i, &lvc);
78 }
79
80 // now do some width calculations
81 for (i = 0; headers[i].text != 0; i++)
82 {
83 headers[i].width = 0;
84
85 ListView_SetColumnWidth(hWndListView, i, LVSCW_AUTOSIZE_USEHEADER);
86 headers[i].hdr_width = ListView_GetColumnWidth(hWndListView, i);
87 }
88 }
89
90 void
91 ListView::noteColumnWidthStart()
92 {
93 dc = GetDC (hWndListView);
94
95 // we must set the font of the DC here, otherwise the width calculations
96 // will be off because the system will use the wrong font metrics
97 HANDLE sysfont = GetStockObject (DEFAULT_GUI_FONT);
98 SelectObject (dc, sysfont);
99
100 int i;
101 for (i = 0; headers[i].text != 0; i++)
102 {
103 headers[i].width = 0;
104 }
105 }
106
107 void
108 ListView::noteColumnWidth(int col_num, const std::string& string)
109 {
110 SIZE s = { 0, 0 };
111
112 // A margin of 3*GetSystemMetrics(SM_CXEDGE) is used at each side of the
113 // header text.
114 int addend = 2*3*GetSystemMetrics(SM_CXEDGE);
115
116 if (string.size())
117 GetTextExtentPoint32 (dc, string.c_str(), string.size(), &s);
118
119 int width = addend + s.cx;
120
121 if (width > headers[col_num].width)
122 headers[col_num].width = width;
123 }
124
125 void
126 ListView::noteColumnWidthEnd()
127 {
128 ReleaseDC(hWndListView, dc);
129 }
130
131 void
132 ListView::resizeColumns(void)
133 {
134 // ensure the last column stretches all the way to the right-hand side of the
135 // listview control
136 int i;
137 int total = 0;
138 for (i = 0; headers[i].text != 0; i++)
139 total = total + headers[i].width;
140
141 RECT r;
142 GetClientRect(hWndListView, &r);
143 int width = r.right - r.left;
144
145 if (total < width)
146 headers[i-1].width += width - total;
147
148 // size each column
149 LVCOLUMN lvc;
150 lvc.mask = LVCF_WIDTH | LVCF_MINWIDTH;
151 for (i = 0; headers[i].text != 0; i++)
152 {
153 lvc.iSubItem = i;
154 lvc.cx = (headers[i].width < headers[i].hdr_width) ? headers[i].hdr_width : headers[i].width;
155 lvc.cxMin = headers[i].hdr_width;
156 #if DEBUG
157 Log (LOG_BABBLE) << "resizeColumns: " << i << " cx " << lvc.cx << " cxMin " << lvc.cxMin <<endLog;
158 #endif
159
160 ListView_SetColumn(hWndListView, i, &lvc);
161 }
162 }
163
164 void
165 ListView::setContents(ListViewContents *_contents)
166 {
167 contents = _contents;
168
169 // disable redrawing of ListView
170 // (otherwise it will redraw every time a row is added, which makes this very slow)
171 SendMessage(hWndListView, WM_SETREDRAW, FALSE, 0);
172
173 // preserve focus/selection
174 int iRow = ListView_GetSelectionMark(hWndListView);
175
176 empty();
177
178 size_t i;
179 for (i = 0; i < contents->size(); i++)
180 {
181 LVITEM lvi;
182 lvi.mask = LVIF_TEXT;
183 lvi.iItem = i;
184 lvi.iSubItem = 0;
185 lvi.pszText = LPSTR_TEXTCALLBACK;
186
187 ListView_InsertItem(hWndListView, &lvi);
188 }
189
190 if (iRow >= 0)
191 {
192 ListView_SetItemState(hWndListView, iRow, LVNI_SELECTED | LVNI_FOCUSED, LVNI_SELECTED | LVNI_FOCUSED);
193 ListView_EnsureVisible(hWndListView, iRow, false);
194 }
195
196 // enable redrawing of ListView and redraw
197 SendMessage(hWndListView, WM_SETREDRAW, TRUE, 0);
198 RedrawWindow(hWndListView, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
199 }
200
201 // Helper class: The char * pointer we hand back needs to remain valid for some
202 // time after OnNotify returns, when the std::string we have retrieved has gone
203 // out of scope, so a static instance of this class maintains a local cache.
204 class StringCache
205 {
206 public:
207 StringCache() : cache(NULL), cache_size(0) { }
208 StringCache & operator = (const std::string & s)
209 {
210 if ((s.length() + 1) > cache_size)
211 {
212 cache_size = s.length() + 1;
213 cache = (char *)realloc(cache, cache_size);
214 }
215 strcpy(cache, s.c_str());
216 return *this;
217 }
218 operator char *() const
219 {
220 return cache;
221 }
222 private:
223 char *cache;
224 size_t cache_size;
225 };
226
227 bool
228 ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult)
229 {
230 #if DEBUG
231 Log (LOG_BABBLE) << "ListView::OnNotify id:" << pNmHdr->idFrom << " hwnd:" << pNmHdr->hwndFrom << " code:" << (int)pNmHdr->code << endLog;
232 #endif
233
234 switch (pNmHdr->code)
235 {
236 case LVN_GETDISPINFO:
237 {
238 NMLVDISPINFO *pNmLvDispInfo = (NMLVDISPINFO *)pNmHdr;
239 #if DEBUG
240 Log (LOG_BABBLE) << "LVN_GETDISPINFO " << pNmLvDispInfo->item.iItem << endLog;
241 #endif
242 if (contents)
243 {
244 int iRow = pNmLvDispInfo->item.iItem;
245 int iCol = pNmLvDispInfo->item.iSubItem;
246
247 static StringCache s;
248 s = (*contents)[iRow]->get_text(iCol);
249 pNmLvDispInfo->item.pszText = s;
250 }
251
252 return true;
253 }
254 break;
255
256 case LVN_GETEMPTYMARKUP:
257 {
258 NMLVEMPTYMARKUP *pNmMarkup = (NMLVEMPTYMARKUP*) pNmHdr;
259
260 MultiByteToWideChar(CP_UTF8, 0,
261 empty_list_text, -1,
262 pNmMarkup->szMarkup, L_MAX_URL_LENGTH);
263
264 *pResult = true;
265 return true;
266 }
267 break;
268
269 case NM_CLICK:
270 {
271 NMITEMACTIVATE *pNmItemAct = (NMITEMACTIVATE *) pNmHdr;
272 #if DEBUG
273 Log (LOG_BABBLE) << "NM_CLICK: pnmitem->iItem " << pNmItemAct->iItem << " pNmItemAct->iSubItem " << pNmItemAct->iSubItem << endLog;
274 #endif
275 int iRow = pNmItemAct->iItem;
276 int iCol = pNmItemAct->iSubItem;
277
278 if (iRow >= 0)
279 {
280 // Inform the item of the click
281 int update = (*contents)[iRow]->do_action(iCol);
282
283 // Update items, if needed
284 if (update > 0)
285 {
286 ListView_RedrawItems(hWndListView, iRow, iRow + update -1);
287 }
288 }
289 return true;
290 }
291 break;
292
293 case NM_CUSTOMDRAW:
294 {
295 NMLVCUSTOMDRAW *pNmLvCustomDraw = (NMLVCUSTOMDRAW *)pNmHdr;
296
297 switch(pNmLvCustomDraw->nmcd.dwDrawStage)
298 {
299 case CDDS_PREPAINT:
300 *pResult = CDRF_NOTIFYITEMDRAW;
301 return true;
302 case CDDS_ITEMPREPAINT:
303 *pResult = CDRF_NOTIFYSUBITEMDRAW;
304 return true;
305 case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
306 {
307 LRESULT result = CDRF_DODEFAULT;
308 int iCol = pNmLvCustomDraw->iSubItem;
309 int iRow = pNmLvCustomDraw->nmcd.dwItemSpec;
310
311 switch (headers[iCol].type)
312 {
313 default:
314 case ListView::ControlType::text:
315 result = CDRF_DODEFAULT;
316 break;
317
318 case ListView::ControlType::checkbox:
319 {
320 // get the subitem text
321 char buf[3];
322 ListView_GetItemText(hWndListView, iRow, iCol, buf, _countof(buf));
323
324 // map the subitem text to a checkbox state
325 UINT state = DFCS_BUTTONCHECK | DFCS_FLAT;
326 if (buf[0] == '\0') // empty
327 {
328 result = CDRF_DODEFAULT;
329 break;
330 }
331 else if (buf[0] == 'y') // yes
332 state |= DFCS_CHECKED;
333 else if ((buf[0] == 'n') && (buf[1] == 'o')) // no
334 state |= 0;
335 else // n/a
336 state |= DFCS_INACTIVE;
337
338 // erase and draw a checkbox
339 RECT r;
340 ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r);
341 HBRUSH hBrush = CreateSolidBrush(ListView_GetBkColor(hWndListView));
342 FillRect(pNmLvCustomDraw->nmcd.hdc, &r, hBrush);
343 DeleteObject(hBrush);
344 DrawFrameControl(pNmLvCustomDraw->nmcd.hdc, &r, DFC_BUTTON, state);
345
346 result = CDRF_SKIPDEFAULT;
347 }
348 break;
349 }
350 *pResult = result;
351 return true;
352 }
353 }
354 }
355 }
356
357 // We don't care.
358 return false;
359 }
360
361 void
362 ListView::empty(void)
363 {
364 ListView_DeleteAllItems(hWndListView);
365 }
366
367 void
368 ListView::setEmptyText(const char *text)
369 {
370 empty_list_text = text;
371 }
This page took 0.053228 seconds and 6 git commands to generate.