#include "ListView.h"
#include "LogSingleton.h"
+#include "resource.h"
+#include "String++.h"
#include <commctrl.h>
// populate with columns
initColumns(headers);
+
+ // create a small icon imagelist
+ // (the order of images matches ListViewLine::State enum)
+ hImgList = ImageList_Create(GetSystemMetrics(SM_CXSMICON),
+ GetSystemMetrics(SM_CYSMICON),
+ ILC_COLOR32, 2, 0);
+ ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_PLUS)));
+ ImageList_AddIcon(hImgList, LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_TREE_MINUS)));
+
+ // create an empty imagelist, used to reset the indent
+ hEmptyImgList = ImageList_Create(1, 1,
+ ILC_COLOR32, 2, 0);
+
+ // LVS_EX_INFOTIP/LVN_GETINFOTIP doesn't work for subitems, so we have to do
+ // our own tooltip handling
+ hWndTip = CreateWindowEx (0,
+ (LPCTSTR) TOOLTIPS_CLASS,
+ NULL,
+ WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ hWndParent,
+ (HMENU) 0,
+ GetModuleHandle(NULL),
+ NULL);
+ // must be topmost so that tooltips will display on top
+ SetWindowPos(hWndTip, HWND_TOPMOST,0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+
+ TOOLINFO ti;
+ memset ((void *)&ti, 0, sizeof(ti));
+ ti.cbSize = sizeof(ti);
+ ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
+ ti.hwnd = hWndParent;
+ ti.uId = (UINT_PTR)hWndListView;
+ ti.lpszText = LPSTR_TEXTCALLBACK; // use TTN_GETDISPINFO
+ SendMessage(hWndTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
+
+ // match long delay for tooltip to disappear used elsewhere (30s)
+ SendMessage(hWndTip, TTM_SETDELAYTIME, TTDT_AUTOPOP, (LPARAM) MAKELONG (30000, 0));
+ // match tip width used elsewhere
+ SendMessage(hWndTip, TTM_SETMAXTIPWIDTH, 0, 450);
+
+ // switch to using wide-char WM_NOTIFY messages
+ ListView_SetUnicodeFormat(hWndListView, TRUE);
}
void
headers = headers_;
// create the columns
- LVCOLUMN lvc;
+ LVCOLUMNW lvc;
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
int i;
for (i = 0; headers[i].text != 0; i++)
{
+ std::wstring h = LoadStringW(headers[i].text);
+
lvc.iSubItem = i;
- lvc.pszText = const_cast <char *> (headers[i].text);
+ lvc.pszText = const_cast <wchar_t *> (h.c_str());
lvc.cx = 100;
lvc.fmt = headers[i].fmt;
- ListView_InsertColumn(hWndListView, i, &lvc);
+ SendMessage(hWndListView, LVM_INSERTCOLUMNW, i, (LPARAM)&lvc);
}
// now do some width calculations
}
}
+// wrappers to help instantiations of the noteColumnWidth() template call the
+// right version of GetTextExtentPoint32
+#undef GetTextExtentPoint32
+
+static BOOL GetTextExtentPoint32(HDC hdc, LPCSTR lpString, int c, LPSIZE psizl)
+{
+ return GetTextExtentPoint32A(hdc, lpString, c, psizl);
+}
+
+static BOOL GetTextExtentPoint32(HDC hdc, LPCWSTR lpString, int c, LPSIZE psizl)
+{
+ return GetTextExtentPoint32W(hdc, lpString, c, psizl);
+}
+
+template <typename T>
void
-ListView::noteColumnWidth(int col_num, const std::string& string)
+ListView::noteColumnWidth(int col_num, const T& string)
{
SIZE s = { 0, 0 };
int width = addend + s.cx;
+ // allow for width of dropdown button in popup columns
+ if (headers[col_num].type == ListView::ControlType::popup)
+ {
+ width += GetSystemMetrics(SM_CXVSCROLL);
+ }
+
if (width > headers[col_num].width)
headers[col_num].width = width;
}
+// explicit instantiation
+template void ListView::noteColumnWidth(int col_num, const std::string& string);
+template void ListView::noteColumnWidth(int col_num, const std::wstring& wstring);
+
void
ListView::noteColumnWidthEnd()
{
}
void
-ListView::setContents(ListViewContents *_contents)
+ListView::setContents(ListViewContents *_contents, bool tree)
{
contents = _contents;
empty();
+ // assign imagelist to listview control (this also sets the size for indents)
+ if (tree)
+ ListView_SetImageList(hWndListView, hImgList, LVSIL_SMALL);
+ else
+ ListView_SetImageList(hWndListView, hEmptyImgList, LVSIL_SMALL);
+
size_t i;
for (i = 0; i < contents->size(); i++)
{
LVITEM lvi;
- lvi.mask = LVIF_TEXT;
+ lvi.mask = LVIF_TEXT | (tree ? LVIF_IMAGE | LVIF_INDENT : 0);
lvi.iItem = i;
lvi.iSubItem = 0;
lvi.pszText = LPSTR_TEXTCALLBACK;
+ if (tree)
+ {
+ lvi.iImage = I_IMAGECALLBACK;
+ lvi.iIndent = (*contents)[i]->get_indent();
+ }
ListView_InsertItem(hWndListView, &lvi);
}
RedrawWindow(hWndListView, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
}
-// Helper class: The char * pointer we hand back needs to remain valid for some
-// time after OnNotify returns, when the std::string we have retrieved has gone
-// out of scope, so a static instance of this class maintains a local cache.
-class StringCache
+// Helper class: The pointer we hand back needs to remain valid for some time
+// after OnNotify returns, when the string object we have retrieved has gone out
+// of scope, so a static instance of this class maintains a local cache.
+template <class T> class StringCache
{
+ typedef typename T::traits_type::char_type char_type;
public:
StringCache() : cache(NULL), cache_size(0) { }
- StringCache & operator = (const std::string & s)
+ StringCache & operator = (const T & s)
{
if ((s.length() + 1) > cache_size)
{
cache_size = s.length() + 1;
- cache = (char *)realloc(cache, cache_size);
+ cache = (char_type *)realloc(cache, cache_size * sizeof(char_type));
}
- strcpy(cache, s.c_str());
+ memcpy(cache, s.c_str(), (s.length() + 1) * sizeof(char_type));
return *this;
}
- operator char *() const
+ operator char_type *() const
{
return cache;
}
private:
- char *cache;
+ char_type *cache;
size_t cache_size;
};
switch (pNmHdr->code)
{
- case LVN_GETDISPINFO:
+ case LVN_GETDISPINFOW:
{
- NMLVDISPINFO *pNmLvDispInfo = (NMLVDISPINFO *)pNmHdr;
+ NMLVDISPINFOW *pNmLvDispInfo = (NMLVDISPINFOW *)pNmHdr;
#if DEBUG
Log (LOG_BABBLE) << "LVN_GETDISPINFO " << pNmLvDispInfo->item.iItem << endLog;
#endif
int iRow = pNmLvDispInfo->item.iItem;
int iCol = pNmLvDispInfo->item.iSubItem;
- static StringCache s;
+ static StringCache<std::wstring> s;
s = (*contents)[iRow]->get_text(iCol);
pNmLvDispInfo->item.pszText = s;
+
+ if (pNmLvDispInfo->item.iSubItem == 0)
+ {
+ pNmLvDispInfo->item.iImage = (int)((*contents)[pNmLvDispInfo->item.iItem]->get_state());
+ }
}
return true;
case LVN_GETEMPTYMARKUP:
{
NMLVEMPTYMARKUP *pNmMarkup = (NMLVEMPTYMARKUP*) pNmHdr;
-
- MultiByteToWideChar(CP_UTF8, 0,
- empty_list_text, -1,
- pNmMarkup->szMarkup, L_MAX_URL_LENGTH);
-
+ wcsncpy(pNmMarkup->szMarkup, empty_list_text.c_str(), L_MAX_URL_LENGTH);
*pResult = true;
return true;
}
if (headers[iCol].type == ListView::ControlType::popup)
{
POINT p;
- // position pop-up menu at the location of the click
GetCursorPos(&p);
+ RECT r;
+ ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r);
+ POINT cp = p;
+ ::ScreenToClient(hWndListView, &cp);
+
+ // if the click isn't over the pop-up button, do nothing yet (but this
+ // might be followed by a NM_DBLCLK)
+ if (cp.x < r.right - GetSystemMetrics(SM_CXVSCROLL))
+ return true;
+
+ // position pop-up menu at the location of the click
update = popup_menu(iRow, iCol, p);
}
else
}
break;
+ case NM_DBLCLK:
+ {
+ NMITEMACTIVATE *pNmItemAct = (NMITEMACTIVATE *) pNmHdr;
+#if DEBUG
+ Log (LOG_BABBLE) << "NM_DBLCLICK: pnmitem->iItem " << pNmItemAct->iItem << " pNmItemAct->iSubItem " << pNmItemAct->iSubItem << endLog;
+#endif
+ int iRow = pNmItemAct->iItem;
+ int iCol = pNmItemAct->iSubItem;
+ if (iRow < 0)
+ return false;
+
+ int update = 0;
+
+ // Inform the item of the double-click
+ update = (*contents)[iRow]->do_default_action(iCol );
+
+ // Update items, if needed
+ if (update > 0)
+ {
+ ListView_RedrawItems(hWndListView, iRow, iRow + update -1);
+ }
+
+ return true;
+ }
+ break;
+
case NM_CUSTOMDRAW:
{
NMLVCUSTOMDRAW *pNmLvCustomDraw = (NMLVCUSTOMDRAW *)pNmHdr;
case ListView::ControlType::checkbox:
{
- // get the subitem text
+ // get the subitem text (as ASCII)
char buf[3];
ListView_GetItemText(hWndListView, iRow, iCol, buf, _countof(buf));
// erase and draw a checkbox
RECT r;
ListView_GetSubItemRect(hWndListView, iRow, iCol, LVIR_BOUNDS, &r);
- HBRUSH hBrush = CreateSolidBrush(ListView_GetBkColor(hWndListView));
+ DWORD bkg_color;
+ if (pNmLvCustomDraw->nmcd.uItemState & CDIS_SELECTED)
+ bkg_color = GetSysColor(COLOR_HIGHLIGHT);
+ else
+ bkg_color = ListView_GetBkColor(hWndListView);
+ HBRUSH hBrush = CreateSolidBrush(bkg_color);
FillRect(pNmLvCustomDraw->nmcd.hdc, &r, hBrush);
DeleteObject(hBrush);
DrawFrameControl(pNmLvCustomDraw->nmcd.hdc, &r, DFC_BUTTON, state);
}
}
}
+ break;
+
+ case LVN_HOTTRACK:
+ {
+ NMLISTVIEW *pNmListView = (NMLISTVIEW *)pNmHdr;
+ int iRow = pNmListView->iItem;
+ int iCol = pNmListView->iSubItem;
+#if DEBUG
+ Log (LOG_BABBLE) << "LVN_HOTTRACK " << iRow << " " << iCol << endLog;
+#endif
+ if (iRow < 0)
+ return true;
+
+ // if we've tracked off to a different cell
+ if ((iRow != iRow_track) || (iCol != iCol_track))
+ {
+#if DEBUG
+ Log (LOG_BABBLE) << "LVN_HOTTRACK changed cell" << endLog;
+#endif
+
+ // if the tooltip for previous cell is displayed, remove it
+ // restart the tooltip AUTOPOP timer for this cell
+ SendMessage(hWndTip, TTM_ACTIVATE, FALSE, 0);
+ SendMessage(hWndTip, TTM_ACTIVATE, TRUE, 0);
+
+ iRow_track = iRow;
+ iCol_track = iCol;
+ }
+
+ return true;
+ }
+ break;
+
+ case LVN_KEYDOWN:
+ {
+ NMLVKEYDOWN *pNmLvKeyDown = (NMLVKEYDOWN *)pNmHdr;
+ int iRow = ListView_GetSelectionMark(hWndListView);
+#if DEBUG
+ Log (LOG_PLAIN) << "LVN_KEYDOWN vkey " << pNmLvKeyDown->wVKey << " on row " << iRow << endLog;
+#endif
+
+ if (contents && iRow >= 0)
+ {
+ int col_num;
+ int action_id;
+ if ((*contents)[iRow]->map_key_to_action(pNmLvKeyDown->wVKey, &col_num, &action_id))
+ {
+ int update;
+ if (action_id >= 0)
+ update = (*contents)[iRow]->do_action(col_num, action_id);
+ else
+ {
+ POINT p;
+ RECT r;
+ ListView_GetSubItemRect(hWndListView, iRow, col_num, LVIR_BOUNDS, &r);
+ p.x = r.left;
+ p.y = r.top;
+ ClientToScreen(hWndListView, &p);
+
+ update = popup_menu(iRow, col_num, p);
+ }
+
+ if (update > 0)
+ ListView_RedrawItems(hWndListView, iRow, iRow + update -1);
+ }
+ }
+ }
+ break;
+
+ case TTN_GETDISPINFO:
+ {
+ // convert mouse position to item/subitem
+ LVHITTESTINFO lvHitTestInfo;
+ lvHitTestInfo.flags = LVHT_ONITEM;
+ GetCursorPos(&lvHitTestInfo.pt);
+ ::ScreenToClient(hWndListView, &lvHitTestInfo.pt);
+ ListView_SubItemHitTest(hWndListView, &lvHitTestInfo);
+
+ int iRow = lvHitTestInfo.iItem;
+ int iCol = lvHitTestInfo.iSubItem;
+ if (iRow < 0)
+ return false;
+
+#if DEBUG
+ Log (LOG_BABBLE) << "TTN_GETDISPINFO " << iRow << " " << iCol << endLog;
+#endif
+
+ // get the tooltip text for that item/subitem
+ static StringCache<std::string> tooltip;
+ tooltip = "";
+ if (contents)
+ tooltip = (*contents)[iRow]->get_tooltip(iCol);
+
+ // set the tooltip text
+ NMTTDISPINFO *pNmTTDispInfo = (NMTTDISPINFO *)pNmHdr;
+ pNmTTDispInfo->lpszText = tooltip;
+ pNmTTDispInfo->hinst = NULL;
+ pNmTTDispInfo->uFlags = 0;
+
+ return true;
+ }
+ break;
}
// We don't care.
}
void
-ListView::setEmptyText(const char *text)
+ListView::setEmptyText(unsigned int text)
{
- empty_list_text = text;
+ empty_list_text = LoadStringW(text);
}
int
// construct menu
HMENU hMenu = CreatePopupMenu();
- MENUITEMINFO mii;
+ MENUITEMINFOW mii;
memset(&mii, 0, sizeof(mii));
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_FTYPE | MIIM_STATE | MIIM_STRING | MIIM_ID;
for (i = al->list.begin (); i != al->list.end (); ++i, ++j)
{
BOOL res;
- mii.dwTypeData = (char *)i->name.c_str();
+ mii.dwTypeData = const_cast <wchar_t *> (i->name.c_str());
mii.fState = (i->selected ? MFS_CHECKED : MFS_UNCHECKED |
i->enabled ? MFS_ENABLED : MFS_DISABLED);
mii.wID = j;
- res = InsertMenuItem(hMenu, -1, TRUE, &mii);
+ res = InsertMenuItemW(hMenu, -1, TRUE, &mii);
if (!res) Log (LOG_BABBLE) << "InsertMenuItem failed " << endLog;
}