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