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
15 #include "LogSingleton.h"
20 // ---------------------------------------------------------------------------
21 // implements class ListView
23 // ListView Common Control
24 // ---------------------------------------------------------------------------
27 ListView::init(HWND parent
, int id
, HeaderList headers
)
31 // locate the listview control
32 hWndListView
= ::GetDlgItem(parent
, id
);
34 // configure the listview control
35 SendMessage(hWndListView
, CCM_SETVERSION
, 6, 0);
37 ListView_SetExtendedListViewStyle(hWndListView
,
38 LVS_EX_COLUMNSNAPPOINTS
| // use cxMin
39 LVS_EX_FULLROWSELECT
|
41 LVS_EX_HEADERDRAGDROP
); // headers can be re-ordered
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
);
48 // ensure an initial item exists for width calculations...
53 lvi
.pszText
= const_cast <char *> ("Working...");
54 ListView_InsertItem(hWndListView
, &lvi
);
56 // populate with columns
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
),
64 ImageList_AddIcon(hImgList
, LoadIcon(GetModuleHandle(NULL
), MAKEINTRESOURCE(IDI_TREE_PLUS
)));
65 ImageList_AddIcon(hImgList
, LoadIcon(GetModuleHandle(NULL
), MAKEINTRESOURCE(IDI_TREE_MINUS
)));
67 // create an empty imagelist, used to reset the indent
68 hEmptyImgList
= ImageList_Create(1, 1,
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
,
76 WS_POPUP
| TTS_NOPREFIX
| TTS_ALWAYSTIP
,
77 CW_USEDEFAULT
, CW_USEDEFAULT
, CW_USEDEFAULT
, CW_USEDEFAULT
,
80 GetModuleHandle(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
);
86 memset ((void *)&ti
, 0, sizeof(ti
));
87 ti
.cbSize
= sizeof(ti
);
88 ti
.uFlags
= TTF_IDISHWND
| TTF_SUBCLASS
;
90 ti
.uId
= (UINT_PTR
)hWndListView
;
91 ti
.lpszText
= LPSTR_TEXTCALLBACK
; // use TTN_GETDISPINFO
92 SendMessage(hWndTip
, TTM_ADDTOOL
, 0, (LPARAM
)&ti
);
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);
101 ListView::initColumns(HeaderList headers_
)
103 // store HeaderList for later use
106 // create the columns
108 lvc
.mask
= LVCF_FMT
| LVCF_WIDTH
| LVCF_TEXT
| LVCF_SUBITEM
;
111 for (i
= 0; headers
[i
].text
!= 0; i
++)
114 lvc
.pszText
= const_cast <char *> (headers
[i
].text
);
116 lvc
.fmt
= headers
[i
].fmt
;
118 ListView_InsertColumn(hWndListView
, i
, &lvc
);
121 // now do some width calculations
122 for (i
= 0; headers
[i
].text
!= 0; i
++)
124 headers
[i
].width
= 0;
126 ListView_SetColumnWidth(hWndListView
, i
, LVSCW_AUTOSIZE_USEHEADER
);
127 headers
[i
].hdr_width
= ListView_GetColumnWidth(hWndListView
, i
);
132 ListView::noteColumnWidthStart()
134 dc
= GetDC (hWndListView
);
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
);
142 for (i
= 0; headers
[i
].text
!= 0; i
++)
144 headers
[i
].width
= 0;
149 ListView::noteColumnWidth(int col_num
, const std::string
& string
)
153 // A margin of 3*GetSystemMetrics(SM_CXEDGE) is used at each side of the
155 int addend
= 2*3*GetSystemMetrics(SM_CXEDGE
);
158 GetTextExtentPoint32 (dc
, string
.c_str(), string
.size(), &s
);
160 int width
= addend
+ s
.cx
;
162 // allow for width of dropdown button in popup columns
163 if (headers
[col_num
].type
== ListView::ControlType::popup
)
165 width
+= GetSystemMetrics(SM_CXVSCROLL
);
168 if (width
> headers
[col_num
].width
)
169 headers
[col_num
].width
= width
;
173 ListView::noteColumnWidthEnd()
175 ReleaseDC(hWndListView
, dc
);
179 ListView::resizeColumns(void)
181 // ensure the last column stretches all the way to the right-hand side of the
185 for (i
= 0; headers
[i
].text
!= 0; i
++)
186 total
= total
+ headers
[i
].width
;
189 GetClientRect(hWndListView
, &r
);
190 int width
= r
.right
- r
.left
;
193 headers
[i
-1].width
+= width
- total
;
197 lvc
.mask
= LVCF_WIDTH
| LVCF_MINWIDTH
;
198 for (i
= 0; headers
[i
].text
!= 0; i
++)
201 lvc
.cx
= (headers
[i
].width
< headers
[i
].hdr_width
) ? headers
[i
].hdr_width
: headers
[i
].width
;
202 lvc
.cxMin
= headers
[i
].hdr_width
;
204 Log (LOG_BABBLE
) << "resizeColumns: " << i
<< " cx " << lvc
.cx
<< " cxMin " << lvc
.cxMin
<<endLog
;
207 ListView_SetColumn(hWndListView
, i
, &lvc
);
212 ListView::setContents(ListViewContents
*_contents
, bool tree
)
214 contents
= _contents
;
216 // disable redrawing of ListView
217 // (otherwise it will redraw every time a row is added, which makes this very slow)
218 SendMessage(hWndListView
, WM_SETREDRAW
, FALSE
, 0);
220 // preserve focus/selection
221 int iRow
= ListView_GetSelectionMark(hWndListView
);
225 // assign imagelist to listview control (this also sets the size for indents)
227 ListView_SetImageList(hWndListView
, hImgList
, LVSIL_SMALL
);
229 ListView_SetImageList(hWndListView
, hEmptyImgList
, LVSIL_SMALL
);
232 for (i
= 0; i
< contents
->size(); i
++)
235 lvi
.mask
= LVIF_TEXT
| (tree
? LVIF_IMAGE
| LVIF_INDENT
: 0);
238 lvi
.pszText
= LPSTR_TEXTCALLBACK
;
241 lvi
.iImage
= I_IMAGECALLBACK
;
242 lvi
.iIndent
= (*contents
)[i
]->get_indent();
245 ListView_InsertItem(hWndListView
, &lvi
);
250 ListView_SetItemState(hWndListView
, iRow
, LVNI_SELECTED
| LVNI_FOCUSED
, LVNI_SELECTED
| LVNI_FOCUSED
);
251 ListView_EnsureVisible(hWndListView
, iRow
, false);
254 // enable redrawing of ListView and redraw
255 SendMessage(hWndListView
, WM_SETREDRAW
, TRUE
, 0);
256 RedrawWindow(hWndListView
, NULL
, NULL
, RDW_ERASE
| RDW_FRAME
| RDW_INVALIDATE
| RDW_ALLCHILDREN
);
259 // Helper class: The char * pointer we hand back needs to remain valid for some
260 // time after OnNotify returns, when the std::string we have retrieved has gone
261 // out of scope, so a static instance of this class maintains a local cache.
265 StringCache() : cache(NULL
), cache_size(0) { }
266 StringCache
& operator = (const std::string
& s
)
268 if ((s
.length() + 1) > cache_size
)
270 cache_size
= s
.length() + 1;
271 cache
= (char *)realloc(cache
, cache_size
);
273 strcpy(cache
, s
.c_str());
276 operator char *() const
286 ListView::OnNotify (NMHDR
*pNmHdr
, LRESULT
*pResult
)
289 Log (LOG_BABBLE
) << "ListView::OnNotify id:" << pNmHdr
->idFrom
<< " hwnd:" << pNmHdr
->hwndFrom
<< " code:" << (int)pNmHdr
->code
<< endLog
;
292 switch (pNmHdr
->code
)
294 case LVN_GETDISPINFO
:
296 NMLVDISPINFO
*pNmLvDispInfo
= (NMLVDISPINFO
*)pNmHdr
;
298 Log (LOG_BABBLE
) << "LVN_GETDISPINFO " << pNmLvDispInfo
->item
.iItem
<< endLog
;
302 int iRow
= pNmLvDispInfo
->item
.iItem
;
303 int iCol
= pNmLvDispInfo
->item
.iSubItem
;
305 static StringCache s
;
306 s
= (*contents
)[iRow
]->get_text(iCol
);
307 pNmLvDispInfo
->item
.pszText
= s
;
309 if (pNmLvDispInfo
->item
.iSubItem
== 0)
311 pNmLvDispInfo
->item
.iImage
= (int)((*contents
)[pNmLvDispInfo
->item
.iItem
]->get_state());
319 case LVN_GETEMPTYMARKUP
:
321 NMLVEMPTYMARKUP
*pNmMarkup
= (NMLVEMPTYMARKUP
*) pNmHdr
;
323 MultiByteToWideChar(CP_UTF8
, 0,
325 pNmMarkup
->szMarkup
, L_MAX_URL_LENGTH
);
334 NMITEMACTIVATE
*pNmItemAct
= (NMITEMACTIVATE
*) pNmHdr
;
336 Log (LOG_BABBLE
) << "NM_CLICK: pnmitem->iItem " << pNmItemAct
->iItem
<< " pNmItemAct->iSubItem " << pNmItemAct
->iSubItem
<< endLog
;
338 int iRow
= pNmItemAct
->iItem
;
339 int iCol
= pNmItemAct
->iSubItem
;
345 if (headers
[iCol
].type
== ListView::ControlType::popup
)
348 // position pop-up menu at the location of the click
351 update
= popup_menu(iRow
, iCol
, p
);
355 // Inform the item of the click
356 update
= (*contents
)[iRow
]->do_action(iCol
, 0);
359 // Update items, if needed
362 ListView_RedrawItems(hWndListView
, iRow
, iRow
+ update
-1);
371 NMLVCUSTOMDRAW
*pNmLvCustomDraw
= (NMLVCUSTOMDRAW
*)pNmHdr
;
373 switch(pNmLvCustomDraw
->nmcd
.dwDrawStage
)
376 *pResult
= CDRF_NOTIFYITEMDRAW
;
378 case CDDS_ITEMPREPAINT
:
379 *pResult
= CDRF_NOTIFYSUBITEMDRAW
;
381 case CDDS_SUBITEM
| CDDS_ITEMPREPAINT
:
383 LRESULT result
= CDRF_DODEFAULT
;
384 int iCol
= pNmLvCustomDraw
->iSubItem
;
385 int iRow
= pNmLvCustomDraw
->nmcd
.dwItemSpec
;
387 switch (headers
[iCol
].type
)
390 case ListView::ControlType::text
:
391 result
= CDRF_DODEFAULT
;
394 case ListView::ControlType::checkbox
:
396 // get the subitem text
398 ListView_GetItemText(hWndListView
, iRow
, iCol
, buf
, _countof(buf
));
400 // map the subitem text to a checkbox state
401 UINT state
= DFCS_BUTTONCHECK
| DFCS_FLAT
;
402 if (buf
[0] == '\0') // empty
404 result
= CDRF_DODEFAULT
;
407 else if (buf
[0] == 'y') // yes
408 state
|= DFCS_CHECKED
;
409 else if ((buf
[0] == 'n') && (buf
[1] == 'o')) // no
412 state
|= DFCS_INACTIVE
;
414 // erase and draw a checkbox
416 ListView_GetSubItemRect(hWndListView
, iRow
, iCol
, LVIR_BOUNDS
, &r
);
418 if (pNmLvCustomDraw
->nmcd
.uItemState
& CDIS_SELECTED
)
419 bkg_color
= GetSysColor(COLOR_HIGHLIGHT
);
421 bkg_color
= ListView_GetBkColor(hWndListView
);
422 HBRUSH hBrush
= CreateSolidBrush(bkg_color
);
423 FillRect(pNmLvCustomDraw
->nmcd
.hdc
, &r
, hBrush
);
424 DeleteObject(hBrush
);
425 DrawFrameControl(pNmLvCustomDraw
->nmcd
.hdc
, &r
, DFC_BUTTON
, state
);
427 result
= CDRF_SKIPDEFAULT
;
431 case ListView::ControlType::popup
:
433 // let the control draw the text, but notify us afterwards
434 result
= CDRF_NOTIFYPOSTPAINT
;
442 case CDDS_SUBITEM
| CDDS_ITEMPOSTPAINT
:
444 LRESULT result
= CDRF_DODEFAULT
;
445 int iCol
= pNmLvCustomDraw
->iSubItem
;
446 int iRow
= pNmLvCustomDraw
->nmcd
.dwItemSpec
;
448 switch (headers
[iCol
].type
)
451 result
= CDRF_DODEFAULT
;
454 case ListView::ControlType::popup
:
456 // draw the control at the RHS of the cell
458 ListView_GetSubItemRect(hWndListView
, iRow
, iCol
, LVIR_BOUNDS
, &r
);
459 r
.left
= r
.right
- GetSystemMetrics(SM_CXVSCROLL
);
460 DrawFrameControl(pNmLvCustomDraw
->nmcd
.hdc
, &r
, DFC_SCROLL
,DFCS_SCROLLCOMBOBOX
);
462 result
= CDRF_DODEFAULT
;
475 NMLISTVIEW
*pNmListView
= (NMLISTVIEW
*)pNmHdr
;
476 int iRow
= pNmListView
->iItem
;
477 int iCol
= pNmListView
->iSubItem
;
479 Log (LOG_BABBLE
) << "LVN_HOTTRACK " << iRow
<< " " << iCol
<< endLog
;
484 // if we've tracked off to a different cell
485 if ((iRow
!= iRow_track
) || (iCol
!= iCol_track
))
488 Log (LOG_BABBLE
) << "LVN_HOTTRACK changed cell" << endLog
;
491 // if the tooltip for previous cell is displayed, remove it
492 // restart the tooltip AUTOPOP timer for this cell
493 SendMessage(hWndTip
, TTM_ACTIVATE
, FALSE
, 0);
494 SendMessage(hWndTip
, TTM_ACTIVATE
, TRUE
, 0);
504 case TTN_GETDISPINFO
:
506 // convert mouse position to item/subitem
507 LVHITTESTINFO lvHitTestInfo
;
508 lvHitTestInfo
.flags
= LVHT_ONITEM
;
509 GetCursorPos(&lvHitTestInfo
.pt
);
510 ::ScreenToClient(hWndListView
, &lvHitTestInfo
.pt
);
511 ListView_SubItemHitTest(hWndListView
, &lvHitTestInfo
);
513 int iRow
= lvHitTestInfo
.iItem
;
514 int iCol
= lvHitTestInfo
.iSubItem
;
519 Log (LOG_BABBLE
) << "TTN_GETDISPINFO " << iRow
<< " " << iCol
<< endLog
;
522 // get the tooltip text for that item/subitem
523 static StringCache tooltip
;
526 tooltip
= (*contents
)[iRow
]->get_tooltip(iCol
);
528 // set the tooltip text
529 NMTTDISPINFO
*pNmTTDispInfo
= (NMTTDISPINFO
*)pNmHdr
;
530 pNmTTDispInfo
->lpszText
= tooltip
;
531 pNmTTDispInfo
->hinst
= NULL
;
532 pNmTTDispInfo
->uFlags
= 0;
544 ListView::empty(void)
546 ListView_DeleteAllItems(hWndListView
);
550 ListView::setEmptyText(const char *text
)
552 empty_list_text
= text
;
556 ListView::popup_menu(int iRow
, int iCol
, POINT p
)
560 HMENU hMenu
= CreatePopupMenu();
563 memset(&mii
, 0, sizeof(mii
));
564 mii
.cbSize
= sizeof(mii
);
565 mii
.fMask
= MIIM_FTYPE
| MIIM_STATE
| MIIM_STRING
| MIIM_ID
;
566 mii
.fType
= MFT_STRING
;
568 ActionList
*al
= (*contents
)[iRow
]->get_actions(iCol
);
572 for (i
= al
->list
.begin (); i
!= al
->list
.end (); ++i
, ++j
)
575 mii
.dwTypeData
= (char *)i
->name
.c_str();
576 mii
.fState
= (i
->selected
? MFS_CHECKED
: MFS_UNCHECKED
|
577 i
->enabled
? MFS_ENABLED
: MFS_DISABLED
);
580 res
= InsertMenuItem(hMenu
, -1, TRUE
, &mii
);
581 if (!res
) Log (LOG_BABBLE
) << "InsertMenuItem failed " << endLog
;
584 int id
= TrackPopupMenu(hMenu
,
585 TPM_LEFTALIGN
| TPM_TOPALIGN
| TPM_RETURNCMD
| TPM_LEFTBUTTON
| TPM_NOANIMATION
,
586 p
.x
, p
.y
, 0, hWndListView
, NULL
);
588 // Inform the item of the menu choice
590 update
= (*contents
)[iRow
]->do_action(iCol
, al
->list
[id
-1].id
);