mirror of
https://github.com/reactos/reactos.git
synced 2025-08-06 17:12:58 +00:00
[EXPLORER] Implement balloon queueing.
This commit is contained in:
parent
bbca71c4a5
commit
bc43733e48
1 changed files with 226 additions and 87 deletions
|
@ -40,7 +40,6 @@ struct InternalIconData : NOTIFYICONDATA
|
||||||
UINT uVersionCopy;
|
UINT uVersionCopy;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct IconWatcherData
|
struct IconWatcherData
|
||||||
{
|
{
|
||||||
HANDLE hProcess;
|
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 :
|
class CNotifyToolbar :
|
||||||
public CWindowImplBaseT< CToolbar<InternalIconData>, CControlWinTraits >
|
public CWindowImplBaseT< CToolbar<InternalIconData>, CControlWinTraits >
|
||||||
|
@ -321,17 +517,13 @@ class CNotifyToolbar :
|
||||||
HIMAGELIST m_ImageList;
|
HIMAGELIST m_ImageList;
|
||||||
int m_VisibleButtonCount;
|
int m_VisibleButtonCount;
|
||||||
|
|
||||||
HWND m_BalloonsParent;
|
CBalloonQueue * m_BalloonQueue;
|
||||||
CTooltips * m_Balloons;
|
|
||||||
InternalIconData * m_currentTooltip;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CNotifyToolbar() :
|
CNotifyToolbar() :
|
||||||
m_ImageList(NULL),
|
m_ImageList(NULL),
|
||||||
m_VisibleButtonCount(0),
|
m_VisibleButtonCount(0),
|
||||||
m_BalloonsParent(NULL),
|
m_BalloonQueue(NULL)
|
||||||
m_Balloons(NULL),
|
|
||||||
m_currentTooltip(NULL)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,14 +647,15 @@ public:
|
||||||
StrNCpy(notifyItem->szInfoTitle, iconData->szInfoTitle, _countof(notifyItem->szInfo));
|
StrNCpy(notifyItem->szInfoTitle, iconData->szInfoTitle, _countof(notifyItem->szInfo));
|
||||||
notifyItem->dwInfoFlags = iconData->dwInfoFlags;
|
notifyItem->dwInfoFlags = iconData->dwInfoFlags;
|
||||||
notifyItem->uTimeout = iconData->uTimeout;
|
notifyItem->uTimeout = iconData->uTimeout;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_VisibleButtonCount++;
|
|
||||||
if (notifyItem->dwState & NIS_HIDDEN)
|
if (notifyItem->dwState & NIS_HIDDEN)
|
||||||
{
|
{
|
||||||
tbBtn.fsState |= TBSTATE_HIDDEN;
|
tbBtn.fsState |= TBSTATE_HIDDEN;
|
||||||
m_VisibleButtonCount--;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_VisibleButtonCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
|
/* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
|
||||||
|
@ -472,7 +665,7 @@ public:
|
||||||
|
|
||||||
if (iconData->uFlags & NIF_INFO)
|
if (iconData->uFlags & NIF_INFO)
|
||||||
{
|
{
|
||||||
UpdateBalloonTip(notifyItem);
|
m_BalloonQueue->UpdateInfo(notifyItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
@ -600,7 +793,7 @@ public:
|
||||||
|
|
||||||
if (iconData->uFlags & NIF_INFO)
|
if (iconData->uFlags & NIF_INFO)
|
||||||
{
|
{
|
||||||
UpdateBalloonTip(notifyItem);
|
m_BalloonQueue->UpdateInfo(notifyItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
@ -650,7 +843,7 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HideBalloonTip(notifyItem);
|
m_BalloonQueue->RemoveInfo(notifyItem);
|
||||||
|
|
||||||
DeleteButton(index);
|
DeleteButton(index);
|
||||||
|
|
||||||
|
@ -659,73 +852,6 @@ public:
|
||||||
return TRUE;
|
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)
|
VOID GetTooltipText(int index, LPTSTR szTip, DWORD cchTip)
|
||||||
{
|
{
|
||||||
|
@ -909,10 +1035,9 @@ public:
|
||||||
NOTIFY_CODE_HANDLER(TTN_SHOW, OnTooltipShow)
|
NOTIFY_CODE_HANDLER(TTN_SHOW, OnTooltipShow)
|
||||||
END_MSG_MAP()
|
END_MSG_MAP()
|
||||||
|
|
||||||
void Initialize(HWND hWndParent, CTooltips * tooltips)
|
void Initialize(HWND hWndParent, CBalloonQueue * queue)
|
||||||
{
|
{
|
||||||
m_BalloonsParent = hWndParent;
|
m_BalloonQueue = queue;
|
||||||
m_Balloons = tooltips;
|
|
||||||
|
|
||||||
DWORD styles =
|
DWORD styles =
|
||||||
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
|
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
|
||||||
|
@ -951,8 +1076,8 @@ class CSysPagerWnd :
|
||||||
public CIconWatcher
|
public CIconWatcher
|
||||||
{
|
{
|
||||||
CNotifyToolbar Toolbar;
|
CNotifyToolbar Toolbar;
|
||||||
|
|
||||||
CTooltips m_Balloons;
|
CTooltips m_Balloons;
|
||||||
|
CBalloonQueue m_BalloonQueue;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CSysPagerWnd() {}
|
CSysPagerWnd() {}
|
||||||
|
@ -983,7 +1108,7 @@ public:
|
||||||
|
|
||||||
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
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);
|
CIconWatcher::Initialize(m_hWnd);
|
||||||
|
|
||||||
HWND hWndTop = GetAncestor(m_hWnd, GA_ROOT);
|
HWND hWndTop = GetAncestor(m_hWnd, GA_ROOT);
|
||||||
|
@ -1001,9 +1126,11 @@ public:
|
||||||
BOOL ret = m_Balloons.AddTool(&ti);
|
BOOL ret = m_Balloons.AddTool(&ti);
|
||||||
if (!ret)
|
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
|
// Explicitly request running applications to re-register their systray icons
|
||||||
::SendNotifyMessageW(HWND_BROADCAST,
|
::SendNotifyMessageW(HWND_BROADCAST,
|
||||||
RegisterWindowMessageW(L"TaskbarCreated"),
|
RegisterWindowMessageW(L"TaskbarCreated"),
|
||||||
|
@ -1014,6 +1141,7 @@ public:
|
||||||
|
|
||||||
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
||||||
{
|
{
|
||||||
|
m_BalloonQueue.Deinit();
|
||||||
CIconWatcher::Uninitialize();
|
CIconWatcher::Uninitialize();
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
@ -1165,11 +1293,21 @@ public:
|
||||||
|
|
||||||
LRESULT OnBalloonPop(UINT uCode, LPNMHDR hdr , BOOL& bHandled)
|
LRESULT OnBalloonPop(UINT uCode, LPNMHDR hdr , BOOL& bHandled)
|
||||||
{
|
{
|
||||||
Toolbar.HideCurrentBalloon();
|
m_BalloonQueue.CloseCurrent();
|
||||||
bHandled = TRUE;
|
bHandled = TRUE;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
||||||
|
{
|
||||||
|
if (m_BalloonQueue.OnTimer(wParam))
|
||||||
|
{
|
||||||
|
bHandled = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void ResizeImagelist()
|
void ResizeImagelist()
|
||||||
{
|
{
|
||||||
Toolbar.ResizeImagelist();
|
Toolbar.ResizeImagelist();
|
||||||
|
@ -1183,6 +1321,7 @@ public:
|
||||||
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
|
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
|
||||||
MESSAGE_HANDLER(WM_SIZE, OnSize)
|
MESSAGE_HANDLER(WM_SIZE, OnSize)
|
||||||
MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu)
|
MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu)
|
||||||
|
MESSAGE_HANDLER(WM_TIMER, OnTimer)
|
||||||
NOTIFY_CODE_HANDLER(TTN_POP, OnBalloonPop)
|
NOTIFY_CODE_HANDLER(TTN_POP, OnBalloonPop)
|
||||||
NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW, OnGetInfoTip)
|
NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW, OnGetInfoTip)
|
||||||
NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
|
NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue