]>
Commit | Line | Data |
---|---|---|
ce9f6dd0 JT |
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" | |
db1c5abc | 16 | #include "resource.h" |
f34a20e7 | 17 | #include "String++.h" |
ce9f6dd0 JT |
18 | |
19 | #include <commctrl.h> | |
20 | ||
21 | // --------------------------------------------------------------------------- | |
22 | // implements class ListView | |
23 | // | |
24 | // ListView Common Control | |
25 | // --------------------------------------------------------------------------- | |
26 | ||
27 | void | |
28 | ListView::init(HWND parent, int id, HeaderList headers) | |
29 | { | |
30 | hWndParent = parent; | |
31 | ||
32 | // locate the listview control | |
33 | hWndListView = ::GetDlgItem(parent, id); | |
34 | ||
35 | // configure the listview control | |
36 | SendMessage(hWndListView, CCM_SETVERSION, 6, 0); | |
37 | ||
38 | ListView_SetExtendedListViewStyle(hWndListView, | |
39 | LVS_EX_COLUMNSNAPPOINTS | // use cxMin | |
40 | LVS_EX_FULLROWSELECT | | |
41 | LVS_EX_GRIDLINES | | |
42 | LVS_EX_HEADERDRAGDROP); // headers can be re-ordered | |
43 | ||
44 | // give the header control a border | |
45 | HWND hWndHeader = ListView_GetHeader(hWndListView); | |
46 | SetWindowLongPtr(hWndHeader, GWL_STYLE, | |
47 | GetWindowLongPtr(hWndHeader, GWL_STYLE) | WS_BORDER); | |
48 | ||
49 | // ensure an initial item exists for width calculations... | |
50 | LVITEM lvi; | |
51 | lvi.mask = LVIF_TEXT; | |
52 | lvi.iItem = 0; | |
53 | lvi.iSubItem = 0; | |
54 | lvi.pszText = const_cast <char *> ("Working..."); | |
55 | ListView_InsertItem(hWndListView, &lvi); | |
56 | ||
57 | // populate with columns | |
58 | initColumns(headers); | |
db1c5abc | 59 | |
68487c7c | 60 | // create a small icon imagelist |
db1c5abc | 61 | // (the order of images matches ListViewLine::State enum) |
68487c7c JT |
62 | hImgList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), |
63 | GetSystemMetrics(SM_CYSMICON), | |
64 | ILC_COLOR32, 2, 0); | |
db1c5abc JT |
65 | ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_PLUS))); |
66 | ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_MINUS))); | |
68487c7c JT |
67 | |
68 | // create an empty imagelist, used to reset the indent | |
69 | hEmptyImgList = ImageList_Create(1, 1, | |
70 | ILC_COLOR32, 2, 0); | |
800274fd JT |
71 | |
72 | // LVS_EX_INFOTIP/LVN_GETINFOTIP doesn't work for subitems, so we have to do | |
73 | // our own tooltip handling | |
74 | hWndTip = CreateWindowEx (0, | |
75 | (LPCTSTR) TOOLTIPS_CLASS, | |
76 | NULL, | |
77 | WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, | |
78 | CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, | |
79 | hWndParent, | |
80 | (HMENU) 0, | |
81 | GetModuleHandle(NULL), | |
82 | NULL); | |
83 | // must be topmost so that tooltips will display on top | |
84 | SetWindowPos(hWndTip, HWND_TOPMOST,0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); | |
85 | ||
86 | TOOLINFO ti; | |
87 | memset ((void *)&ti, 0, sizeof(ti)); | |
88 | ti.cbSize = sizeof(ti); | |
89 | ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS; | |
90 | ti.hwnd = hWndParent; | |
91 | ti.uId = (UINT_PTR)hWndListView; | |
92 | ti.lpszText = LPSTR_TEXTCALLBACK; // use TTN_GETDISPINFO | |
93 | SendMessage(hWndTip, TTM_ADDTOOL, 0, (LPARAM)&ti); | |
94 | ||
95 | // match long delay for tooltip to disappear used elsewhere (30s) | |
96 | SendMessage(hWndTip, TTM_SETDELAYTIME, TTDT_AUTOPOP, (LPARAM) MAKELONG (30000, 0)); | |
97 | // match tip width used elsewhere | |
98 | SendMessage(hWndTip, TTM_SETMAXTIPWIDTH, 0, 450); | |
f34a20e7 JT |
99 | |
100 | // switch to using wide-char WM_NOTIFY messages | |
101 | ListView_SetUnicodeFormat(hWndListView, TRUE); | |
ce9f6dd0 JT |
102 | } |
103 | ||
104 | void | |
105 | ListView::initColumns(HeaderList headers_) | |
106 | { | |
107 | // store HeaderList for later use | |
108 | headers = headers_; | |
109 | ||
110 | // create the columns | |
111 | LVCOLUMN lvc; | |
112 | lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; | |
113 | ||
114 | int i; | |
115 | for (i = 0; headers[i].text != 0; i++) | |
116 | { | |
117 | lvc.iSubItem = i; | |
118 | lvc.pszText = const_cast <char *> (headers[i].text); | |
119 | lvc.cx = 100; | |
120 | lvc.fmt = headers[i].fmt; | |
121 | ||
122 | ListView_InsertColumn(hWndListView, i, &lvc); | |
123 | } | |
124 | ||
125 | // now do some width calculations | |
126 | for (i = 0; headers[i].text != 0; i++) | |
127 | { | |
128 | headers[i].width = 0; | |
129 | ||
130 | ListView_SetColumnWidth(hWndListView, i, LVSCW_AUTOSIZE_USEHEADER); | |
131 | headers[i].hdr_width = ListView_GetColumnWidth(hWndListView, i); | |
132 | } | |
133 | } | |
134 | ||
135 | void | |
136 | ListView::noteColumnWidthStart() | |
137 | { | |
138 | dc = GetDC (hWndListView); | |
139 | ||
140 | // we must set the font of the DC here, otherwise the width calculations | |
141 | // will be off because the system will use the wrong font metrics | |
142 | HANDLE sysfont = GetStockObject (DEFAULT_GUI_FONT); | |
143 | SelectObject (dc, sysfont); | |
144 | ||
145 | int i; | |
146 | for (i = 0; headers[i].text != 0; i++) | |
147 | { | |
148 | headers[i].width = 0; | |
149 | } | |
150 | } | |
151 | ||
152 | void | |
153 | ListView::noteColumnWidth(int col_num, const std::string& string) | |
154 | { | |
155 | SIZE s = { 0, 0 }; | |
156 | ||
157 | // A margin of 3*GetSystemMetrics(SM_CXEDGE) is used at each side of the | |
158 | // header text. | |
159 | int addend = 2*3*GetSystemMetrics(SM_CXEDGE); | |
160 | ||
161 | if (string.size()) | |
162 | GetTextExtentPoint32 (dc, string.c_str(), string.size(), &s); | |
163 | ||
164 | int width = addend + s.cx; | |
165 | ||
d5a81cbe JT |
166 | // allow for width of dropdown button in popup columns |
167 | if (headers[col_num].type == ListView::ControlType::popup) | |
168 | { | |
169 | width += GetSystemMetrics(SM_CXVSCROLL); | |
170 | } | |
171 | ||
ce9f6dd0 JT |
172 | if (width > headers[col_num].width) |
173 | headers[col_num].width = width; | |
174 | } | |
175 | ||
176 | void | |
177 | ListView::noteColumnWidthEnd() | |
178 | { | |
179 | ReleaseDC(hWndListView, dc); | |
180 | } | |
181 | ||
182 | void | |
183 | ListView::resizeColumns(void) | |
184 | { | |
185 | // ensure the last column stretches all the way to the right-hand side of the | |
186 | // listview control | |
187 | int i; | |
188 | int total = 0; | |
189 | for (i = 0; headers[i].text != 0; i++) | |
190 | total = total + headers[i].width; | |
191 | ||
192 | RECT r; | |
193 | GetClientRect(hWndListView, &r); | |
194 | int width = r.right - r.left; | |
195 | ||
196 | if (total < width) | |
197 | headers[i-1].width += width - total; | |
198 | ||
199 | // size each column | |
200 | LVCOLUMN lvc; | |
201 | lvc.mask = LVCF_WIDTH | LVCF_MINWIDTH; | |
202 | for (i = 0; headers[i].text != 0; i++) | |
203 | { | |
204 | lvc.iSubItem = i; | |
205 | lvc.cx = (headers[i].width < headers[i].hdr_width) ? headers[i].hdr_width : headers[i].width; | |
206 | lvc.cxMin = headers[i].hdr_width; | |
207 | #if DEBUG | |
208 | Log (LOG_BABBLE) << "resizeColumns: " << i << " cx " << lvc.cx << " cxMin " << lvc.cxMin <<endLog; | |
209 | #endif | |
210 | ||
211 | ListView_SetColumn(hWndListView, i, &lvc); | |
212 | } | |
213 | } | |
214 | ||
215 | void | |
68487c7c | 216 | ListView::setContents(ListViewContents *_contents, bool tree) |
ce9f6dd0 JT |
217 | { |
218 | contents = _contents; | |
219 | ||
220 | // disable redrawing of ListView | |
221 | // (otherwise it will redraw every time a row is added, which makes this very slow) | |
222 | SendMessage(hWndListView, WM_SETREDRAW, FALSE, 0); | |
223 | ||
224 | // preserve focus/selection | |
225 | int iRow = ListView_GetSelectionMark(hWndListView); | |
226 | ||
227 | empty(); | |
228 | ||
68487c7c JT |
229 | // assign imagelist to listview control (this also sets the size for indents) |
230 | if (tree) | |
231 | ListView_SetImageList(hWndListView, hImgList, LVSIL_SMALL); | |
232 | else | |
233 | ListView_SetImageList(hWndListView, hEmptyImgList, LVSIL_SMALL); | |
234 | ||
ce9f6dd0 JT |
235 | size_t i; |
236 | for (i = 0; i < contents->size(); i++) | |
237 | { | |
238 | LVITEM lvi; | |
68487c7c | 239 | lvi.mask = LVIF_TEXT | (tree ? LVIF_IMAGE | LVIF_INDENT : 0); |
ce9f6dd0 JT |
240 | lvi.iItem = i; |
241 | lvi.iSubItem = 0; | |
242 | lvi.pszText = LPSTR_TEXTCALLBACK; | |
68487c7c JT |
243 | if (tree) |
244 | { | |
245 | lvi.iImage = I_IMAGECALLBACK; | |
246 | lvi.iIndent = (*contents)[i]->get_indent(); | |
247 | } | |
ce9f6dd0 JT |
248 | |
249 | ListView_InsertItem(hWndListView, &lvi); | |
250 | } | |
251 | ||
252 | if (iRow >= 0) | |
253 | { | |
254 | ListView_SetItemState(hWndListView, iRow, LVNI_SELECTED | LVNI_FOCUSED, LVNI_SELECTED | LVNI_FOCUSED); | |
255 | ListView_EnsureVisible(hWndListView, iRow, false); | |
256 | } | |
257 | ||
258 | // enable redrawing of ListView and redraw | |
259 | SendMessage(hWndListView, WM_SETREDRAW, TRUE, 0); | |
260 | RedrawWindow(hWndListView, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); | |
261 | } | |
262 | ||
f34a20e7 JT |
263 | // Helper class: The pointer we hand back needs to remain valid for some time |
264 | // after OnNotify returns, when the string object we have retrieved has gone out | |
265 | // of scope, so a static instance of this class maintains a local cache. | |
266 | template <class T> class StringCache | |
ce9f6dd0 | 267 | { |
f34a20e7 | 268 | typedef typename T::traits_type::char_type char_type; |
ce9f6dd0 JT |
269 | public: |
270 | StringCache() : cache(NULL), cache_size(0) { } | |
f34a20e7 | 271 | StringCache & operator = (const T & s) |
ce9f6dd0 JT |
272 | { |
273 | if ((s.length() + 1) > cache_size) | |
274 | { | |
275 | cache_size = s.length() + 1; | |
f34a20e7 | 276 | cache = (char_type *)realloc(cache, cache_size * sizeof(char_type)); |
ce9f6dd0 | 277 | } |
f34a20e7 | 278 | memcpy(cache, s.c_str(), cache_size * sizeof(char_type)); |
ce9f6dd0 JT |
279 | return *this; |
280 | } | |
f34a20e7 | 281 | operator char_type *() const |
ce9f6dd0 JT |
282 | { |
283 | return cache; | |
284 | } | |
285 | private: | |
f34a20e7 | 286 | char_type *cache; |
ce9f6dd0 JT |
287 | size_t cache_size; |
288 | }; | |
289 | ||
290 | bool | |
291 | ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult) | |
292 | { | |
293 | #if DEBUG | |
294 | Log (LOG_BABBLE) << "ListView::OnNotify id:" << pNmHdr->idFrom << " hwnd:" << pNmHdr->hwndFrom << " code:" << (int)pNmHdr->code << endLog; | |
295 | #endif | |
296 | ||
297 | switch (pNmHdr->code) | |
298 | { | |
f34a20e7 | 299 | case LVN_GETDISPINFOW: |
ce9f6dd0 | 300 | { |
f34a20e7 | 301 | NMLVDISPINFOW *pNmLvDispInfo = (NMLVDISPINFOW *)pNmHdr; |
ce9f6dd0 JT |
302 | #if DEBUG |
303 | Log (LOG_BABBLE) << "LVN_GETDISPINFO " << pNmLvDispInfo->item.iItem << endLog; | |
304 | #endif | |
305 | if (contents) | |
306 | { | |
307 | int iRow = pNmLvDispInfo->item.iItem; | |
308 | int iCol = pNmLvDispInfo->item.iSubItem; | |
309 | ||
f34a20e7 | 310 | static StringCache<std::wstring> s; |
ce9f6dd0 JT |
311 | s = (*contents)[iRow]->get_text(iCol); |
312 | pNmLvDispInfo->item.pszText = s; | |
db1c5abc JT |
313 | |
314 | if (pNmLvDispInfo->item.iSubItem == 0) | |
315 | { | |
316 | pNmLvDispInfo->item.iImage = (int)((*contents)[pNmLvDispInfo->item.iItem]->get_state()); | |
317 | } | |
ce9f6dd0 JT |
318 | } |
319 | ||
320 | return true; | |
321 | } | |
322 | break; | |
323 | ||
324 | case LVN_GETEMPTYMARKUP: | |
325 | { | |
326 | NMLVEMPTYMARKUP *pNmMarkup = (NMLVEMPTYMARKUP*) pNmHdr; | |
327 | ||
328 | MultiByteToWideChar(CP_UTF8, 0, | |
329 | empty_list_text, -1, | |
330 | pNmMarkup->szMarkup, L_MAX_URL_LENGTH); | |
331 | ||
332 | *pResult = true; | |
333 | return true; | |
334 | } | |
335 | break; | |
336 | ||
337 | case NM_CLICK: | |
338 | { | |
339 | NMITEMACTIVATE *pNmItemAct = (NMITEMACTIVATE *) pNmHdr; | |
340 | #if DEBUG | |
341 | Log (LOG_BABBLE) << "NM_CLICK: pnmitem->iItem " << pNmItemAct->iItem << " pNmItemAct->iSubItem " << pNmItemAct->iSubItem << endLog; | |
342 | #endif | |
343 | int iRow = pNmItemAct->iItem; | |
344 | int iCol = pNmItemAct->iSubItem; | |
4018914c JT |
345 | if (iRow < 0) |
346 | return false; | |
ce9f6dd0 | 347 | |
4018914c JT |
348 | int update = 0; |
349 | ||
350 | if (headers[iCol].type == ListView::ControlType::popup) | |
351 | { | |
352 | POINT p; | |
4018914c JT |
353 | GetCursorPos(&p); |
354 | ||
76dc99c1 JT |
355 | RECT r; |
356 | ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r); | |
357 | POINT cp = p; | |
358 | ::ScreenToClient(hWndListView, &cp); | |
359 | ||
360 | // if the click isn't over the pop-up button, do nothing yet (but this | |
361 | // might be followed by a NM_DBLCLK) | |
362 | if (cp.x < r.right - GetSystemMetrics(SM_CXVSCROLL)) | |
363 | return true; | |
364 | ||
365 | // position pop-up menu at the location of the click | |
4018914c JT |
366 | update = popup_menu(iRow, iCol, p); |
367 | } | |
368 | else | |
ce9f6dd0 JT |
369 | { |
370 | // Inform the item of the click | |
4018914c JT |
371 | update = (*contents)[iRow]->do_action(iCol, 0); |
372 | } | |
ce9f6dd0 | 373 | |
4018914c JT |
374 | // Update items, if needed |
375 | if (update > 0) | |
376 | { | |
377 | ListView_RedrawItems(hWndListView, iRow, iRow + update -1); | |
ce9f6dd0 | 378 | } |
4018914c | 379 | |
ce9f6dd0 JT |
380 | return true; |
381 | } | |
382 | break; | |
61183f15 | 383 | |
76dc99c1 JT |
384 | case NM_DBLCLK: |
385 | { | |
386 | NMITEMACTIVATE *pNmItemAct = (NMITEMACTIVATE *) pNmHdr; | |
387 | #if DEBUG | |
388 | Log (LOG_BABBLE) << "NM_DBLCLICK: pnmitem->iItem " << pNmItemAct->iItem << " pNmItemAct->iSubItem " << pNmItemAct->iSubItem << endLog; | |
389 | #endif | |
390 | int iRow = pNmItemAct->iItem; | |
391 | int iCol = pNmItemAct->iSubItem; | |
392 | if (iRow < 0) | |
393 | return false; | |
394 | ||
395 | int update = 0; | |
396 | ||
397 | // Inform the item of the double-click | |
398 | update = (*contents)[iRow]->do_default_action(iCol ); | |
399 | ||
400 | // Update items, if needed | |
401 | if (update > 0) | |
402 | { | |
403 | ListView_RedrawItems(hWndListView, iRow, iRow + update -1); | |
404 | } | |
405 | ||
406 | return true; | |
407 | } | |
408 | break; | |
409 | ||
61183f15 JT |
410 | case NM_CUSTOMDRAW: |
411 | { | |
412 | NMLVCUSTOMDRAW *pNmLvCustomDraw = (NMLVCUSTOMDRAW *)pNmHdr; | |
413 | ||
414 | switch(pNmLvCustomDraw->nmcd.dwDrawStage) | |
415 | { | |
416 | case CDDS_PREPAINT: | |
417 | *pResult = CDRF_NOTIFYITEMDRAW; | |
418 | return true; | |
419 | case CDDS_ITEMPREPAINT: | |
420 | *pResult = CDRF_NOTIFYSUBITEMDRAW; | |
421 | return true; | |
422 | case CDDS_SUBITEM | CDDS_ITEMPREPAINT: | |
423 | { | |
424 | LRESULT result = CDRF_DODEFAULT; | |
425 | int iCol = pNmLvCustomDraw->iSubItem; | |
426 | int iRow = pNmLvCustomDraw->nmcd.dwItemSpec; | |
427 | ||
428 | switch (headers[iCol].type) | |
429 | { | |
430 | default: | |
431 | case ListView::ControlType::text: | |
432 | result = CDRF_DODEFAULT; | |
433 | break; | |
434 | ||
435 | case ListView::ControlType::checkbox: | |
436 | { | |
f34a20e7 | 437 | // get the subitem text (as ASCII) |
61183f15 JT |
438 | char buf[3]; |
439 | ListView_GetItemText(hWndListView, iRow, iCol, buf, _countof(buf)); | |
440 | ||
441 | // map the subitem text to a checkbox state | |
442 | UINT state = DFCS_BUTTONCHECK | DFCS_FLAT; | |
443 | if (buf[0] == '\0') // empty | |
444 | { | |
445 | result = CDRF_DODEFAULT; | |
446 | break; | |
447 | } | |
448 | else if (buf[0] == 'y') // yes | |
449 | state |= DFCS_CHECKED; | |
450 | else if ((buf[0] == 'n') && (buf[1] == 'o')) // no | |
451 | state |= 0; | |
452 | else // n/a | |
453 | state |= DFCS_INACTIVE; | |
454 | ||
455 | // erase and draw a checkbox | |
456 | RECT r; | |
457 | ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r); | |
05edbf21 JT |
458 | DWORD bkg_color; |
459 | if (pNmLvCustomDraw->nmcd.uItemState & CDIS_SELECTED) | |
460 | bkg_color = GetSysColor(COLOR_HIGHLIGHT); | |
461 | else | |
462 | bkg_color = ListView_GetBkColor(hWndListView); | |
463 | HBRUSH hBrush = CreateSolidBrush(bkg_color); | |
61183f15 JT |
464 | FillRect(pNmLvCustomDraw->nmcd.hdc, &r, hBrush); |
465 | DeleteObject(hBrush); | |
466 | DrawFrameControl(pNmLvCustomDraw->nmcd.hdc, &r, DFC_BUTTON, state); | |
467 | ||
468 | result = CDRF_SKIPDEFAULT; | |
469 | } | |
470 | break; | |
4018914c JT |
471 | |
472 | case ListView::ControlType::popup: | |
473 | { | |
474 | // let the control draw the text, but notify us afterwards | |
475 | result = CDRF_NOTIFYPOSTPAINT; | |
476 | } | |
477 | break; | |
478 | } | |
479 | ||
480 | *pResult = result; | |
481 | return true; | |
482 | } | |
483 | case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT: | |
484 | { | |
485 | LRESULT result = CDRF_DODEFAULT; | |
486 | int iCol = pNmLvCustomDraw->iSubItem; | |
487 | int iRow = pNmLvCustomDraw->nmcd.dwItemSpec; | |
488 | ||
489 | switch (headers[iCol].type) | |
490 | { | |
491 | default: | |
492 | result = CDRF_DODEFAULT; | |
493 | break; | |
494 | ||
495 | case ListView::ControlType::popup: | |
496 | { | |
497 | // draw the control at the RHS of the cell | |
498 | RECT r; | |
499 | ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r); | |
500 | r.left = r.right - GetSystemMetrics(SM_CXVSCROLL); | |
501 | DrawFrameControl(pNmLvCustomDraw->nmcd.hdc, &r, DFC_SCROLL,DFCS_SCROLLCOMBOBOX); | |
502 | ||
503 | result = CDRF_DODEFAULT; | |
504 | } | |
505 | break; | |
61183f15 JT |
506 | } |
507 | *pResult = result; | |
508 | return true; | |
509 | } | |
510 | } | |
511 | } | |
800274fd JT |
512 | break; |
513 | ||
514 | case LVN_HOTTRACK: | |
515 | { | |
516 | NMLISTVIEW *pNmListView = (NMLISTVIEW *)pNmHdr; | |
517 | int iRow = pNmListView->iItem; | |
518 | int iCol = pNmListView->iSubItem; | |
519 | #if DEBUG | |
520 | Log (LOG_BABBLE) << "LVN_HOTTRACK " << iRow << " " << iCol << endLog; | |
521 | #endif | |
522 | if (iRow < 0) | |
523 | return true; | |
524 | ||
525 | // if we've tracked off to a different cell | |
526 | if ((iRow != iRow_track) || (iCol != iCol_track)) | |
527 | { | |
528 | #if DEBUG | |
529 | Log (LOG_BABBLE) << "LVN_HOTTRACK changed cell" << endLog; | |
530 | #endif | |
531 | ||
532 | // if the tooltip for previous cell is displayed, remove it | |
533 | // restart the tooltip AUTOPOP timer for this cell | |
534 | SendMessage(hWndTip, TTM_ACTIVATE, FALSE, 0); | |
535 | SendMessage(hWndTip, TTM_ACTIVATE, TRUE, 0); | |
536 | ||
537 | iRow_track = iRow; | |
538 | iCol_track = iCol; | |
539 | } | |
540 | ||
541 | return true; | |
542 | } | |
543 | break; | |
544 | ||
22e01f51 JT |
545 | case LVN_KEYDOWN: |
546 | { | |
547 | NMLVKEYDOWN *pNmLvKeyDown = (NMLVKEYDOWN *)pNmHdr; | |
548 | int iRow = ListView_GetSelectionMark(hWndListView); | |
549 | #if DEBUG | |
550 | Log (LOG_PLAIN) << "LVN_KEYDOWN vkey " << pNmLvKeyDown->wVKey << " on row " << iRow << endLog; | |
551 | #endif | |
552 | ||
553 | if (contents && iRow >= 0) | |
554 | { | |
555 | int col_num; | |
556 | int action_id; | |
557 | if ((*contents)[iRow]->map_key_to_action(pNmLvKeyDown->wVKey, &col_num, &action_id)) | |
558 | { | |
559 | int update; | |
560 | if (action_id >= 0) | |
561 | update = (*contents)[iRow]->do_action(col_num, action_id); | |
562 | else | |
563 | { | |
564 | POINT p; | |
565 | RECT r; | |
566 | ListView_GetSubItemRect(hWndListView, iRow, col_num, LVIR_BOUNDS, &r); | |
567 | p.x = r.left; | |
568 | p.y = r.top; | |
569 | ClientToScreen(hWndListView, &p); | |
570 | ||
571 | update = popup_menu(iRow, col_num, p); | |
572 | } | |
573 | ||
574 | if (update > 0) | |
575 | ListView_RedrawItems(hWndListView, iRow, iRow + update -1); | |
576 | } | |
577 | } | |
578 | } | |
579 | break; | |
580 | ||
800274fd JT |
581 | case TTN_GETDISPINFO: |
582 | { | |
583 | // convert mouse position to item/subitem | |
584 | LVHITTESTINFO lvHitTestInfo; | |
585 | lvHitTestInfo.flags = LVHT_ONITEM; | |
586 | GetCursorPos(&lvHitTestInfo.pt); | |
587 | ::ScreenToClient(hWndListView, &lvHitTestInfo.pt); | |
588 | ListView_SubItemHitTest(hWndListView, &lvHitTestInfo); | |
589 | ||
590 | int iRow = lvHitTestInfo.iItem; | |
591 | int iCol = lvHitTestInfo.iSubItem; | |
592 | if (iRow < 0) | |
593 | return false; | |
594 | ||
595 | #if DEBUG | |
596 | Log (LOG_BABBLE) << "TTN_GETDISPINFO " << iRow << " " << iCol << endLog; | |
597 | #endif | |
598 | ||
599 | // get the tooltip text for that item/subitem | |
f34a20e7 | 600 | static StringCache<std::string> tooltip; |
800274fd JT |
601 | tooltip = ""; |
602 | if (contents) | |
603 | tooltip = (*contents)[iRow]->get_tooltip(iCol); | |
604 | ||
605 | // set the tooltip text | |
606 | NMTTDISPINFO *pNmTTDispInfo = (NMTTDISPINFO *)pNmHdr; | |
607 | pNmTTDispInfo->lpszText = tooltip; | |
608 | pNmTTDispInfo->hinst = NULL; | |
609 | pNmTTDispInfo->uFlags = 0; | |
610 | ||
611 | return true; | |
612 | } | |
613 | break; | |
ce9f6dd0 JT |
614 | } |
615 | ||
616 | // We don't care. | |
617 | return false; | |
618 | } | |
619 | ||
620 | void | |
621 | ListView::empty(void) | |
622 | { | |
623 | ListView_DeleteAllItems(hWndListView); | |
624 | } | |
625 | ||
626 | void | |
627 | ListView::setEmptyText(const char *text) | |
628 | { | |
629 | empty_list_text = text; | |
630 | } | |
4018914c JT |
631 | |
632 | int | |
633 | ListView::popup_menu(int iRow, int iCol, POINT p) | |
634 | { | |
635 | int update = 0; | |
636 | // construct menu | |
637 | HMENU hMenu = CreatePopupMenu(); | |
638 | ||
f34a20e7 | 639 | MENUITEMINFOW mii; |
4018914c JT |
640 | memset(&mii, 0, sizeof(mii)); |
641 | mii.cbSize = sizeof(mii); | |
642 | mii.fMask = MIIM_FTYPE | MIIM_STATE | MIIM_STRING | MIIM_ID; | |
643 | mii.fType = MFT_STRING; | |
644 | ||
645 | ActionList *al = (*contents)[iRow]->get_actions(iCol); | |
646 | ||
647 | Actions::iterator i; | |
648 | int j = 1; | |
649 | for (i = al->list.begin (); i != al->list.end (); ++i, ++j) | |
650 | { | |
651 | BOOL res; | |
f34a20e7 | 652 | mii.dwTypeData = const_cast <wchar_t *> (i->name.c_str()); |
4018914c JT |
653 | mii.fState = (i->selected ? MFS_CHECKED : MFS_UNCHECKED | |
654 | i->enabled ? MFS_ENABLED : MFS_DISABLED); | |
655 | mii.wID = j; | |
656 | ||
f34a20e7 | 657 | res = InsertMenuItemW(hMenu, -1, TRUE, &mii); |
4018914c JT |
658 | if (!res) Log (LOG_BABBLE) << "InsertMenuItem failed " << endLog; |
659 | } | |
660 | ||
661 | int id = TrackPopupMenu(hMenu, | |
662 | TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_NOANIMATION, | |
663 | p.x, p.y, 0, hWndListView, NULL); | |
664 | ||
665 | // Inform the item of the menu choice | |
666 | if (id) | |
667 | update = (*contents)[iRow]->do_action(iCol, al->list[id-1].id); | |
668 | ||
669 | DestroyMenu(hMenu); | |
670 | delete al; | |
671 | ||
672 | return update; | |
673 | } |