From bbca71c4a5604f4919d792c4b414ae84a05c4594 Mon Sep 17 00:00:00 2001 From: David Quintana Date: Fri, 19 Jan 2018 16:39:46 +0100 Subject: [PATCH] [EXPLORER] Implement rudimentary uVersion management, and notification balloons. - uVersion will only be truly useful when Vista+'s V4 style notification icons are implemented. - Balloon notifications do not yet support queuing and auto-closing. - Force the notification icon tooltips to always show even if the taskbar isn't foreground. [ROSCTRLS.H] Implement CTooltips class which manages a comctl32 tooltips window. --- base/shell/explorer/trayntfy.cpp | 217 ++++++++++++++++++++++++++--- sdk/include/reactos/rosctrls.h | 225 +++++++++++++++++++++++++++++++ 2 files changed, 421 insertions(+), 21 deletions(-) diff --git a/base/shell/explorer/trayntfy.cpp b/base/shell/explorer/trayntfy.cpp index 1864e33e96f..7e13d9f131f 100644 --- a/base/shell/explorer/trayntfy.cpp +++ b/base/shell/explorer/trayntfy.cpp @@ -34,6 +34,12 @@ typedef struct _SYS_PAGER_COPY_DATA NOTIFYICONDATA nicon_data; } SYS_PAGER_COPY_DATA, *PSYS_PAGER_COPY_DATA; +struct InternalIconData : NOTIFYICONDATA +{ + // Must keep a separate copy since the original is unioned with uTimeout. + UINT uVersionCopy; +}; + struct IconWatcherData { @@ -310,15 +316,22 @@ private: class CNotifyToolbar : - public CWindowImplBaseT< CToolbar, CControlWinTraits > + public CWindowImplBaseT< CToolbar, CControlWinTraits > { HIMAGELIST m_ImageList; int m_VisibleButtonCount; + HWND m_BalloonsParent; + CTooltips * m_Balloons; + InternalIconData * m_currentTooltip; + public: CNotifyToolbar() : m_ImageList(NULL), - m_VisibleButtonCount(0) + m_VisibleButtonCount(0), + m_BalloonsParent(NULL), + m_Balloons(NULL), + m_currentTooltip(NULL) { } @@ -331,15 +344,13 @@ public: return m_VisibleButtonCount; } - int FindItem(IN HWND hWnd, IN UINT uID, NOTIFYICONDATA ** pdata) + int FindItem(IN HWND hWnd, IN UINT uID, InternalIconData ** pdata) { int count = GetButtonCount(); for (int i = 0; i < count; i++) { - NOTIFYICONDATA * data; - - data = GetItemData(i); + InternalIconData * data = GetItemData(i); if (data->hWnd == hWnd && data->uID == uID) @@ -358,7 +369,7 @@ public: int count = GetButtonCount(); for (int i = 0; i < count; i++) { - NOTIFYICONDATA * data = GetItemData(i); + InternalIconData * data = GetItemData(i); if (data->hIcon == handle) { TBBUTTON btn; @@ -373,7 +384,7 @@ public: BOOL AddButton(IN CONST NOTIFYICONDATA *iconData) { TBBUTTON tbBtn; - NOTIFYICONDATA * notifyItem; + InternalIconData * notifyItem; WCHAR text[] = L""; TRACE("Adding icon %d from hWnd %08x flags%s%s state%s%s", @@ -390,7 +401,7 @@ public: return FALSE; } - notifyItem = new NOTIFYICONDATA(); + notifyItem = new InternalIconData(); ZeroMemory(notifyItem, sizeof(*notifyItem)); notifyItem->hWnd = iconData->hWnd; @@ -437,6 +448,16 @@ public: StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip); } + if (iconData->uFlags & NIF_INFO) + { + // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always. + StrNCpy(notifyItem->szInfo, iconData->szInfo, _countof(notifyItem->szInfo)); + StrNCpy(notifyItem->szInfoTitle, iconData->szInfoTitle, _countof(notifyItem->szInfo)); + notifyItem->dwInfoFlags = iconData->dwInfoFlags; + notifyItem->uTimeout = iconData->uTimeout; + + } + m_VisibleButtonCount++; if (notifyItem->dwState & NIS_HIDDEN) { @@ -444,17 +465,45 @@ public: m_VisibleButtonCount--; } - /* TODO: support NIF_INFO, NIF_GUID, NIF_REALTIME, NIF_SHOWTIP */ + /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */ CToolbar::AddButton(&tbBtn); SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); + if (iconData->uFlags & NIF_INFO) + { + UpdateBalloonTip(notifyItem); + } + + return TRUE; + } + + BOOL SwitchVersion(IN CONST NOTIFYICONDATA *iconData) + { + InternalIconData * notifyItem; + int index = FindItem(iconData->hWnd, iconData->uID, ¬ifyItem); + if (index < 0) + { + WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData->uID, iconData->hWnd); + return FALSE; + } + + if (iconData->uVersion != 0 && iconData->uVersion != NOTIFYICON_VERSION) + { + WARN("Tried to set the version of icon %d from hWnd %08x, to an unknown value %d. Vista+ program?", iconData->uID, iconData->hWnd, iconData->uVersion); + return FALSE; + } + + // We can not store the version in the uVersion field, because it's union'd with uTimeout, + // which we also need to keep track of. + notifyItem->uVersionCopy = iconData->uVersion; + return TRUE; } BOOL UpdateButton(IN CONST NOTIFYICONDATA *iconData) { - NOTIFYICONDATA * notifyItem; + InternalIconData * notifyItem; TBBUTTONINFO tbbi = { 0 }; TRACE("Updating icon %d from hWnd %08x flags%s%s state%s%s", @@ -536,16 +585,30 @@ public: StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip); } - /* TODO: support NIF_INFO, NIF_GUID, NIF_REALTIME, NIF_SHOWTIP */ + if (iconData->uFlags & NIF_INFO) + { + // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always. + StrNCpy(notifyItem->szInfo, iconData->szInfo, _countof(notifyItem->szInfo)); + StrNCpy(notifyItem->szInfoTitle, iconData->szInfoTitle, _countof(notifyItem->szInfo)); + notifyItem->dwInfoFlags = iconData->dwInfoFlags; + notifyItem->uTimeout = iconData->uTimeout; + } + + /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */ SetButtonInfo(index, &tbbi); + if (iconData->uFlags & NIF_INFO) + { + UpdateBalloonTip(notifyItem); + } + return TRUE; } BOOL RemoveButton(IN CONST NOTIFYICONDATA *iconData) { - NOTIFYICONDATA * notifyItem; + InternalIconData * notifyItem; TRACE("Removing icon %d from hWnd %08x", iconData->uID, iconData->hWnd); @@ -587,16 +650,86 @@ public: } } - delete notifyItem; + HideBalloonTip(notifyItem); + DeleteButton(index); + delete notifyItem; + return TRUE; } + void UpdateBalloonTip(InternalIconData* notifyItem) + { + size_t len = 0; + if (SUCCEEDED(StringCchLength(notifyItem->szInfo, _countof(notifyItem->szInfo), &len)) && len > 0) + { + ShowBalloonTip(notifyItem); + } + else + { + HideBalloonTip(notifyItem); + } + } + + static WPARAM GetTitleIcon(DWORD dwFlags, HICON hIcon) + { + if (dwFlags & NIIF_USER) + return reinterpret_cast(hIcon); + + return dwFlags & 3; + } + + BOOL ShowBalloonTip(IN OUT InternalIconData *notifyItem) + { + DbgPrint("ShowBalloonTip called for flags=%x text=%ws; title=%ws", notifyItem->dwInfoFlags, notifyItem->szInfo, notifyItem->szInfoTitle); + + // TODO: Queueing -> NIF_REALTIME? (Vista+) + // TODO: NIIF_NOSOUND, Vista+ flags + + const WPARAM icon = GetTitleIcon(notifyItem->dwInfoFlags, notifyItem->hIcon); + BOOL ret = m_Balloons->SetTitle(notifyItem->szInfoTitle, icon); + if (!ret) + DbgPrint("SetTitle failed, GetLastError=%d", GetLastError()); + + const int index = FindItem(notifyItem->hWnd, notifyItem->uID, NULL); + RECT rc; + GetItemRect(index, &rc); + ClientToScreen(&rc); // I have no idea why this is needed! >_< + WORD x = (rc.left + rc.right) / 2; + WORD y = (rc.top + rc.bottom) / 2; + DbgPrint("ClientToScreen returned (%d, %d, %d, %d) x=%d, y=%d", + rc.left, rc.top, + rc.right, rc.bottom, x, y); + m_Balloons->TrackPosition(x, y); + m_Balloons->UpdateTipText(m_BalloonsParent, reinterpret_cast(m_hWnd), notifyItem->szInfo); + m_Balloons->TrackActivate(m_BalloonsParent, reinterpret_cast(m_hWnd)); + m_currentTooltip = notifyItem; + + return TRUE; + } + + VOID HideBalloonTip(IN OUT InternalIconData *notifyItem) + { + DbgPrint("HideBalloonTip called"); + + if (m_currentTooltip == notifyItem) + { + // Prevent Re-entry + m_currentTooltip = NULL; + m_Balloons->TrackDeactivate(); + } + } + + VOID HideCurrentBalloon() + { + if (m_currentTooltip != NULL) + HideBalloonTip(m_currentTooltip); + } + VOID GetTooltipText(int index, LPTSTR szTip, DWORD cchTip) { - NOTIFYICONDATA * notifyItem; - notifyItem = GetItemData(index); + InternalIconData * notifyItem = GetItemData(index); if (notifyItem) { @@ -626,7 +759,7 @@ public: int count = GetButtonCount(); for (int i = 0; i < count; i++) { - NOTIFYICONDATA * data = GetItemData(i); + InternalIconData * data = GetItemData(i); BOOL hasSharedIcon = data->dwState & NIS_SHAREDICON; INT iIcon = hasSharedIcon ? FindExistingSharedIcon(data->hIcon) : -1; if (iIcon < 0) @@ -659,7 +792,7 @@ private: L"WM_XBUTTONDBLCLK" }; - NOTIFYICONDATA * notifyItem = GetItemData(wIndex); + InternalIconData * notifyItem = GetItemData(wIndex); if (!::IsWindow(notifyItem->hWnd)) { @@ -770,15 +903,17 @@ private: return 0; } - public: BEGIN_MSG_MAP(CNotifyToolbar) MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseEvent) NOTIFY_CODE_HANDLER(TTN_SHOW, OnTooltipShow) END_MSG_MAP() - void Initialize(HWND hWndParent) + void Initialize(HWND hWndParent, CTooltips * tooltips) { + m_BalloonsParent = hWndParent; + m_Balloons = tooltips; + DWORD styles = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | TBSTYLE_FLAT | TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | TBSTYLE_TRANSPARENT | @@ -786,6 +921,13 @@ public: SubclassWindow(CToolbar::Create(hWndParent, styles)); + // Force the toolbar tooltips window to always show tooltips even if not foreground + HWND tooltipsWnd = (HWND)SendMessageW(TB_GETTOOLTIPS); + if (tooltipsWnd) + { + ::SetWindowLong(tooltipsWnd, GWL_STYLE, ::GetWindowLong(tooltipsWnd, GWL_STYLE) | TTS_ALWAYSTIP); + } + SetWindowTheme(m_hWnd, L"TrayNotify", NULL); m_ImageList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000); @@ -810,6 +952,8 @@ class CSysPagerWnd : { CNotifyToolbar Toolbar; + CTooltips m_Balloons; + public: CSysPagerWnd() {} virtual ~CSysPagerWnd() {} @@ -839,9 +983,27 @@ public: LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { - Toolbar.Initialize(m_hWnd); + Toolbar.Initialize(m_hWnd, &m_Balloons); CIconWatcher::Initialize(m_hWnd); + HWND hWndTop = GetAncestor(m_hWnd, GA_ROOT); + + m_Balloons.Create(hWndTop, TTS_NOPREFIX | TTS_BALLOON | TTS_CLOSE); + + TOOLINFOW ti = { 0 }; + ti.cbSize = TTTOOLINFOW_V1_SIZE; + ti.uFlags = TTF_TRACK | TTF_IDISHWND; + ti.uId = reinterpret_cast(Toolbar.m_hWnd); + ti.hwnd = m_hWnd; + ti.lpszText = NULL; + ti.lParam = NULL; + + BOOL ret = m_Balloons.AddTool(&ti); + if (!ret) + { + DbgPrint("AddTool failed, LastError=%d (probably meaningless unless non-zero)", GetLastError()); + } + // Explicitly request running applications to re-register their systray icons ::SendNotifyMessageW(HWND_BROADCAST, RegisterWindowMessageW(L"TaskbarCreated"), @@ -890,6 +1052,11 @@ public: (void)RemoveIconFromWatcher(iconData); } break; + case NIM_SETFOCUS: + Toolbar.SetFocus(); + ret = TRUE; + case NIM_SETVERSION: + ret = Toolbar.SwitchVersion(iconData); default: TRACE("NotifyIconCmd received with unknown code %d.\n", data->notify_code); return FALSE; @@ -996,6 +1163,13 @@ public: return 0; } + LRESULT OnBalloonPop(UINT uCode, LPNMHDR hdr , BOOL& bHandled) + { + Toolbar.HideCurrentBalloon(); + bHandled = TRUE; + return 0; + } + void ResizeImagelist() { Toolbar.ResizeImagelist(); @@ -1009,6 +1183,7 @@ public: MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground) MESSAGE_HANDLER(WM_SIZE, OnSize) MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu) + NOTIFY_CODE_HANDLER(TTN_POP, OnBalloonPop) NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW, OnGetInfoTip) NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw) END_MSG_MAP() diff --git a/sdk/include/reactos/rosctrls.h b/sdk/include/reactos/rosctrls.h index a5976416d80..f332aca915a 100644 --- a/sdk/include/reactos/rosctrls.h +++ b/sdk/include/reactos/rosctrls.h @@ -581,3 +581,228 @@ public: } }; + +class CTooltips : + public CWindow +{ +public: // Configuration methods + + HWND Create(HWND hWndParent, DWORD dwStyles = WS_POPUP | TTS_NOPREFIX, DWORD dwExStyles = WS_EX_TOPMOST) + { + RECT r = { 0 }; + return CWindow::Create(TOOLTIPS_CLASS, hWndParent, r, L"", dwStyles, dwExStyles); + } + +public: // Relay event + + // Win7+: Can use GetMessageExtraInfo to provide the WPARAM value. + VOID RelayEvent(MSG * pMsg, WPARAM extraInfo = 0) + { + SendMessageW(TTM_RELAYEVENT, extraInfo, reinterpret_cast(pMsg)); + } + +public: // Helpers + + INT GetToolCount() + { + return SendMessageW(TTM_GETTOOLCOUNT, 0, 0); + } + + BOOL AddTool(IN CONST TTTOOLINFOW * pInfo) + { + return SendMessageW(TTM_ADDTOOL, 0, reinterpret_cast(pInfo)); + } + + VOID DelTool(IN HWND hwndToolOwner, IN UINT uId) + { + TTTOOLINFOW info = { sizeof(TTTOOLINFOW), 0 }; + info.hwnd = hwndToolOwner; + info.uId = uId; + SendMessageW(TTM_DELTOOL, 0, reinterpret_cast(&info)); + } + + VOID NewToolRect(IN HWND hwndToolOwner, IN UINT uId, IN RECT rect) + { + TTTOOLINFOW info = { sizeof(TTTOOLINFOW), 0 }; + info.hwnd = hwndToolOwner; + info.uId = uId; + info.rect = rect; + SendMessageW(TTM_NEWTOOLRECT, 0, reinterpret_cast(&info)); + } + + BOOL GetToolInfo(IN HWND hwndToolOwner, IN UINT uId, IN OUT TTTOOLINFOW * pInfo) + { + pInfo->hwnd = hwndToolOwner; + pInfo->uId = uId; + return SendMessageW(TTM_GETTOOLINFO, 0, reinterpret_cast(pInfo)); + } + + VOID SetToolInfo(IN CONST TTTOOLINFOW * pInfo) + { + SendMessageW(TTM_SETTOOLINFO, 0, reinterpret_cast(pInfo)); + } + + BOOL HitTest(IN CONST TTHITTESTINFOW * pInfo) + { + return SendMessageW(TTM_HITTEST, 0, reinterpret_cast(pInfo)); + } + + VOID GetText(IN HWND hwndToolOwner, IN UINT uId, OUT PWSTR pBuffer, IN DWORD cchBuffer) + { + TTTOOLINFOW info = { sizeof(TTTOOLINFOW), 0 }; + info.hwnd = hwndToolOwner; + info.uId = uId; + info.lpszText = pBuffer; + SendMessageW(TTM_GETTEXT, cchBuffer, reinterpret_cast(&info)); + } + + VOID UpdateTipText(IN HWND hwndToolOwner, IN UINT uId, IN PCWSTR szText, IN HINSTANCE hinstResourceOwner = NULL) + { + TTTOOLINFOW info = { sizeof(TTTOOLINFOW), 0 }; + info.hwnd = hwndToolOwner; + info.uId = uId; + info.lpszText = const_cast(szText); + info.hinst = hinstResourceOwner; + SendMessageW(TTM_UPDATETIPTEXT, 0, reinterpret_cast(&info)); + } + + BOOL EnumTools(IN CONST TTTOOLINFOW * pInfo) + { + return SendMessageW(TTM_ENUMTOOLS, 0, reinterpret_cast(pInfo)); + } + + BOOL GetCurrentTool(OUT OPTIONAL TTTOOLINFOW * pInfo = NULL) + { + return SendMessageW(TTM_GETCURRENTTOOL, 0, reinterpret_cast(pInfo)); + } + + VOID GetTitle(TTGETTITLE * pTitleInfo) + { + SendMessageW(TTM_GETTITLE, 0, reinterpret_cast(pTitleInfo)); + } + + BOOL SetTitle(PCWSTR szTitleText, WPARAM icon = 0) + { + return SendMessageW(TTM_SETTITLE, icon, reinterpret_cast(szTitleText)); + } + + VOID TrackActivate(IN HWND hwndToolOwner, IN UINT uId) + { + TTTOOLINFOW info = { sizeof(TTTOOLINFOW), 0 }; + info.hwnd = hwndToolOwner; + info.uId = uId; + SendMessageW(TTM_TRACKACTIVATE, TRUE, reinterpret_cast(&info)); + } + + VOID TrackDeactivate() + { + SendMessageW(TTM_TRACKACTIVATE, FALSE, NULL); + } + + VOID TrackPosition(IN WORD x, IN WORD y) + { + SendMessageW(TTM_TRACKPOSITION, 0, MAKELPARAM(x, y)); + } + + // Opens the tooltip + VOID Popup() + { + SendMessageW(TTM_POPUP); + } + + // Closes the tooltip - Pressing the [X] for a TTF_CLOSE balloon is equivalent to calling this + VOID Pop() + { + SendMessageW(TTM_POP); + } + + // Delay times for AUTOMATIC tooltips (they don't affect balloons) + INT GetDelayTime(UINT which) + { + return SendMessageW(TTM_GETDELAYTIME, which); + } + + VOID SetDelayTime(UINT which, WORD time) + { + SendMessageW(TTM_SETDELAYTIME, which, MAKELPARAM(time, 0)); + } + + // Activates or deactivates the automatic tooltip display when hovering a control + VOID Activate(IN BOOL bActivate = TRUE) + { + SendMessageW(TTM_ACTIVATE, bActivate); + } + + // Adjusts the position of a tooltip when used to display trimmed text + VOID AdjustRect(IN BOOL bTextToWindow, IN OUT RECT * pRect) + { + SendMessageW(TTM_ADJUSTRECT, bTextToWindow, reinterpret_cast(pRect)); + } + + // Useful for TTF_ABSOLUTE|TTF_TRACK tooltip positioning + SIZE GetBubbleSize(IN TTTOOLINFOW * pInfo) + { + DWORD ret = SendMessageW(TTM_GETBUBBLESIZE, 0, reinterpret_cast(pInfo)); + const SIZE sz = { LOWORD(ret), HIWORD(ret) }; + return sz; + } + + // Fills the RECT with the margin size previously set. Default is 0 margins. + VOID GetMargin(OUT RECT * pRect) + { + SendMessageW(TTM_GETMARGIN, 0, reinterpret_cast(pRect)); + } + + VOID SetMargin(IN RECT * pRect) + { + SendMessageW(TTM_SETMARGIN, 0, reinterpret_cast(pRect)); + } + + // Gets a previously established max width. Returns -1 if no limit is set + INT GetMaxTipWidth() + { + return SendMessageW(TTM_GETMAXTIPWIDTH); + } + + INT SetMaxTipWidth(IN OPTIONAL INT width = -1) + { + return SendMessageW(TTM_SETMAXTIPWIDTH, 0, width); + } + + // Get the color of the tooltip text + COLORREF GetTipTextColor() + { + return SendMessageW(TTM_GETTIPTEXTCOLOR); + } + + VOID SetTipTextColor(IN COLORREF textColor) + { + SendMessageW(TTM_SETTIPTEXTCOLOR, textColor); + } + + COLORREF GetTipBkColor() + { + return SendMessageW(TTM_GETTIPBKCOLOR); + } + + VOID SetTipBkColor(IN COLORREF textColor) + { + SendMessageW(TTM_SETTIPBKCOLOR, textColor); + } + + VOID SetWindowTheme(IN PCWSTR szThemeName) + { + SendMessageW(TTM_SETWINDOWTHEME, 0, reinterpret_cast(szThemeName)); + } + + // Forces redraw + VOID Update() + { + SendMessageW(TTM_UPDATE); + } + + HWND WindowFromPoint(IN POINT * pPoint) + { + return reinterpret_cast(SendMessageW(TTM_WINDOWFROMPOINT, 0, reinterpret_cast(pPoint))); + } +};