https://git.reactos.org/?p=reactos.git;a=commitdiff;h=bc43733e4842f4bcc5ea2…
commit bc43733e4842f4bcc5ea2d87fa9f69b442a3fcba
Author: David Quintana <gigaherz(a)gmail.com>
AuthorDate: Mon Jan 22 20:27:32 2018 +0100
Commit: David Quintana <gigaherz(a)gmail.com>
CommitDate: Tue Jan 23 22:13:01 2018 +0100
[EXPLORER] Implement balloon queueing.
---
base/shell/explorer/trayntfy.cpp | 313 ++++++++++++++++++++++++++++-----------
1 file changed, 226 insertions(+), 87 deletions(-)
diff --git a/base/shell/explorer/trayntfy.cpp b/base/shell/explorer/trayntfy.cpp
index 7e13d9f131..f61e1f6bb4 100644
--- a/base/shell/explorer/trayntfy.cpp
+++ b/base/shell/explorer/trayntfy.cpp
@@ -40,7 +40,6 @@ struct InternalIconData : NOTIFYICONDATA
UINT uVersionCopy;
};
-
struct IconWatcherData
{
HANDLE hProcess;
@@ -314,6 +313,203 @@ private:
}
};
+class CBalloonQueue
+{
+public:
+ static const int TimerInterval = 2000;
+ static const int BalloonsTimerId = 1;
+ static const int MinTimeout = 10000;
+ static const int MaxTimeout = 30000;
+ static const int CooldownBetweenBalloons = 2000;
+
+private:
+ struct Info
+ {
+ InternalIconData * pSource;
+ WCHAR szInfo[256];
+ WCHAR szInfoTitle[64];
+ WPARAM uIcon;
+ UINT uTimeout;
+
+ Info(InternalIconData * source)
+ {
+ pSource = source;
+ StrNCpy(szInfo, source->szInfo, _countof(szInfo));
+ StrNCpy(szInfoTitle, source->szInfoTitle, _countof(szInfoTitle));
+ uIcon = source->dwInfoFlags & NIIF_ICON_MASK;
+ if (source->dwInfoFlags == NIIF_USER)
+ uIcon = reinterpret_cast<WPARAM>(source->hIcon);
+ uTimeout = source->uTimeout;
+ }
+ };
+
+ HWND m_hwndParent;
+
+ CTooltips * m_tooltips;
+
+ CAtlList<Info> m_queue;
+
+ CToolbar<InternalIconData> * m_toolbar;
+
+ InternalIconData * m_current;
+ bool m_currentClosed;
+
+ int m_timer;
+
+public:
+ CBalloonQueue() :
+ m_hwndParent(NULL),
+ m_tooltips(NULL),
+ m_toolbar(NULL),
+ m_current(NULL),
+ m_currentClosed(false),
+ m_timer(-1)
+ {
+ }
+
+ void Init(HWND hwndParent, CToolbar<InternalIconData> * toolbar, CTooltips *
balloons)
+ {
+ m_hwndParent = hwndParent;
+ m_toolbar = toolbar;
+ m_tooltips = balloons;
+ }
+
+ void Deinit()
+ {
+ if (m_timer >= 0)
+ {
+ ::KillTimer(m_hwndParent, m_timer);
+ }
+ }
+
+ bool OnTimer(int timerId)
+ {
+ if (timerId != m_timer)
+ return false;
+
+ ::KillTimer(m_hwndParent, m_timer);
+ m_timer = -1;
+
+ if (m_current && !m_currentClosed)
+ {
+ Close(m_current);
+ }
+ else
+ {
+ m_current = NULL;
+ m_currentClosed = false;
+ if (!m_queue.IsEmpty())
+ {
+ Info info = m_queue.RemoveHead();
+ Show(info);
+ }
+ }
+
+ return true;
+ }
+
+ void UpdateInfo(InternalIconData * notifyItem)
+ {
+ size_t len = 0;
+ HRESULT hr = StringCchLength(notifyItem->szInfo,
_countof(notifyItem->szInfo), &len);
+ if (SUCCEEDED(hr) && len > 0)
+ {
+ Info info(notifyItem);
+
+ // If m_current == notifyItem, we want to replace the previous balloon even
if there is a queue.
+ if (m_current != notifyItem && (m_current != NULL ||
!m_queue.IsEmpty()))
+ {
+ m_queue.AddTail(info);
+ }
+ else
+ {
+ Show(info);
+ }
+ }
+ else
+ {
+ Close(notifyItem);
+ }
+ }
+
+ void RemoveInfo(InternalIconData * notifyItem)
+ {
+ Close(notifyItem);
+
+ POSITION position = m_queue.GetHeadPosition();
+ while(position != NULL)
+ {
+ Info& info = m_queue.GetNext(position);
+ if (info.pSource == notifyItem)
+ {
+ m_queue.RemoveAt(position);
+ }
+ }
+ }
+
+ void CloseCurrent()
+ {
+ if (m_current != NULL)
+ Close(m_current);
+ }
+
+private:
+
+ int IndexOf(InternalIconData * pdata)
+ {
+ int count = m_toolbar->GetButtonCount();
+ for (int i = 0; i < count; i++)
+ {
+ if (m_toolbar->GetItemData(i) == pdata)
+ return i;
+ }
+ return -1;
+ }
+
+ void SetTimer(int length)
+ {
+ m_timer = ::SetTimer(m_hwndParent, BalloonsTimerId, length, NULL);
+ }
+
+ void Show(Info& info)
+ {
+ TRACE("ShowBalloonTip called for flags=%x text=%ws; title=%ws\n",
info.uIcon, info.szInfo, info.szInfoTitle);
+
+ // TODO: NIF_REALTIME, NIIF_NOSOUND, other Vista+ flags
+
+ const int index = IndexOf(info.pSource);
+ RECT rc;
+ m_toolbar->GetItemRect(index, &rc);
+ m_toolbar->ClientToScreen(&rc);
+ const WORD x = (rc.left + rc.right) / 2;
+ const WORD y = (rc.top + rc.bottom) / 2;
+
+ m_tooltips->SetTitle(info.szInfoTitle, info.uIcon);
+ m_tooltips->TrackPosition(x, y);
+ m_tooltips->UpdateTipText(m_hwndParent,
reinterpret_cast<LPARAM>(m_toolbar->m_hWnd), info.szInfo);
+ m_tooltips->TrackActivate(m_hwndParent,
reinterpret_cast<LPARAM>(m_toolbar->m_hWnd));
+
+ m_current = info.pSource;
+ int timeout = info.uTimeout;
+ if (timeout < MinTimeout) timeout = MinTimeout;
+ if (timeout > MaxTimeout) timeout = MaxTimeout;
+
+ SetTimer(timeout);
+ }
+
+ void Close(IN OUT InternalIconData * notifyItem)
+ {
+ TRACE("HideBalloonTip called\n");
+
+ if (m_current == notifyItem && !m_currentClosed)
+ {
+ // Prevent Re-entry
+ m_currentClosed = true;
+ m_tooltips->TrackDeactivate();
+ SetTimer(CooldownBetweenBalloons);
+ }
+ }
+};
class CNotifyToolbar :
public CWindowImplBaseT< CToolbar<InternalIconData>, CControlWinTraits >
@@ -321,17 +517,13 @@ class CNotifyToolbar :
HIMAGELIST m_ImageList;
int m_VisibleButtonCount;
- HWND m_BalloonsParent;
- CTooltips * m_Balloons;
- InternalIconData * m_currentTooltip;
+ CBalloonQueue * m_BalloonQueue;
public:
CNotifyToolbar() :
m_ImageList(NULL),
m_VisibleButtonCount(0),
- m_BalloonsParent(NULL),
- m_Balloons(NULL),
- m_currentTooltip(NULL)
+ m_BalloonQueue(NULL)
{
}
@@ -455,14 +647,15 @@ public:
StrNCpy(notifyItem->szInfoTitle, iconData->szInfoTitle,
_countof(notifyItem->szInfo));
notifyItem->dwInfoFlags = iconData->dwInfoFlags;
notifyItem->uTimeout = iconData->uTimeout;
-
}
- m_VisibleButtonCount++;
if (notifyItem->dwState & NIS_HIDDEN)
{
tbBtn.fsState |= TBSTATE_HIDDEN;
- m_VisibleButtonCount--;
+ }
+ else
+ {
+ m_VisibleButtonCount++;
}
/* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
@@ -472,7 +665,7 @@ public:
if (iconData->uFlags & NIF_INFO)
{
- UpdateBalloonTip(notifyItem);
+ m_BalloonQueue->UpdateInfo(notifyItem);
}
return TRUE;
@@ -600,7 +793,7 @@ public:
if (iconData->uFlags & NIF_INFO)
{
- UpdateBalloonTip(notifyItem);
+ m_BalloonQueue->UpdateInfo(notifyItem);
}
return TRUE;
@@ -650,7 +843,7 @@ public:
}
}
- HideBalloonTip(notifyItem);
+ m_BalloonQueue->RemoveInfo(notifyItem);
DeleteButton(index);
@@ -659,73 +852,6 @@ public:
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<WPARAM>(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<LPARAM>(m_hWnd), notifyItem->szInfo);
- m_Balloons->TrackActivate(m_BalloonsParent,
reinterpret_cast<LPARAM>(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)
{
@@ -909,10 +1035,9 @@ public:
NOTIFY_CODE_HANDLER(TTN_SHOW, OnTooltipShow)
END_MSG_MAP()
- void Initialize(HWND hWndParent, CTooltips * tooltips)
+ void Initialize(HWND hWndParent, CBalloonQueue * queue)
{
- m_BalloonsParent = hWndParent;
- m_Balloons = tooltips;
+ m_BalloonQueue = queue;
DWORD styles =
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
@@ -951,8 +1076,8 @@ class CSysPagerWnd :
public CIconWatcher
{
CNotifyToolbar Toolbar;
-
CTooltips m_Balloons;
+ CBalloonQueue m_BalloonQueue;
public:
CSysPagerWnd() {}
@@ -983,7 +1108,7 @@ public:
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
- Toolbar.Initialize(m_hWnd, &m_Balloons);
+ Toolbar.Initialize(m_hWnd, &m_BalloonQueue);
CIconWatcher::Initialize(m_hWnd);
HWND hWndTop = GetAncestor(m_hWnd, GA_ROOT);
@@ -1001,9 +1126,11 @@ public:
BOOL ret = m_Balloons.AddTool(&ti);
if (!ret)
{
- DbgPrint("AddTool failed, LastError=%d (probably meaningless unless
non-zero)", GetLastError());
+ WARN("AddTool failed, LastError=%d (probably meaningless unless
non-zero)\n", GetLastError());
}
+ m_BalloonQueue.Init(m_hWnd, &Toolbar, &m_Balloons);
+
// Explicitly request running applications to re-register their systray icons
::SendNotifyMessageW(HWND_BROADCAST,
RegisterWindowMessageW(L"TaskbarCreated"),
@@ -1014,6 +1141,7 @@ public:
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
+ m_BalloonQueue.Deinit();
CIconWatcher::Uninitialize();
return TRUE;
}
@@ -1165,11 +1293,21 @@ public:
LRESULT OnBalloonPop(UINT uCode, LPNMHDR hdr , BOOL& bHandled)
{
- Toolbar.HideCurrentBalloon();
+ m_BalloonQueue.CloseCurrent();
bHandled = TRUE;
return 0;
}
+ LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+ {
+ if (m_BalloonQueue.OnTimer(wParam))
+ {
+ bHandled = TRUE;
+ }
+
+ return 0;
+ }
+
void ResizeImagelist()
{
Toolbar.ResizeImagelist();
@@ -1183,6 +1321,7 @@ public:
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu)
+ MESSAGE_HANDLER(WM_TIMER, OnTimer)
NOTIFY_CODE_HANDLER(TTN_POP, OnBalloonPop)
NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW, OnGetInfoTip)
NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)