[EXPLORER] Implement balloon queueing.

This commit is contained in:
David Quintana 2018-01-22 20:27:32 +01:00
parent bbca71c4a5
commit bc43733e48

View file

@ -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)