Tidy up spacing in french translation
[cygwin-setup.git] / ListView.cc
blob62a37ab173b6bf0bec3a130378425c3dd6e667fb
1 /*
2 * Copyright (c) 2016 Jon Turney
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.
9 * A copy of the GNU General Public License can be found at
10 * http://www.gnu.org/
14 #include "ListView.h"
15 #include "LogSingleton.h"
16 #include "resource.h"
17 #include "String++.h"
19 #include <commctrl.h>
21 // ---------------------------------------------------------------------------
22 // implements class ListView
24 // ListView Common Control
25 // ---------------------------------------------------------------------------
27 void
28 ListView::init(HWND parent, int id, HeaderList headers)
30 hWndParent = parent;
32 // locate the listview control
33 hWndListView = ::GetDlgItem(parent, id);
35 // configure the listview control
36 SendMessage(hWndListView, CCM_SETVERSION, 6, 0);
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
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);
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);
57 // populate with columns
58 initColumns(headers);
60 // create a small icon imagelist
61 // (the order of images matches ListViewLine::State enum)
62 hImgList = ImageList_Create(GetSystemMetrics(SM_CXSMICON),
63 GetSystemMetrics(SM_CYSMICON),
64 ILC_COLOR32, 2, 0);
65 ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_PLUS)));
66 ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_MINUS)));
68 // create an empty imagelist, used to reset the indent
69 hEmptyImgList = ImageList_Create(1, 1,
70 ILC_COLOR32, 2, 0);
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);
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);
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);
100 // switch to using wide-char WM_NOTIFY messages
101 ListView_SetUnicodeFormat(hWndListView, TRUE);
104 void
105 ListView::initColumns(HeaderList headers_)
107 // store HeaderList for later use
108 headers = headers_;
110 // create the columns
111 LVCOLUMNW lvc;
112 lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
114 int i;
115 for (i = 0; headers[i].text != 0; i++)
117 std::wstring h = LoadStringW(headers[i].text);
119 lvc.iSubItem = i;
120 lvc.pszText = const_cast <wchar_t *> (h.c_str());
121 lvc.cx = 100;
122 lvc.fmt = headers[i].fmt;
124 SendMessage(hWndListView, LVM_INSERTCOLUMNW, i, (LPARAM)&lvc);
127 // now do some width calculations
128 for (i = 0; headers[i].text != 0; i++)
130 headers[i].width = 0;
132 ListView_SetColumnWidth(hWndListView, i, LVSCW_AUTOSIZE_USEHEADER);
133 headers[i].hdr_width = ListView_GetColumnWidth(hWndListView, i);
137 void
138 ListView::noteColumnWidthStart()
140 dc = GetDC (hWndListView);
142 // we must set the font of the DC here, otherwise the width calculations
143 // will be off because the system will use the wrong font metrics
144 HANDLE sysfont = GetStockObject (DEFAULT_GUI_FONT);
145 SelectObject (dc, sysfont);
147 int i;
148 for (i = 0; headers[i].text != 0; i++)
150 headers[i].width = 0;
154 // wrappers to help instantiations of the noteColumnWidth() template call the
155 // right version of GetTextExtentPoint32
156 #undef GetTextExtentPoint32
158 static BOOL GetTextExtentPoint32(HDC hdc, LPCSTR lpString, int c, LPSIZE psizl)
160 return GetTextExtentPoint32A(hdc, lpString, c, psizl);
163 static BOOL GetTextExtentPoint32(HDC hdc, LPCWSTR lpString, int c, LPSIZE psizl)
165 return GetTextExtentPoint32W(hdc, lpString, c, psizl);
168 template <typename T>
169 void
170 ListView::noteColumnWidth(int col_num, const T& string)
172 SIZE s = { 0, 0 };
174 // A margin of 3*GetSystemMetrics(SM_CXEDGE) is used at each side of the
175 // header text.
176 int addend = 2*3*GetSystemMetrics(SM_CXEDGE);
178 if (string.size())
179 GetTextExtentPoint32 (dc, string.c_str(), string.size(), &s);
181 int width = addend + s.cx;
183 // allow for width of dropdown button in popup columns
184 if (headers[col_num].type == ListView::ControlType::popup)
186 width += GetSystemMetrics(SM_CXVSCROLL);
189 if (width > headers[col_num].width)
190 headers[col_num].width = width;
193 // explicit instantiation
194 template void ListView::noteColumnWidth(int col_num, const std::string& string);
195 template void ListView::noteColumnWidth(int col_num, const std::wstring& wstring);
197 void
198 ListView::noteColumnWidthEnd()
200 ReleaseDC(hWndListView, dc);
203 void
204 ListView::resizeColumns(void)
206 // ensure the last column stretches all the way to the right-hand side of the
207 // listview control
208 int i;
209 int total = 0;
210 for (i = 0; headers[i].text != 0; i++)
211 total = total + headers[i].width;
213 RECT r;
214 GetClientRect(hWndListView, &r);
215 int width = r.right - r.left;
217 if (total < width)
218 headers[i-1].width += width - total;
220 // size each column
221 LVCOLUMN lvc;
222 lvc.mask = LVCF_WIDTH | LVCF_MINWIDTH;
223 for (i = 0; headers[i].text != 0; i++)
225 lvc.iSubItem = i;
226 lvc.cx = (headers[i].width < headers[i].hdr_width) ? headers[i].hdr_width : headers[i].width;
227 lvc.cxMin = headers[i].hdr_width;
228 #if DEBUG
229 Log (LOG_BABBLE) << "resizeColumns: " << i << " cx " << lvc.cx << " cxMin " << lvc.cxMin <<endLog;
230 #endif
232 ListView_SetColumn(hWndListView, i, &lvc);
236 void
237 ListView::setContents(ListViewContents *_contents, bool tree)
239 contents = _contents;
241 // disable redrawing of ListView
242 // (otherwise it will redraw every time a row is added, which makes this very slow)
243 SendMessage(hWndListView, WM_SETREDRAW, FALSE, 0);
245 // preserve focus/selection
246 int iRow = ListView_GetSelectionMark(hWndListView);
248 empty();
250 // assign imagelist to listview control (this also sets the size for indents)
251 if (tree)
252 ListView_SetImageList(hWndListView, hImgList, LVSIL_SMALL);
253 else
254 ListView_SetImageList(hWndListView, hEmptyImgList, LVSIL_SMALL);
256 size_t i;
257 for (i = 0; i < contents->size(); i++)
259 LVITEM lvi;
260 lvi.mask = LVIF_TEXT | (tree ? LVIF_IMAGE | LVIF_INDENT : 0);
261 lvi.iItem = i;
262 lvi.iSubItem = 0;
263 lvi.pszText = LPSTR_TEXTCALLBACK;
264 if (tree)
266 lvi.iImage = I_IMAGECALLBACK;
267 lvi.iIndent = (*contents)[i]->get_indent();
270 ListView_InsertItem(hWndListView, &lvi);
273 if (iRow >= 0)
275 ListView_SetItemState(hWndListView, iRow, LVNI_SELECTED | LVNI_FOCUSED, LVNI_SELECTED | LVNI_FOCUSED);
276 ListView_EnsureVisible(hWndListView, iRow, false);
279 // enable redrawing of ListView and redraw
280 SendMessage(hWndListView, WM_SETREDRAW, TRUE, 0);
281 RedrawWindow(hWndListView, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
284 // Helper class: The pointer we hand back needs to remain valid for some time
285 // after OnNotify returns, when the string object we have retrieved has gone out
286 // of scope, so a static instance of this class maintains a local cache.
287 template <class T> class StringCache
289 typedef typename T::traits_type::char_type char_type;
290 public:
291 StringCache() : cache(NULL), cache_size(0) { }
292 StringCache & operator = (const T & s)
294 if ((s.length() + 1) > cache_size)
296 cache_size = s.length() + 1;
297 cache = (char_type *)realloc(cache, cache_size * sizeof(char_type));
299 memcpy(cache, s.c_str(), (s.length() + 1) * sizeof(char_type));
300 return *this;
302 operator char_type *() const
304 return cache;
306 private:
307 char_type *cache;
308 size_t cache_size;
311 bool
312 ListView::OnNotify (NMHDR *pNmHdr, LRESULT *pResult)
314 #if DEBUG
315 Log (LOG_BABBLE) << "ListView::OnNotify id:" << pNmHdr->idFrom << " hwnd:" << pNmHdr->hwndFrom << " code:" << (int)pNmHdr->code << endLog;
316 #endif
318 switch (pNmHdr->code)
320 case LVN_GETDISPINFOW:
322 NMLVDISPINFOW *pNmLvDispInfo = (NMLVDISPINFOW *)pNmHdr;
323 #if DEBUG
324 Log (LOG_BABBLE) << "LVN_GETDISPINFO " << pNmLvDispInfo->item.iItem << endLog;
325 #endif
326 if (contents)
328 int iRow = pNmLvDispInfo->item.iItem;
329 int iCol = pNmLvDispInfo->item.iSubItem;
331 static StringCache<std::wstring> s;
332 s = (*contents)[iRow]->get_text(iCol);
333 pNmLvDispInfo->item.pszText = s;
335 if (pNmLvDispInfo->item.iSubItem == 0)
337 pNmLvDispInfo->item.iImage = (int)((*contents)[pNmLvDispInfo->item.iItem]->get_state());
341 return true;
343 break;
345 case LVN_GETEMPTYMARKUP:
347 NMLVEMPTYMARKUP *pNmMarkup = (NMLVEMPTYMARKUP*) pNmHdr;
348 wcsncpy(pNmMarkup->szMarkup, empty_list_text.c_str(), L_MAX_URL_LENGTH);
349 *pResult = true;
350 return true;
352 break;
354 case NM_CLICK:
356 NMITEMACTIVATE *pNmItemAct = (NMITEMACTIVATE *) pNmHdr;
357 #if DEBUG
358 Log (LOG_BABBLE) << "NM_CLICK: pnmitem->iItem " << pNmItemAct->iItem << " pNmItemAct->iSubItem " << pNmItemAct->iSubItem << endLog;
359 #endif
360 int iRow = pNmItemAct->iItem;
361 int iCol = pNmItemAct->iSubItem;
362 if (iRow < 0)
363 return false;
365 int update = 0;
367 if (headers[iCol].type == ListView::ControlType::popup)
369 POINT p;
370 GetCursorPos(&p);
372 RECT r;
373 ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r);
374 POINT cp = p;
375 ::ScreenToClient(hWndListView, &cp);
377 // if the click isn't over the pop-up button, do nothing yet (but this
378 // might be followed by a NM_DBLCLK)
379 if (cp.x < r.right - GetSystemMetrics(SM_CXVSCROLL))
380 return true;
382 // position pop-up menu at the location of the click
383 update = popup_menu(iRow, iCol, p);
385 else
387 // Inform the item of the click
388 update = (*contents)[iRow]->do_action(iCol, 0);
391 // Update items, if needed
392 if (update > 0)
394 ListView_RedrawItems(hWndListView, iRow, iRow + update -1);
397 return true;
399 break;
401 case NM_DBLCLK:
403 NMITEMACTIVATE *pNmItemAct = (NMITEMACTIVATE *) pNmHdr;
404 #if DEBUG
405 Log (LOG_BABBLE) << "NM_DBLCLICK: pnmitem->iItem " << pNmItemAct->iItem << " pNmItemAct->iSubItem " << pNmItemAct->iSubItem << endLog;
406 #endif
407 int iRow = pNmItemAct->iItem;
408 int iCol = pNmItemAct->iSubItem;
409 if (iRow < 0)
410 return false;
412 int update = 0;
414 // Inform the item of the double-click
415 update = (*contents)[iRow]->do_default_action(iCol );
417 // Update items, if needed
418 if (update > 0)
420 ListView_RedrawItems(hWndListView, iRow, iRow + update -1);
423 return true;
425 break;
427 case NM_CUSTOMDRAW:
429 NMLVCUSTOMDRAW *pNmLvCustomDraw = (NMLVCUSTOMDRAW *)pNmHdr;
431 switch(pNmLvCustomDraw->nmcd.dwDrawStage)
433 case CDDS_PREPAINT:
434 *pResult = CDRF_NOTIFYITEMDRAW;
435 return true;
436 case CDDS_ITEMPREPAINT:
437 *pResult = CDRF_NOTIFYSUBITEMDRAW;
438 return true;
439 case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
441 LRESULT result = CDRF_DODEFAULT;
442 int iCol = pNmLvCustomDraw->iSubItem;
443 int iRow = pNmLvCustomDraw->nmcd.dwItemSpec;
445 switch (headers[iCol].type)
447 default:
448 case ListView::ControlType::text:
449 result = CDRF_DODEFAULT;
450 break;
452 case ListView::ControlType::checkbox:
454 // get the subitem text (as ASCII)
455 char buf[3];
456 ListView_GetItemText(hWndListView, iRow, iCol, buf, _countof(buf));
458 // map the subitem text to a checkbox state
459 UINT state = DFCS_BUTTONCHECK | DFCS_FLAT;
460 if (buf[0] == '\0') // empty
462 result = CDRF_DODEFAULT;
463 break;
465 else if (buf[0] == 'y') // yes
466 state |= DFCS_CHECKED;
467 else if ((buf[0] == 'n') && (buf[1] == 'o')) // no
468 state |= 0;
469 else // n/a
470 state |= DFCS_INACTIVE;
472 // erase and draw a checkbox
473 RECT r;
474 ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r);
475 DWORD bkg_color;
476 if (pNmLvCustomDraw->nmcd.uItemState & CDIS_SELECTED)
477 bkg_color = GetSysColor(COLOR_HIGHLIGHT);
478 else
479 bkg_color = ListView_GetBkColor(hWndListView);
480 HBRUSH hBrush = CreateSolidBrush(bkg_color);
481 FillRect(pNmLvCustomDraw->nmcd.hdc, &r, hBrush);
482 DeleteObject(hBrush);
483 DrawFrameControl(pNmLvCustomDraw->nmcd.hdc, &r, DFC_BUTTON, state);
485 result = CDRF_SKIPDEFAULT;
487 break;
489 case ListView::ControlType::popup:
491 // let the control draw the text, but notify us afterwards
492 result = CDRF_NOTIFYPOSTPAINT;
494 break;
497 *pResult = result;
498 return true;
500 case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT:
502 LRESULT result = CDRF_DODEFAULT;
503 int iCol = pNmLvCustomDraw->iSubItem;
504 int iRow = pNmLvCustomDraw->nmcd.dwItemSpec;
506 switch (headers[iCol].type)
508 default:
509 result = CDRF_DODEFAULT;
510 break;
512 case ListView::ControlType::popup:
514 // draw the control at the RHS of the cell
515 RECT r;
516 ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r);
517 r.left = r.right - GetSystemMetrics(SM_CXVSCROLL);
518 DrawFrameControl(pNmLvCustomDraw->nmcd.hdc, &r, DFC_SCROLL,DFCS_SCROLLCOMBOBOX);
520 result = CDRF_DODEFAULT;
522 break;
524 *pResult = result;
525 return true;
529 break;
531 case LVN_HOTTRACK:
533 NMLISTVIEW *pNmListView = (NMLISTVIEW *)pNmHdr;
534 int iRow = pNmListView->iItem;
535 int iCol = pNmListView->iSubItem;
536 #if DEBUG
537 Log (LOG_BABBLE) << "LVN_HOTTRACK " << iRow << " " << iCol << endLog;
538 #endif
539 if (iRow < 0)
540 return true;
542 // if we've tracked off to a different cell
543 if ((iRow != iRow_track) || (iCol != iCol_track))
545 #if DEBUG
546 Log (LOG_BABBLE) << "LVN_HOTTRACK changed cell" << endLog;
547 #endif
549 // if the tooltip for previous cell is displayed, remove it
550 // restart the tooltip AUTOPOP timer for this cell
551 SendMessage(hWndTip, TTM_ACTIVATE, FALSE, 0);
552 SendMessage(hWndTip, TTM_ACTIVATE, TRUE, 0);
554 iRow_track = iRow;
555 iCol_track = iCol;
558 return true;
560 break;
562 case LVN_KEYDOWN:
564 NMLVKEYDOWN *pNmLvKeyDown = (NMLVKEYDOWN *)pNmHdr;
565 int iRow = ListView_GetSelectionMark(hWndListView);
566 #if DEBUG
567 Log (LOG_PLAIN) << "LVN_KEYDOWN vkey " << pNmLvKeyDown->wVKey << " on row " << iRow << endLog;
568 #endif
570 if (contents && iRow >= 0)
572 int col_num;
573 int action_id;
574 if ((*contents)[iRow]->map_key_to_action(pNmLvKeyDown->wVKey, &col_num, &action_id))
576 int update;
577 if (action_id >= 0)
578 update = (*contents)[iRow]->do_action(col_num, action_id);
579 else
581 POINT p;
582 RECT r;
583 ListView_GetSubItemRect(hWndListView, iRow, col_num, LVIR_BOUNDS, &r);
584 p.x = r.left;
585 p.y = r.top;
586 ClientToScreen(hWndListView, &p);
588 update = popup_menu(iRow, col_num, p);
591 if (update > 0)
592 ListView_RedrawItems(hWndListView, iRow, iRow + update -1);
596 break;
598 case TTN_GETDISPINFO:
600 // convert mouse position to item/subitem
601 LVHITTESTINFO lvHitTestInfo;
602 lvHitTestInfo.flags = LVHT_ONITEM;
603 GetCursorPos(&lvHitTestInfo.pt);
604 ::ScreenToClient(hWndListView, &lvHitTestInfo.pt);
605 ListView_SubItemHitTest(hWndListView, &lvHitTestInfo);
607 int iRow = lvHitTestInfo.iItem;
608 int iCol = lvHitTestInfo.iSubItem;
609 if (iRow < 0)
610 return false;
612 #if DEBUG
613 Log (LOG_BABBLE) << "TTN_GETDISPINFO " << iRow << " " << iCol << endLog;
614 #endif
616 // get the tooltip text for that item/subitem
617 static StringCache<std::string> tooltip;
618 tooltip = "";
619 if (contents)
620 tooltip = (*contents)[iRow]->get_tooltip(iCol);
622 // set the tooltip text
623 NMTTDISPINFO *pNmTTDispInfo = (NMTTDISPINFO *)pNmHdr;
624 pNmTTDispInfo->lpszText = tooltip;
625 pNmTTDispInfo->hinst = NULL;
626 pNmTTDispInfo->uFlags = 0;
628 return true;
630 break;
633 // We don't care.
634 return false;
637 void
638 ListView::empty(void)
640 ListView_DeleteAllItems(hWndListView);
643 void
644 ListView::setEmptyText(unsigned int text)
646 empty_list_text = LoadStringW(text);
650 ListView::popup_menu(int iRow, int iCol, POINT p)
652 int update = 0;
653 // construct menu
654 HMENU hMenu = CreatePopupMenu();
656 MENUITEMINFOW mii;
657 memset(&mii, 0, sizeof(mii));
658 mii.cbSize = sizeof(mii);
659 mii.fMask = MIIM_FTYPE | MIIM_STATE | MIIM_STRING | MIIM_ID;
660 mii.fType = MFT_STRING;
662 ActionList *al = (*contents)[iRow]->get_actions(iCol);
664 Actions::iterator i;
665 int j = 1;
666 for (i = al->list.begin (); i != al->list.end (); ++i, ++j)
668 BOOL res;
669 mii.dwTypeData = const_cast <wchar_t *> (i->name.c_str());
670 mii.fState = (i->selected ? MFS_CHECKED : MFS_UNCHECKED |
671 i->enabled ? MFS_ENABLED : MFS_DISABLED);
672 mii.wID = j;
674 res = InsertMenuItemW(hMenu, -1, TRUE, &mii);
675 if (!res) Log (LOG_BABBLE) << "InsertMenuItem failed " << endLog;
678 int id = TrackPopupMenu(hMenu,
679 TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_NOANIMATION,
680 p.x, p.y, 0, hWndListView, NULL);
682 // Inform the item of the menu choice
683 if (id)
684 update = (*contents)[iRow]->do_action(iCol, al->list[id-1].id);
686 DestroyMenu(hMenu);
687 delete al;
689 return update;