4090 lines
122 KiB
C++
4090 lines
122 KiB
C++
#include "cabinet.h"
|
|
#include "trayclok.h"
|
|
#include <atlstuff.h>
|
|
#include "traynot.h"
|
|
#include "rcids.h"
|
|
#include "tray.h"
|
|
#include "util.h"
|
|
#include "shellapi.h"
|
|
|
|
//
|
|
// Tray Notify Icon area implementation notes / details:
|
|
//
|
|
// - The icons are held in a toolbar with CTrayItem * on each button's lParam
|
|
//
|
|
|
|
//
|
|
// #defines for TrayNotify
|
|
//
|
|
|
|
// Internal Tray Notify Timer IDs
|
|
#define TID_DEMOTEDMENU 2
|
|
#define TID_BALLOONPOP 3
|
|
#define TID_BALLOONPOPWAIT 4
|
|
#define TID_BALLOONSHOW 5
|
|
#define TID_RUDEAPPHIDE 6 // When a fullscreen (rude) app has gone away
|
|
|
|
|
|
#define KEYBOARD_VERSION 3
|
|
|
|
#define TT_CHEVRON_INFOTIP_INTERVAL 30000 // 30 seconds
|
|
#define TT_BALLOONPOP_INTERVAL 50
|
|
#define TT_BALLOONPOP_INTERVAL_INCREMENT 30
|
|
#define TT_BALLOONSHOW_INTERVAL 3000 // 3 seconds
|
|
#define TT_RUDEAPPHIDE_INTERVAL 10000 // 10 seconds
|
|
|
|
#define TT_DEMOTEDMENU_INTERVAL (2 * g_uDoubleClick)
|
|
|
|
#define MAX_TIP_WIDTH 300
|
|
|
|
#define MIN_INFO_TIME 10000 // 10 secs is minimum time a balloon can be up
|
|
#define MAX_INFO_TIME 60000 // 1 min is the max time it can be up
|
|
|
|
|
|
// Atleast 2 items are necessary to "demote" them under the chevron...
|
|
#define MIN_DEMOTED_ITEMS_THRESHOLD 2
|
|
|
|
#define PGMP_RECALCSIZE 200
|
|
|
|
#define BALLOON_INTERVAL_MAX 10000
|
|
#define BALLOON_INTERVAL_MEDIUM 3000
|
|
#define BALLOON_INTERVAL_MIN 1000
|
|
|
|
const TCHAR CTrayNotify::c_szTrayNotify[] = TEXT("TrayNotifyWnd");
|
|
const WCHAR CTrayNotify::c_wzTrayNotifyTheme[] = L"TrayNotify";
|
|
const WCHAR CTrayNotify::c_wzTrayNotifyHorizTheme[] = L"TrayNotifyHoriz";
|
|
const WCHAR CTrayNotify::c_wzTrayNotifyVertTheme[] = L"TrayNotifyVert";
|
|
const WCHAR CTrayNotify::c_wzTrayNotifyHorizOpenTheme[] = L"TrayNotifyHorizOpen";
|
|
const WCHAR CTrayNotify::c_wzTrayNotifyVertOpenTheme[] = L"TrayNotifyVertOpen";
|
|
|
|
//
|
|
// Global functions...
|
|
//
|
|
int CALLBACK DeleteDPAPtrCB(TNINFOITEM *pItem, void *pData);
|
|
|
|
int CALLBACK DeleteDPAPtrCB(TNINFOITEM *pItem, void *pData)
|
|
{
|
|
LocalFree(pItem);
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Stub for CTrayNotify, so as to not break the COM rules of refcounting a static object
|
|
//
|
|
class ATL_NO_VTABLE CTrayNotifyStub :
|
|
public CComObjectRootEx<CComSingleThreadModel>,
|
|
public CComCoClass<CTrayNotifyStub, &CLSID_TrayNotify>,
|
|
public ITrayNotify
|
|
{
|
|
public:
|
|
CTrayNotifyStub() {};
|
|
virtual ~CTrayNotifyStub() {};
|
|
|
|
DECLARE_NOT_AGGREGATABLE(CTrayNotifyStub)
|
|
|
|
BEGIN_COM_MAP(CTrayNotifyStub)
|
|
COM_INTERFACE_ENTRY(ITrayNotify)
|
|
END_COM_MAP()
|
|
|
|
// *** ITrayNotify methoid ***
|
|
STDMETHODIMP SetPreference(LPNOTIFYITEM pNotifyItem);
|
|
STDMETHODIMP RegisterCallback(INotificationCB* pNotifyCB);
|
|
STDMETHODIMP EnableAutoTray(BOOL bTraySetting);
|
|
};
|
|
|
|
//
|
|
// CTrayNotifyStub functions...
|
|
//
|
|
HRESULT CTrayNotifyStub::SetPreference(LPNOTIFYITEM pNotifyItem)
|
|
{
|
|
return c_tray._trayNotify.SetPreference(pNotifyItem);
|
|
}
|
|
|
|
HRESULT CTrayNotifyStub::RegisterCallback(INotificationCB* pNotifyCB)
|
|
{
|
|
return c_tray._trayNotify.RegisterCallback(pNotifyCB);
|
|
}
|
|
|
|
HRESULT CTrayNotifyStub::EnableAutoTray(BOOL bTraySetting)
|
|
{
|
|
return c_tray._trayNotify.EnableAutoTray(bTraySetting);
|
|
}
|
|
|
|
HRESULT CTrayNotifyStub_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk)
|
|
{
|
|
if (pUnkOuter != NULL)
|
|
return CLASS_E_NOAGGREGATION;
|
|
|
|
CComObject<CTrayNotifyStub> *pStub = new CComObject<CTrayNotifyStub>;
|
|
if (pStub)
|
|
return pStub->QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
|
|
else
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
|
|
//
|
|
// CTrayNotify Methods..
|
|
//
|
|
|
|
// IUnknown methods
|
|
STDMETHODIMP_(ULONG) CTrayNotify::AddRef()
|
|
{
|
|
return InterlockedIncrement(&m_cRef);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CTrayNotify::Release()
|
|
{
|
|
if (InterlockedDecrement(&m_cRef))
|
|
return m_cRef;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef FULL_DEBUG
|
|
|
|
void CTrayNotify::_TestNotify()
|
|
{
|
|
// Loop thru the toolbar
|
|
INT_PTR iCount = m_TrayItemManager.GetItemCount();
|
|
for (int i = 0; i < iCount; i++)
|
|
{
|
|
TBBUTTONINFO tbbi;
|
|
TCHAR szButtonText[MAX_PATH];
|
|
|
|
tbbi.cbSize = sizeof(TBBUTTONINFO);
|
|
tbbi.dwMask = TBIF_BYINDEX | TBIF_IMAGE | TBIF_TEXT | TBIF_COMMAND;
|
|
tbbi.pszText = szButtonText;
|
|
tbbi.cchText = ARRAYSIZE(szButtonText);
|
|
|
|
INT_PTR j = SendMessage(_hwndToolbar, TB_GETBUTTONINFO, i, (LPARAM)&tbbi);
|
|
if (j != -1)
|
|
{
|
|
TCHAR tempBuf[MAX_PATH];
|
|
wsprintf(tempBuf, TEXT("Toolbar pos i = %d ==> idCommand = %d, iImage = %d, pszText = %s"), i, tbbi.idCommand, tbbi.iImage, tbbi.pszText);
|
|
|
|
MessageBox(NULL, tempBuf, TEXT("My Test Message"), MB_OK);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // DEBUG
|
|
|
|
void CTrayNotify::_TickleForTooltip(CNotificationItem *pni)
|
|
{
|
|
if (pni->pszIconText == NULL || *pni->pszIconText == 0)
|
|
{
|
|
//
|
|
// item hasn't set tooltip yet, tickle it by sending
|
|
// mouse-moved notification
|
|
//
|
|
CTrayItem *pti = m_TrayItemManager.GetItemDataByIndex(
|
|
m_TrayItemManager.FindItemAssociatedWithHwndUid(pni->hWnd, pni->uID));
|
|
if (pti)
|
|
{
|
|
_SendNotify(pti, WM_MOUSEMOVE);
|
|
}
|
|
}
|
|
}
|
|
|
|
HRESULT CTrayNotify::RegisterCallback(INotificationCB* pNotifyCB)
|
|
{
|
|
if (!_fNoTrayItemsDisplayPolicyEnabled)
|
|
{
|
|
ATOMICRELEASE(_pNotifyCB);
|
|
if (pNotifyCB)
|
|
{
|
|
pNotifyCB->AddRef();
|
|
|
|
// Add Current Items
|
|
int i = 0;
|
|
BOOL bStat = FALSE;
|
|
do
|
|
{
|
|
CNotificationItem ni;
|
|
if (m_TrayItemManager.GetTrayItem(i++, &ni, &bStat))
|
|
{
|
|
if (bStat)
|
|
{
|
|
pNotifyCB->Notify(NIM_ADD, &ni);
|
|
_TickleForTooltip(&ni);
|
|
}
|
|
}
|
|
else
|
|
break;
|
|
} while (TRUE);
|
|
|
|
// Add Past Items
|
|
i = 0;
|
|
bStat = FALSE;
|
|
do
|
|
{
|
|
CNotificationItem ni;
|
|
if (m_TrayItemRegistry.GetTrayItem(i++, &ni, &bStat))
|
|
{
|
|
if (bStat)
|
|
pNotifyCB->Notify(NIM_ADD, &ni);
|
|
}
|
|
else
|
|
break;
|
|
} while (TRUE);
|
|
}
|
|
|
|
_pNotifyCB = pNotifyCB;
|
|
}
|
|
else
|
|
{
|
|
_pNotifyCB = NULL;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CTrayNotify::SetPreference(LPNOTIFYITEM pNotifyItem)
|
|
{
|
|
// This function should NEVER be called if the NoTrayItemsDisplayPolicy is enabled...
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
ASSERT(!GetIsNoAutoTrayPolicyEnabled());
|
|
|
|
ASSERT( pNotifyItem->dwUserPref == TNUP_AUTOMATIC ||
|
|
pNotifyItem->dwUserPref == TNUP_DEMOTED ||
|
|
pNotifyItem->dwUserPref == TNUP_PROMOTED );
|
|
|
|
INT_PTR iItem = -1;
|
|
|
|
if (pNotifyItem->hWnd)
|
|
{
|
|
iItem = m_TrayItemManager.FindItemAssociatedWithHwndUid(pNotifyItem->hWnd, pNotifyItem->uID);
|
|
if (iItem != -1)
|
|
{
|
|
CTrayItem * pti = m_TrayItemManager.GetItemDataByIndex(iItem);
|
|
if (pti && pti->dwUserPref != pNotifyItem->dwUserPref)
|
|
{
|
|
pti->dwUserPref = pNotifyItem->dwUserPref;
|
|
// If the preference changes, the accumulated time must start again...
|
|
if (pti->IsStartupIcon())
|
|
pti->uNumSeconds = 0;
|
|
|
|
_PlaceItem(iItem, pti, TRAYEVENT_ONAPPLYUSERPREF);
|
|
_Size();
|
|
|
|
return S_OK;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_TrayItemRegistry.SetPastItemPreference(pNotifyItem))
|
|
return S_OK;
|
|
}
|
|
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
UINT CTrayNotify::_GetAccumulatedTime(CTrayItem * pti)
|
|
{
|
|
// The global user event timer...
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
IUserEventTimer * pUserEventTimer = _CreateTimer(TF_ICONDEMOTE_TIMER);
|
|
UINT uTimerElapsed = 0;
|
|
if (pUserEventTimer)
|
|
{
|
|
if (SUCCEEDED(pUserEventTimer->GetUserEventTimerElapsed(pti->hWnd, pti->uIconDemoteTimerID, &uTimerElapsed)))
|
|
{
|
|
uTimerElapsed /= 1000;
|
|
}
|
|
}
|
|
|
|
return uTimerElapsed;
|
|
}
|
|
|
|
void CTrayNotify::_RemoveImage(UINT uIMLIndex)
|
|
{
|
|
INT_PTR nCount;
|
|
INT_PTR i;
|
|
|
|
if (uIMLIndex != (UINT)-1)
|
|
{
|
|
ImageList_Remove(_himlIcons, uIMLIndex);
|
|
|
|
nCount = m_TrayItemManager.GetItemCount();
|
|
for (i = nCount - 1; i >= 0; i--)
|
|
{
|
|
int iImage = m_TrayItemManager.GetTBBtnImage(i);
|
|
if (iImage > (int)uIMLIndex)
|
|
m_TrayItemManager.SetTBBtnImage(i, iImage - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Returns TRUE if either the images are OK as they are or they needed
|
|
// resizing and the resize process worked. FALSE otherwise.
|
|
BOOL CTrayNotify::_CheckAndResizeImages()
|
|
{
|
|
HIMAGELIST himlOld, himlNew;
|
|
int cxSmIconNew, cySmIconNew, cxSmIconOld, cySmIconOld;
|
|
int i, cItems;
|
|
HICON hicon;
|
|
BOOL fOK = TRUE;
|
|
|
|
// if (!ptnd)
|
|
// return 0;
|
|
|
|
if (_fNoTrayItemsDisplayPolicyEnabled)
|
|
return fOK;
|
|
|
|
himlOld = _himlIcons;
|
|
|
|
// Do dimensions match current icons?
|
|
cxSmIconNew = GetSystemMetrics(SM_CXSMICON);
|
|
cySmIconNew = GetSystemMetrics(SM_CYSMICON);
|
|
ImageList_GetIconSize(himlOld, &cxSmIconOld, &cySmIconOld);
|
|
if (cxSmIconNew != cxSmIconOld || cySmIconNew != cySmIconOld)
|
|
{
|
|
// Nope, we're gonna need a new imagelist.
|
|
himlNew = ImageList_Create(cxSmIconNew, cySmIconNew, SHGetImageListFlags(_hwndToolbar), 0, 1);
|
|
if (himlNew)
|
|
{
|
|
// Copy the images over to the new image list.
|
|
cItems = ImageList_GetImageCount(himlOld);
|
|
for (i = 0; i < cItems; i++)
|
|
{
|
|
// REVIEW - there's no way to copy images to an empty
|
|
// imagelist, resizing it on the way.
|
|
hicon = ImageList_GetIcon(himlOld, i, ILD_NORMAL);
|
|
if (hicon)
|
|
{
|
|
if (ImageList_AddIcon(himlNew, hicon) == -1)
|
|
{
|
|
// Couldn't copy image so bail.
|
|
fOK = FALSE;
|
|
}
|
|
DestroyIcon(hicon);
|
|
}
|
|
else
|
|
{
|
|
fOK = FALSE;
|
|
}
|
|
|
|
// FU - bail.
|
|
if (!fOK)
|
|
break;
|
|
}
|
|
|
|
// Did everything copy over OK?
|
|
if (fOK)
|
|
{
|
|
// Yep, Set things up to use the new one.
|
|
_himlIcons = himlNew;
|
|
m_TrayItemManager.SetIconList(_himlIcons);
|
|
// Destroy the old icon cache.
|
|
ImageList_Destroy(himlOld);
|
|
SendMessage(_hwndToolbar, TB_SETIMAGELIST, 0, (LPARAM) _himlIcons);
|
|
SendMessage(_hwndToolbar, TB_AUTOSIZE, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
// Nope, stick with what we have.
|
|
ImageList_Destroy(himlNew);
|
|
}
|
|
}
|
|
}
|
|
|
|
return fOK;
|
|
}
|
|
|
|
void CTrayNotify::_ActivateTips(BOOL bActivate)
|
|
{
|
|
if (_fNoTrayItemsDisplayPolicyEnabled)
|
|
return;
|
|
|
|
if (bActivate && !_CanActivateTips())
|
|
return;
|
|
|
|
if (_hwndToolbarInfoTip)
|
|
SendMessage(_hwndToolbarInfoTip, TTM_ACTIVATE, (WPARAM)bActivate, 0);
|
|
}
|
|
|
|
// x,y in client coords
|
|
|
|
void CTrayNotify::_InfoTipMouseClick(int x, int y, BOOL bRightMouseButtonClick)
|
|
{
|
|
if (!_fNoTrayItemsDisplayPolicyEnabled && _pinfo)
|
|
{
|
|
RECT rect;
|
|
GetWindowRect(_hwndInfoTip, &rect);
|
|
// x & y are mapped to our window so map the rect to our window as well
|
|
MapWindowRect(HWND_DESKTOP, _hwndNotify, &rect); // screen -> client
|
|
|
|
POINT pt = {x, y};
|
|
if (PtInRect(&rect, pt))
|
|
{
|
|
SHAllowSetForegroundWindow(_pinfo->hWnd);
|
|
|
|
_beLastBalloonEvent = (bRightMouseButtonClick ? BALLOONEVENT_USERRIGHTCLICK : BALLOONEVENT_USERLEFTCLICK);
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, FALSE,
|
|
FALSE, (bRightMouseButtonClick ? NIN_BALLOONTIMEOUT : NIN_BALLOONUSERCLICK));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_PositionInfoTip()
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
if (_pinfo)
|
|
{
|
|
int x = 0;
|
|
int y = 0;
|
|
|
|
// if (_pinfo->hWnd == _hwndNotify && _pinfo->uID == UID_CHEVRONBUTTON)
|
|
if (_IsChevronInfoTip(_pinfo->hWnd, _pinfo->uID))
|
|
{
|
|
RECT rc;
|
|
GetWindowRect(_hwndChevron, &rc);
|
|
x = rc.left;
|
|
y = rc.top;
|
|
}
|
|
else
|
|
{
|
|
INT_PTR iIndex = m_TrayItemManager.FindItemAssociatedWithHwndUid(_pinfo->hWnd, _pinfo->uID);
|
|
if (iIndex != -1)
|
|
{
|
|
RECT rc;
|
|
if (SendMessage(_hwndToolbar, TB_GETITEMRECT, iIndex, (LPARAM)&rc))
|
|
{
|
|
MapWindowRect(_hwndToolbar, HWND_DESKTOP, &rc);
|
|
x = (rc.left + rc.right)/2;
|
|
y = (rc.top + rc.bottom)/2;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
SendMessage(_hwndInfoTip, TTM_TRACKPOSITION, 0, MAKELONG(x, y));
|
|
}
|
|
}
|
|
|
|
BOOL CTrayNotify::_IsScreenSaverRunning()
|
|
{
|
|
BOOL fRunning;
|
|
if (SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &fRunning, 0))
|
|
{
|
|
return fRunning;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
UINT CTrayNotify::_GetQueueCount()
|
|
{
|
|
return _dpaInfo ? _dpaInfo.GetPtrCount() : 0;
|
|
}
|
|
|
|
// NOTE: sligtly different versions of this exist in...
|
|
// SHPlaySound() -> shell32
|
|
// IEPlaySound() -> shdocvw/browseui
|
|
|
|
STDAPI_(void) ExplorerPlaySound(LPCTSTR pszSound)
|
|
{
|
|
// note, we have access only to global system sounds here as we use "Apps\.Default"
|
|
TCHAR szKey[256];
|
|
wnsprintf(szKey, ARRAYSIZE(szKey), TEXT("AppEvents\\Schemes\\Apps\\.Default\\%s\\.current"), pszSound);
|
|
|
|
TCHAR szFileName[MAX_PATH];
|
|
szFileName[0] = 0;
|
|
LONG cbSize = sizeof(szFileName);
|
|
|
|
// test for an empty string, PlaySound will play the Default Sound if we
|
|
// give it a sound it cannot find...
|
|
|
|
if ((RegQueryValue(HKEY_CURRENT_USER, szKey, szFileName, &cbSize) == ERROR_SUCCESS)
|
|
&& szFileName[0])
|
|
{
|
|
// flags are relevant, we try to not stomp currently playing sounds
|
|
PlaySound(szFileName, NULL, SND_FILENAME | SND_ASYNC | SND_NODEFAULT | SND_NOSTOP);
|
|
}
|
|
}
|
|
|
|
DWORD CTrayNotify::_ShowBalloonTip(LPTSTR szTitle, DWORD dwInfoFlags, UINT uTimeout, DWORD dwLastSoundTime)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
DWORD dwCurrentSoundTime = dwLastSoundTime;
|
|
|
|
SendMessage(_hwndInfoTip, TTM_SETTITLE, dwInfoFlags & NIIF_ICON_MASK, (LPARAM)szTitle);
|
|
if (!(dwInfoFlags & NIIF_NOSOUND))
|
|
{
|
|
// make sure at least 5 seconds pass between sounds, avoid annoying balloons
|
|
if ((GetTickCount() - dwLastSoundTime) >= 5000)
|
|
{
|
|
dwCurrentSoundTime = GetTickCount();
|
|
ExplorerPlaySound(TEXT("SystemNotification"));
|
|
}
|
|
}
|
|
|
|
_PositionInfoTip();
|
|
|
|
// if tray is in auto hide mode unhide it
|
|
c_tray.Unhide();
|
|
c_tray._fBalloonUp = TRUE;
|
|
|
|
TOOLINFO ti = {0};
|
|
ti.cbSize = sizeof(ti);
|
|
ti.hwnd = _hwndNotify;
|
|
ti.uId = (INT_PTR)_hwndNotify;
|
|
ti.lpszText = _pinfo->szInfo;
|
|
|
|
SendMessage(_hwndInfoTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
|
|
|
|
// disable regular tooltips
|
|
_fInfoTipShowing = TRUE;
|
|
_ActivateTips(FALSE);
|
|
|
|
// show the balloon
|
|
SendMessage(_hwndInfoTip, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&ti);
|
|
|
|
_SetTimer(TF_INFOTIP_TIMER, TNM_INFOTIPTIMER, uTimeout, &_uInfoTipTimer);
|
|
|
|
return dwCurrentSoundTime;
|
|
}
|
|
|
|
void CTrayNotify::_HideBalloonTip()
|
|
{
|
|
_litsLastInfoTip = LITS_BALLOONDESTROYED;
|
|
|
|
SendMessage(_hwndInfoTip, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)0);
|
|
|
|
TOOLINFO ti = {0};
|
|
ti.cbSize = sizeof(ti);
|
|
ti.hwnd = _hwndNotify;
|
|
ti.uId = (INT_PTR)_hwndNotify;
|
|
ti.lpszText = NULL;
|
|
|
|
SendMessage(_hwndInfoTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
|
|
}
|
|
|
|
void CTrayNotify::_DisableCurrentInfoTip(CTrayItem * ptiTemp, UINT uReason, BOOL bBalloonShowing)
|
|
{
|
|
_KillTimer(TF_INFOTIP_TIMER, _uInfoTipTimer);
|
|
_uInfoTipTimer = 0;
|
|
|
|
if (ptiTemp)
|
|
{
|
|
if (!uReason)
|
|
{
|
|
uReason = NIN_BALLOONTIMEOUT;
|
|
}
|
|
_SendNotify(ptiTemp, uReason);
|
|
}
|
|
|
|
delete _pinfo;
|
|
_pinfo = NULL;
|
|
|
|
if (bBalloonShowing)
|
|
_HideBalloonTip();
|
|
}
|
|
|
|
void CTrayNotify::_EmptyInfoTipQueue()
|
|
{
|
|
delete _pinfo;
|
|
_pinfo = NULL;
|
|
_dpaInfo.EnumCallback(DeleteDPAPtrCB, NULL);
|
|
_dpaInfo.DeleteAllPtrs();
|
|
}
|
|
|
|
BOOL CTrayNotify::_CanShowBalloon()
|
|
{
|
|
if (!_bStartMenuAllowsTrayBalloon || _bWaitingBetweenBalloons || _bWorkStationLocked
|
|
|| _bRudeAppLaunched || IsDirectXAppRunningFullScreen() || _bWaitAfterRudeAppHide)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CTrayNotify::_ShowInfoTip(HWND hwnd, UINT uID, BOOL bShow, BOOL bAsync, UINT uReason)
|
|
{
|
|
if (_fNoTrayItemsDisplayPolicyEnabled)
|
|
return;
|
|
|
|
// make sure we only show/hide what we intended to show/hide
|
|
if (_pinfo && _pinfo->hWnd == hwnd && _pinfo->uID == uID)
|
|
{
|
|
CTrayItem * pti = NULL;
|
|
INT_PTR nIcon = ( _IsChevronInfoTip(hwnd, uID) ?
|
|
-1 :
|
|
m_TrayItemManager.FindItemAssociatedWithHwndUid(hwnd, uID) );
|
|
if (nIcon != -1)
|
|
pti = m_TrayItemManager.GetItemDataByIndex(nIcon);
|
|
|
|
BOOL bNotify = TRUE;
|
|
|
|
if (bShow && pti)
|
|
{
|
|
if (!_fEnableUserTrackedInfoTips || pti->dwUserPref == TNUP_DEMOTED)
|
|
{
|
|
_SendNotify(pti, NIN_BALLOONSHOW);
|
|
_SendNotify(pti, NIN_BALLOONTIMEOUT);
|
|
}
|
|
}
|
|
|
|
// if ( (!(hwnd == _hwndNotify && uID == UID_CHEVRONBUTTON)) &&
|
|
if ( !_IsChevronInfoTip(hwnd, uID) &&
|
|
( !pti || pti->IsHidden()
|
|
|| pti->dwUserPref == TNUP_DEMOTED
|
|
|| !_fEnableUserTrackedInfoTips
|
|
)
|
|
)
|
|
{
|
|
// icon is hidden, cannot show its balloon
|
|
bNotify = !bShow;
|
|
bShow = FALSE; //show the next balloon instead
|
|
}
|
|
|
|
if (bShow)
|
|
{
|
|
if (bAsync)
|
|
{
|
|
PostMessage(_hwndNotify, TNM_ASYNCINFOTIP, (WPARAM)hwnd, (LPARAM)uID);
|
|
}
|
|
else if (_CanShowBalloon())
|
|
{
|
|
DWORD dwLastSoundTime = 0;
|
|
|
|
if ((nIcon != -1) && pti)
|
|
{
|
|
_PlaceItem(nIcon, pti, TRAYEVENT_ONINFOTIP);
|
|
dwLastSoundTime = pti->dwLastSoundTime;
|
|
}
|
|
|
|
dwLastSoundTime = _ShowBalloonTip(_pinfo->szTitle, _pinfo->dwInfoFlags, _pinfo->uTimeout, dwLastSoundTime);
|
|
|
|
if ((nIcon != -1) && pti)
|
|
{
|
|
pti->dwLastSoundTime = dwLastSoundTime;
|
|
_SendNotify(pti, NIN_BALLOONSHOW);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_IsChevronInfoTip(hwnd, uID))
|
|
{
|
|
// If the user clicked on the chevron info tip, we dont want to show the
|
|
// chevron any more, otherwise we want to show it once more the next session
|
|
// for a maximum of 5 sessions...
|
|
m_TrayItemRegistry.IncChevronInfoTipShownInRegistry(uReason == NIN_BALLOONUSERCLICK);
|
|
}
|
|
|
|
_DisableCurrentInfoTip(pti, uReason, TRUE);
|
|
|
|
_bWaitingBetweenBalloons = TRUE;
|
|
|
|
c_tray._fBalloonUp = FALSE;
|
|
_fInfoTipShowing = FALSE;
|
|
_ActivateTips(TRUE);
|
|
|
|
// we are hiding the current balloon. are there any waiting? yes, then show the first one
|
|
if (_GetQueueCount())
|
|
{
|
|
_pinfo = _dpaInfo.DeletePtr(0);
|
|
SetTimer(_hwndNotify, TID_BALLOONPOPWAIT, _GetBalloonWaitInterval(_beLastBalloonEvent), NULL);
|
|
}
|
|
else
|
|
{
|
|
_bWaitingBetweenBalloons = FALSE;
|
|
UpdateWindow(_hwndToolbar);
|
|
}
|
|
}
|
|
}
|
|
else if (_pinfo && !bShow)
|
|
{
|
|
// we wanted to hide something that wasn't showing up
|
|
// maybe it's in the queue
|
|
|
|
// Remove only the first info tip from this (hwnd, uID)
|
|
_RemoveInfoTipFromQueue(hwnd, uID, TRUE);
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_SetInfoTip(HWND hWnd, UINT uID, LPTSTR pszInfo, LPTSTR pszInfoTitle,
|
|
DWORD dwInfoFlags, UINT uTimeout, BOOL bAsync)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
// show the new one...
|
|
if (pszInfo[0])
|
|
{
|
|
TNINFOITEM *pii = new TNINFOITEM;
|
|
if (pii)
|
|
{
|
|
pii->hWnd = hWnd;
|
|
pii->uID = uID;
|
|
lstrcpyn(pii->szInfo, pszInfo, ARRAYSIZE(pii->szInfo));
|
|
lstrcpyn(pii->szTitle, pszInfoTitle, ARRAYSIZE(pii->szTitle));
|
|
pii->uTimeout = uTimeout;
|
|
if (pii->uTimeout < MIN_INFO_TIME)
|
|
pii->uTimeout = MIN_INFO_TIME;
|
|
else if (pii->uTimeout > MAX_INFO_TIME)
|
|
pii->uTimeout = MAX_INFO_TIME;
|
|
pii->dwInfoFlags = dwInfoFlags;
|
|
|
|
// if _pinfo is non NULL then we have a balloon showing right now
|
|
if (_pinfo || _GetQueueCount())
|
|
{
|
|
// if this is a different icon making the change request
|
|
// we might have to queue this up
|
|
if (hWnd != _pinfo->hWnd || uID != _pinfo->uID)
|
|
{
|
|
// if the current balloon has not been up for the minimum
|
|
// show delay or there are other items in the queue
|
|
// add this to the queue
|
|
if (!_dpaInfo || _dpaInfo.AppendPtr(pii) == -1)
|
|
{
|
|
delete pii;
|
|
}
|
|
return;
|
|
}
|
|
|
|
CTrayItem * ptiTemp = NULL;
|
|
INT_PTR nIcon = m_TrayItemManager.FindItemAssociatedWithHwndUid(_pinfo->hWnd, _pinfo->uID);
|
|
if (nIcon != -1)
|
|
ptiTemp = m_TrayItemManager.GetItemDataByIndex(nIcon);
|
|
|
|
_DisableCurrentInfoTip(ptiTemp, NIN_BALLOONTIMEOUT, FALSE);
|
|
}
|
|
|
|
_pinfo = pii; // in with the new
|
|
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, TRUE, bAsync, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// empty text means get rid of the balloon
|
|
_beLastBalloonEvent = BALLOONEVENT_BALLOONHIDE;
|
|
_ShowInfoTip(hWnd, uID, FALSE, FALSE, NIN_BALLOONHIDE);
|
|
}
|
|
}
|
|
|
|
DWORD CTrayNotify::_GetBalloonWaitInterval(BALLOONEVENT be)
|
|
{
|
|
switch (be)
|
|
{
|
|
case BALLOONEVENT_USERLEFTCLICK:
|
|
return BALLOON_INTERVAL_MAX;
|
|
|
|
case BALLOONEVENT_TIMEOUT:
|
|
case BALLOONEVENT_NONE:
|
|
case BALLOONEVENT_APPDEMOTE:
|
|
case BALLOONEVENT_BALLOONHIDE:
|
|
return BALLOON_INTERVAL_MEDIUM;
|
|
|
|
case BALLOONEVENT_USERRIGHTCLICK:
|
|
case BALLOONEVENT_USERXCLICK:
|
|
default:
|
|
return BALLOON_INTERVAL_MIN;
|
|
}
|
|
}
|
|
|
|
BOOL CTrayNotify::_ModifyNotify(PNOTIFYICONDATA32 pnid, INT_PTR nIcon, BOOL *pbRefresh, BOOL bFirstTime)
|
|
{
|
|
BOOL fResize = FALSE;
|
|
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
CTrayItem * pti = m_TrayItemManager.GetItemDataByIndex(nIcon);
|
|
if (!pti)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
_CheckAndResizeImages();
|
|
|
|
if (pnid->uFlags & NIF_STATE)
|
|
{
|
|
#define NIS_VALIDMASK (NIS_HIDDEN | NIS_SHAREDICON)
|
|
DWORD dwOldState = pti->dwState;
|
|
|
|
// validate mask
|
|
if (pnid->dwStateMask & ~NIS_VALIDMASK)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pti->dwState = (pnid->dwState & pnid->dwStateMask) | (pti->dwState & ~pnid->dwStateMask);
|
|
|
|
if (pnid->dwStateMask & NIS_HIDDEN)
|
|
{
|
|
if (pti->IsHidden())
|
|
{
|
|
m_TrayItemManager.SetTBBtnStateHelper(nIcon, TBSTATE_ENABLED, FALSE);
|
|
_PlaceItem(nIcon, pti, TRAYEVENT_ONICONHIDE);
|
|
}
|
|
else
|
|
{
|
|
// When the icon is inserted the first time, this function is called..
|
|
// If the icon ended the previous session in the secondary tray, then it would
|
|
// start this session in the secondary tray, in which case, the icon should not
|
|
// be enabled...
|
|
if (!bFirstTime)
|
|
{
|
|
m_TrayItemManager.SetTBBtnStateHelper(nIcon, TBSTATE_ENABLED, TRUE);
|
|
_PlaceItem(nIcon, pti, TRAYEVENT_ONICONUNHIDE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((pnid->dwState ^ dwOldState) & NIS_SHAREDICON)
|
|
{
|
|
if (dwOldState & NIS_SHAREDICON)
|
|
{
|
|
// if we're going from shared to not shared,
|
|
// clear the icon
|
|
m_TrayItemManager.SetTBBtnImage(nIcon, -1);
|
|
pti->hIcon = NULL;
|
|
}
|
|
}
|
|
fResize |= ((pnid->dwState ^ dwOldState) & NIS_HIDDEN);
|
|
}
|
|
|
|
if (pnid->uFlags & NIF_GUID)
|
|
{
|
|
memcpy(&(pti->guidItem), &(pnid->guidItem), sizeof(pnid->guidItem));
|
|
}
|
|
|
|
// The icon is the only thing that can fail, so I will do it first
|
|
if (pnid->uFlags & NIF_ICON)
|
|
{
|
|
int iImageNew, iImageOld;
|
|
HICON hIcon1 = NULL, hIcon2 = NULL;
|
|
BOOL bIsEqualIcon = FALSE;
|
|
|
|
iImageOld = m_TrayItemManager.GetTBBtnImage(nIcon);
|
|
|
|
if (!bFirstTime)
|
|
{
|
|
if (iImageOld != -1)
|
|
{
|
|
if (_himlIcons)
|
|
{
|
|
hIcon1 = ImageList_GetIcon(_himlIcons, iImageOld, ILD_NORMAL);
|
|
}
|
|
|
|
if (hIcon1)
|
|
{
|
|
hIcon2 = GetHIcon(pnid);
|
|
}
|
|
}
|
|
|
|
if (iImageOld != -1 && hIcon1 && hIcon2)
|
|
{
|
|
bIsEqualIcon = SHAreIconsEqual(hIcon1, hIcon2);
|
|
}
|
|
|
|
if (hIcon1)
|
|
DestroyIcon(hIcon1);
|
|
}
|
|
|
|
if (pti->IsIconShared())
|
|
{
|
|
iImageNew = m_TrayItemManager.FindImageIndex(GetHIcon(pnid), TRUE);
|
|
if (iImageNew == -1)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GetHIcon(pnid))
|
|
{
|
|
// Replace icon knows how to handle -1 for add
|
|
iImageNew = ImageList_ReplaceIcon(_himlIcons, iImageOld, GetHIcon(pnid));
|
|
if (iImageNew < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_RemoveImage(iImageOld);
|
|
iImageNew = -1;
|
|
}
|
|
|
|
if (pti->IsSharedIconSource())
|
|
{
|
|
INT_PTR iCount = m_TrayItemManager.GetItemCount();
|
|
// if we're the source of shared icons, we need to go update all the other icons that
|
|
// are using our icon
|
|
for (INT_PTR i = 0; i < iCount; i++)
|
|
{
|
|
if (m_TrayItemManager.GetTBBtnImage(i) == iImageOld)
|
|
{
|
|
CTrayItem * ptiTemp = m_TrayItemManager.GetItemDataByIndex(i);
|
|
ptiTemp->hIcon = GetHIcon(pnid);
|
|
m_TrayItemManager.SetTBBtnImage(i, iImageNew);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (iImageOld == -1 || iImageNew == -1)
|
|
fResize = TRUE;
|
|
}
|
|
pti->hIcon = GetHIcon(pnid);
|
|
m_TrayItemManager.SetTBBtnImage(nIcon, iImageNew);
|
|
|
|
// Dont count HICON_MODIFies the first time...
|
|
if (!pti->IsHidden() && !bFirstTime)
|
|
{
|
|
pti->SetItemSameIconModify(bIsEqualIcon);
|
|
_PlaceItem(nIcon, pti, TRAYEVENT_ONICONMODIFY);
|
|
}
|
|
}
|
|
|
|
if (pnid->uFlags & NIF_MESSAGE)
|
|
{
|
|
pti->uCallbackMessage = pnid->uCallbackMessage;
|
|
}
|
|
|
|
if (pnid->uFlags & NIF_TIP)
|
|
{
|
|
m_TrayItemManager.SetTBBtnText(nIcon, pnid->szTip);
|
|
StrCpyN(pti->szIconText, pnid->szTip, ARRAYSIZE(pnid->szTip));
|
|
}
|
|
|
|
if (fResize)
|
|
_OnSizeChanged(FALSE);
|
|
|
|
// need to have info stuff done after resize because we need to
|
|
// position the infotip relative to the hwndToolbar
|
|
if (pnid->uFlags & NIF_INFO)
|
|
{
|
|
// if button is hidden we don't show infotip
|
|
if (!pti->IsHidden())
|
|
{
|
|
_SetInfoTip(pti->hWnd, pti->uID, pnid->szInfo, pnid->szInfoTitle, pnid->dwInfoFlags,
|
|
pnid->uTimeout, (bFirstTime || fResize));
|
|
}
|
|
}
|
|
|
|
if (!bFirstTime)
|
|
_NotifyCallback(NIM_MODIFY, nIcon, -1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CTrayNotify::_SetVersionNotify(PNOTIFYICONDATA32 pnid, INT_PTR nIcon)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
CTrayItem * pti = m_TrayItemManager.GetItemDataByIndex(nIcon);
|
|
if (!pti)
|
|
return FALSE;
|
|
|
|
if (pnid->uVersion < NOTIFYICON_VERSION)
|
|
{
|
|
pti->uVersion = 0;
|
|
return TRUE;
|
|
}
|
|
else if (pnid->uVersion == NOTIFYICON_VERSION)
|
|
{
|
|
pti->uVersion = NOTIFYICON_VERSION;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_NotifyCallback(DWORD dwMessage, INT_PTR nCurrentItem, INT_PTR nPastItem)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
if (_pNotifyCB)
|
|
{
|
|
CNotificationItem * pni = new CNotificationItem;
|
|
if (pni)
|
|
{
|
|
BOOL bStat = FALSE;
|
|
if (nCurrentItem != -1 && m_TrayItemManager.GetTrayItem(nCurrentItem, pni, &bStat) && bStat)
|
|
{
|
|
PostMessage(_hwndNotify, TNM_NOTIFY, dwMessage, (LPARAM)pni);
|
|
}
|
|
else if (nCurrentItem == -1 && nPastItem != -1)
|
|
{
|
|
bStat = FALSE;
|
|
m_TrayItemRegistry.GetTrayItem(nPastItem, pni, &bStat);
|
|
|
|
if (bStat)
|
|
PostMessage(_hwndNotify, TNM_NOTIFY, dwMessage, (LPARAM)pni);
|
|
else
|
|
delete pni;
|
|
}
|
|
else
|
|
delete pni;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_RemoveInfoTipFromQueue(HWND hWnd, UINT uID, BOOL bRemoveFirstOnly /* Default = FALSE */)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
int cItems = _GetQueueCount();
|
|
|
|
for (int i = 0; _dpaInfo && (i < cItems); i++)
|
|
{
|
|
TNINFOITEM * pii = _dpaInfo.GetPtr(i);
|
|
|
|
if (pii->hWnd == hWnd && pii->uID == uID)
|
|
{
|
|
_dpaInfo.DeletePtr(i); // this removes the element from the dpa
|
|
delete pii;
|
|
|
|
if (bRemoveFirstOnly)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
i = i-1;
|
|
cItems = _GetQueueCount();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL CTrayNotify::_DeleteNotify(INT_PTR nIcon, BOOL bShutdown, BOOL bShouldSaveIcon)
|
|
{
|
|
BOOL bRet = FALSE;
|
|
|
|
if (_fNoTrayItemsDisplayPolicyEnabled)
|
|
return bRet;
|
|
|
|
_NotifyCallback(NIM_DELETE, nIcon, -1);
|
|
CTrayItem *pti = m_TrayItemManager.GetItemDataByIndex(nIcon);
|
|
if (pti)
|
|
{
|
|
_RemoveInfoTipFromQueue(pti->hWnd, pti->uID);
|
|
|
|
// delete info tip if showing
|
|
if (_pinfo && _pinfo->hWnd == pti->hWnd && _pinfo->uID == pti->uID)
|
|
{
|
|
// frees pinfo and shows the next balloon if any
|
|
_beLastBalloonEvent = BALLOONEVENT_BALLOONHIDE;
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, FALSE, TRUE, NIN_BALLOONHIDE);
|
|
}
|
|
|
|
// Save the icon info only if needed...
|
|
if (bShouldSaveIcon && pti->ShouldSaveIcon())
|
|
{
|
|
if (pti->IsStartupIcon() && !pti->IsDemoted() && pti->dwUserPref == TNUP_AUTOMATIC)
|
|
pti->uNumSeconds = _GetAccumulatedTime(pti);
|
|
|
|
// On Delete, add the icon to the past icons list, and the item to the past items list
|
|
HICON hIcon = NULL;
|
|
if (_himlIcons)
|
|
{
|
|
hIcon = ImageList_GetIcon(_himlIcons, m_TrayItemManager.GetTBBtnImage(nIcon), ILD_NORMAL);
|
|
}
|
|
|
|
int nPastSessionIndex = m_TrayItemRegistry.DoesIconExistFromPreviousSession(pti, pti->szIconText, hIcon);
|
|
|
|
if (nPastSessionIndex != -1)
|
|
{
|
|
_NotifyCallback(NIM_DELETE, -1, nPastSessionIndex);
|
|
m_TrayItemRegistry.DeletePastItem(nPastSessionIndex);
|
|
}
|
|
|
|
if (m_TrayItemRegistry.AddToPastItems(pti, hIcon))
|
|
_NotifyCallback(NIM_ADD, -1, 0);
|
|
|
|
if (hIcon)
|
|
DestroyIcon(hIcon);
|
|
}
|
|
|
|
_KillItemTimer(pti);
|
|
|
|
bRet = (BOOL) SendMessage(_hwndToolbar, TB_DELETEBUTTON, nIcon, 0);
|
|
|
|
if (!bShutdown)
|
|
{
|
|
_UpdateChevronState(_fBangMenuOpen, FALSE, TRUE);
|
|
_OnSizeChanged(FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_ERROR, "Removing nIcon %x - pti is NULL", nIcon);
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
BOOL CTrayNotify::_InsertNotify(PNOTIFYICONDATA32 pnid)
|
|
{
|
|
TBBUTTON tbb;
|
|
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
// First insert a totally "default" icon
|
|
CTrayItem * pti = new CTrayItem;
|
|
if (!pti)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pti->hWnd = GetHWnd(pnid);
|
|
pti->uID = pnid->uID;
|
|
if (_bStartupIcon)
|
|
pti->SetStartupIcon(TRUE);
|
|
|
|
if (!SUCCEEDED(SHExeNameFromHWND(pti->hWnd, pti->szExeName, ARRAYSIZE(pti->szExeName))))
|
|
{
|
|
pti->szExeName[0] = '\0';
|
|
}
|
|
|
|
INT_PTR nPastSessionIndex = m_TrayItemRegistry.CheckAndRestorePersistentIconSettings( pti,
|
|
((pnid->uFlags & NIF_TIP) ? pnid->szTip : NULL),
|
|
((pnid->uFlags & NIF_ICON) ? GetHIcon(pnid) : NULL) );
|
|
|
|
tbb.dwData = (DWORD_PTR)pti;
|
|
tbb.iBitmap = -1;
|
|
tbb.idCommand = Toolbar_GetUniqueID(_hwndToolbar);
|
|
tbb.fsStyle = BTNS_BUTTON;
|
|
tbb.fsState = TBSTATE_ENABLED;
|
|
|
|
// The "Show Always" flag should be special-cased...
|
|
// If the item didnt exist before, and is added for the first time
|
|
if (nPastSessionIndex == -1)
|
|
{
|
|
if (pnid->dwStateMask & NIS_SHOWALWAYS)
|
|
{
|
|
// Make sure that only the explorer process is setting this mask...
|
|
if ( pti->szExeName && _szExplorerExeName &&
|
|
lstrcmpi(pti->szExeName, _szExplorerExeName) )
|
|
{
|
|
pti->dwUserPref = TNUP_PROMOTED;
|
|
}
|
|
}
|
|
}
|
|
pnid->dwStateMask &= ~NIS_SHOWALWAYS;
|
|
|
|
// If one of the icons had been placed in the secondary tray in the previous session...
|
|
if (pti->IsDemoted() && m_TrayItemRegistry.IsAutoTrayEnabled())
|
|
{
|
|
tbb.fsState |= TBSTATE_HIDDEN;
|
|
}
|
|
|
|
tbb.iString = -1;
|
|
|
|
BOOL fRet = TRUE;
|
|
|
|
BOOL fRedraw = _SetRedraw(FALSE);
|
|
|
|
// Insert at the zeroth position (from the beginning)
|
|
INT_PTR iInsertPos = 0;
|
|
if (SendMessage(_hwndToolbar, TB_INSERTBUTTON, iInsertPos, (LPARAM)&tbb))
|
|
{
|
|
// Then modify this icon with the specified info
|
|
if (!_ModifyNotify(pnid, iInsertPos, NULL, TRUE))
|
|
{
|
|
_DeleteNotify(iInsertPos, FALSE, FALSE);
|
|
fRet = FALSE;
|
|
}
|
|
// BUG : 404477, Re-entrancy case where traynotify gets a TBN_DELETINGBUTTON
|
|
// when processing a TB_INSERTBUTTON. In this re-entrant scenario, pti is
|
|
// invalid after the TB_INSERTBUTTON above, even though it was created fine
|
|
// before the TB_INSERTBUTTON.
|
|
// Hence check for pti...
|
|
else if (!pti)
|
|
{
|
|
fRet = FALSE;
|
|
}
|
|
else
|
|
{
|
|
// The item has been successfully added to the tray, and the user's
|
|
// settings have been honored. So it can be deleted from the Past Items
|
|
// list and the Past Items bucket...
|
|
if (nPastSessionIndex != -1)
|
|
{
|
|
_NotifyCallback(NIM_DELETE, -1, nPastSessionIndex);
|
|
m_TrayItemRegistry.DeletePastItem(nPastSessionIndex);
|
|
}
|
|
|
|
if (!_PlaceItem(0, pti, TRAYEVENT_ONNEWITEMINSERT))
|
|
{
|
|
_UpdateChevronState(_fBangMenuOpen, FALSE, TRUE);
|
|
_OnSizeChanged(FALSE);
|
|
}
|
|
// _hwndToolbar might not be large enough to hold new icon
|
|
_Size();
|
|
}
|
|
}
|
|
|
|
_SetRedraw(fRedraw);
|
|
|
|
if (fRet)
|
|
_NotifyCallback(NIM_ADD, iInsertPos, -1);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
|
|
// set the mouse cursor to the center of the button.
|
|
// do this becaus our tray notifies don't have enough data slots to
|
|
// pass through info about the button's position.
|
|
void CTrayNotify::_SetCursorPos(INT_PTR i)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
RECT rc;
|
|
if (SendMessage(_hwndToolbar, TB_GETITEMRECT, i, (LPARAM)&rc))
|
|
{
|
|
MapWindowPoints(_hwndToolbar, HWND_DESKTOP, (LPPOINT)&rc, 2);
|
|
SetCursorPos((rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2);
|
|
}
|
|
}
|
|
|
|
LRESULT CTrayNotify::_SendNotify(CTrayItem * pti, UINT uMsg)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
if (pti->uCallbackMessage && pti->hWnd)
|
|
return SendNotifyMessage(pti->hWnd, pti->uCallbackMessage, pti->uID, uMsg);
|
|
return 0;
|
|
}
|
|
|
|
void CTrayNotify::_SetToolbarHotItem(HWND hWndToolbar, UINT nToolbarIcon)
|
|
{
|
|
if (!_fNoTrayItemsDisplayPolicyEnabled && hWndToolbar && nToolbarIcon != -1)
|
|
{
|
|
SetFocus(hWndToolbar);
|
|
InvalidateRect(hWndToolbar, NULL, TRUE);
|
|
SendMessage(hWndToolbar, TB_SETHOTITEM, nToolbarIcon, 0);
|
|
}
|
|
}
|
|
|
|
LRESULT CALLBACK CTrayNotify::ChevronSubClassWndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
|
|
LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
CTrayNotify * pTrayNotify = reinterpret_cast<CTrayNotify*>(dwRefData);
|
|
AssertMsg((pTrayNotify != NULL), TEXT("pTrayNotify SHOULD NOT be NULL, as passed to ChevronSubClassWndProc."));
|
|
|
|
if (pTrayNotify->_fNoTrayItemsDisplayPolicyEnabled)
|
|
{
|
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
static BOOL bBangMenuOpenLastTime = pTrayNotify->_fBangMenuOpen;
|
|
|
|
switch(uMsg)
|
|
{
|
|
case WM_KEYDOWN:
|
|
{
|
|
BOOL fLastHot = FALSE;
|
|
switch(wParam)
|
|
{
|
|
case VK_UP:
|
|
case VK_LEFT:
|
|
fLastHot = TRUE;
|
|
// Fall through...
|
|
|
|
case VK_DOWN:
|
|
case VK_RIGHT:
|
|
{
|
|
INT_PTR nToolbarIconSelected = pTrayNotify->_GetToolbarFirstVisibleItem(
|
|
pTrayNotify->_hwndToolbar, fLastHot);
|
|
|
|
pTrayNotify->_fChevronSelected = FALSE;
|
|
if (!fLastHot)
|
|
{
|
|
if (nToolbarIconSelected != -1)
|
|
// The toolbar has been selected
|
|
{
|
|
pTrayNotify->_SetToolbarHotItem(pTrayNotify->_hwndToolbar, nToolbarIconSelected);
|
|
}
|
|
else if (pTrayNotify->_hwndClock)
|
|
{
|
|
SetFocus(pTrayNotify->_hwndClock);
|
|
}
|
|
else
|
|
// No visible items on the tray, no clock, so nothing happens
|
|
{
|
|
pTrayNotify->_fChevronSelected = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pTrayNotify->_hwndClock)
|
|
{
|
|
SetFocus(pTrayNotify->_hwndClock);
|
|
}
|
|
else if (nToolbarIconSelected != -1)
|
|
{
|
|
pTrayNotify->_SetToolbarHotItem(pTrayNotify->_hwndToolbar, nToolbarIconSelected);
|
|
}
|
|
else
|
|
{
|
|
pTrayNotify->_fChevronSelected = TRUE;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case VK_RETURN:
|
|
case VK_SPACE:
|
|
pTrayNotify->_ToggleDemotedMenu();
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_SETFOCUS:
|
|
case WM_KILLFOCUS:
|
|
{
|
|
if (pTrayNotify->_hTheme)
|
|
{
|
|
pTrayNotify->_fHasFocus = (uMsg == WM_SETFOCUS);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (InRange(uMsg, WM_MOUSEFIRST, WM_MOUSELAST))
|
|
{
|
|
if (bBangMenuOpenLastTime != pTrayNotify->_fBangMenuOpen)
|
|
{
|
|
TOOLINFO ti = {0};
|
|
ti.cbSize = sizeof(ti);
|
|
ti.hwnd = pTrayNotify->_hwndNotify;
|
|
ti.uId = (UINT_PTR)pTrayNotify->_hwndChevron;
|
|
ti.lpszText = (LPTSTR)MAKEINTRESOURCE(!pTrayNotify->_fBangMenuOpen ? IDS_SHOWDEMOTEDTIP : IDS_HIDEDEMOTEDTIP);
|
|
ti.hinst = hinstCabinet;
|
|
|
|
SendMessage(pTrayNotify->_hwndChevronToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM)(LPTOOLINFO)&ti);
|
|
bBangMenuOpenLastTime = pTrayNotify->_fBangMenuOpen;
|
|
}
|
|
|
|
MSG msg = {0};
|
|
msg.lParam = lParam;
|
|
msg.wParam = wParam;
|
|
msg.message = uMsg;
|
|
msg.hwnd = hwnd;
|
|
|
|
SendMessage(pTrayNotify->_hwndChevronToolTip, TTM_RELAYEVENT, 0, (LPARAM)(LPMSG)&msg);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
LRESULT CALLBACK CTrayNotify::s_ToolbarWndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
|
|
LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
BOOL fClickDown = FALSE;
|
|
|
|
CTrayNotify * pTrayNotify = reinterpret_cast<CTrayNotify*>(dwRefData);
|
|
AssertMsg((pTrayNotify != NULL), TEXT("pTrayNotify SHOULD NOT be NULL, as passed to s_ToolbarWndProc."));
|
|
|
|
if (pTrayNotify->_fNoTrayItemsDisplayPolicyEnabled)
|
|
{
|
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_KEYDOWN:
|
|
pTrayNotify->_fReturn = (wParam == VK_RETURN);
|
|
break;
|
|
|
|
case WM_CONTEXTMENU:
|
|
{
|
|
INT_PTR i = SendMessage(pTrayNotify->_hwndToolbar, TB_GETHOTITEM, 0, 0);
|
|
if (i != -1)
|
|
{
|
|
CTrayItem * pti = pTrayNotify->m_TrayItemManager.GetItemDataByIndex(i);
|
|
if (lParam == (LPARAM)-1)
|
|
pTrayNotify->_fKey = TRUE;
|
|
|
|
if (pTrayNotify->_fKey)
|
|
{
|
|
pTrayNotify->_SetCursorPos(i);
|
|
}
|
|
|
|
if (pTrayNotify->_hwndToolbarInfoTip)
|
|
SendMessage(pTrayNotify->_hwndToolbarInfoTip, TTM_POP, 0, 0);
|
|
|
|
if (pti)
|
|
{
|
|
SHAllowSetForegroundWindow(pti->hWnd);
|
|
if (pti->uVersion >= KEYBOARD_VERSION)
|
|
{
|
|
pTrayNotify->_SendNotify(pti, WM_CONTEXTMENU);
|
|
}
|
|
else
|
|
{
|
|
if (pTrayNotify->_fKey)
|
|
{
|
|
pTrayNotify->_SendNotify(pti, WM_RBUTTONDOWN);
|
|
pTrayNotify->_SendNotify(pti, WM_RBUTTONUP);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (InRange(uMsg, WM_MOUSEFIRST, WM_MOUSELAST))
|
|
{
|
|
pTrayNotify->_OnMouseEvent(uMsg, wParam, lParam);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
void CTrayNotify::_ToggleTrayItems(BOOL bEnable)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
for (INT_PTR i = m_TrayItemManager.GetItemCount()-1; i >= 0; i--)
|
|
{
|
|
CTrayItem *pti = m_TrayItemManager.GetItemDataByIndex(i);
|
|
|
|
if (pti)
|
|
{
|
|
if (bEnable)
|
|
{
|
|
_PlaceItem(i, pti, TRAYEVENT_ONAPPLYUSERPREF);
|
|
}
|
|
else // if (disable)
|
|
{
|
|
_PlaceItem(i, pti, TRAYEVENT_ONDISABLEAUTOTRAY);
|
|
if (_pinfo && _IsChevronInfoTip(_pinfo->hWnd, _pinfo->uID))
|
|
{
|
|
//hide the balloon
|
|
_beLastBalloonEvent = BALLOONEVENT_NONE;
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, FALSE, FALSE, NIN_BALLOONHIDE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
HRESULT CTrayNotify::EnableAutoTray(BOOL bTraySetting)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
// This function will NEVER be called if the auto tray is disabled by policy,
|
|
// or if the system tray is made invisible by policy...
|
|
ASSERT(m_TrayItemRegistry.IsNoAutoTrayPolicyEnabled() == FALSE);
|
|
|
|
if (bTraySetting != m_TrayItemRegistry.IsAutoTrayEnabledByUser())
|
|
{
|
|
// NOTENOTE : Always assign this value BEFORE calling _ToggleTrayItems, since the timers
|
|
// are started ONLY if auto tray is enabled..
|
|
m_TrayItemRegistry.SetIsAutoTrayEnabledInRegistry(bTraySetting);
|
|
|
|
// Update the duration that the icon was present in the tray
|
|
_SetUsedTime();
|
|
|
|
_ToggleTrayItems(bTraySetting);
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
void CTrayNotify::_ShowChevronInfoTip()
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
if (m_TrayItemRegistry.ShouldChevronInfoTipBeShown() && !SHRestricted(REST_NOSMBALLOONTIP))
|
|
{
|
|
TCHAR szInfoTitle[64];
|
|
LoadString(hinstCabinet, IDS_BANGICONINFOTITLE, szInfoTitle, ARRAYSIZE(szInfoTitle));
|
|
|
|
TCHAR szInfoTip[256];
|
|
LoadString(hinstCabinet, IDS_BANGICONINFOTIP1, szInfoTip, ARRAYSIZE(szInfoTip));
|
|
|
|
_SetInfoTip(_hwndNotify, UID_CHEVRONBUTTON, szInfoTip, szInfoTitle,
|
|
TT_CHEVRON_INFOTIP_INTERVAL, NIIF_INFO, FALSE);
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_SetUsedTime()
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
for (INT_PTR i = m_TrayItemManager.GetItemCount()-1; i >= 0; i--)
|
|
{
|
|
CTrayItem * pti = m_TrayItemManager.GetItemDataByIndex(i);
|
|
ASSERT(pti);
|
|
if (pti->IsStartupIcon())
|
|
{
|
|
pti->uNumSeconds = (pti->IsIconTimerCurrent() ? _GetAccumulatedTime(pti)
|
|
: pti->uNumSeconds);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL CTrayNotify::GetTrayItemCB(INT_PTR nIndex, void *pCallbackData, TRAYCBARG trayCallbackArg,
|
|
TRAYCBRET * pOutData)
|
|
{
|
|
ASSERT(pOutData);
|
|
|
|
if (pCallbackData)
|
|
{
|
|
CTrayNotify * pTrayNotify = (CTrayNotify *) pCallbackData;
|
|
|
|
ASSERT(!pTrayNotify->_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
if ( (nIndex < 0) || (nIndex >= pTrayNotify->m_TrayItemManager.GetItemCount()) )
|
|
return FALSE;
|
|
|
|
switch(trayCallbackArg)
|
|
{
|
|
case TRAYCBARG_ALL:
|
|
case TRAYCBARG_PTI:
|
|
{
|
|
CTrayItem * pti = pTrayNotify->m_TrayItemManager.GetItemDataByIndex(nIndex);
|
|
|
|
ASSERT(pti);
|
|
if (pti->IsStartupIcon())
|
|
pti->uNumSeconds = (pti->IsIconTimerCurrent() ? pTrayNotify->_GetAccumulatedTime(pti) : pti->uNumSeconds);
|
|
|
|
pOutData->pti = pti;
|
|
}
|
|
if (trayCallbackArg == TRAYCBARG_PTI)
|
|
{
|
|
return TRUE;
|
|
}
|
|
//
|
|
// else fall through..
|
|
//
|
|
|
|
case TRAYCBARG_HICON:
|
|
{
|
|
int nImageIndex = pTrayNotify->m_TrayItemManager.GetTBBtnImage(nIndex);
|
|
pOutData->hIcon = NULL;
|
|
if (pTrayNotify->_himlIcons)
|
|
{
|
|
pOutData->hIcon = ImageList_GetIcon(pTrayNotify->_himlIcons, nImageIndex, ILD_NORMAL);
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
LRESULT CTrayNotify::_Create(HWND hWnd)
|
|
{
|
|
LRESULT lres = -1;
|
|
|
|
_nMaxHorz = 0x7fff;
|
|
_nMaxVert = 0x7fff;
|
|
_fAnimateMenuOpen = ShouldTaskbarAnimate();
|
|
_fRedraw = TRUE;
|
|
_bStartupIcon = TRUE;
|
|
_fInfoTipShowing = FALSE;
|
|
_fItemClicked = FALSE;
|
|
_fChevronSelected = FALSE;
|
|
_fEnableUserTrackedInfoTips = TRUE;
|
|
_fBangMenuOpen = FALSE;
|
|
|
|
_bWorkStationLocked = FALSE;
|
|
_bRudeAppLaunched = FALSE;
|
|
_bWaitAfterRudeAppHide = FALSE;
|
|
|
|
_bWaitingBetweenBalloons = FALSE;
|
|
|
|
// Assume that the start menu has been auto-popped
|
|
_bStartMenuAllowsTrayBalloon = FALSE;
|
|
_beLastBalloonEvent = BALLOONEVENT_NONE;
|
|
|
|
_litsLastInfoTip = LITS_BALLOONNONE;
|
|
|
|
_fNoTrayItemsDisplayPolicyEnabled = (SHRestricted(REST_NOTRAYITEMSDISPLAY) != 0);
|
|
|
|
_idMouseActiveIcon = -1;
|
|
|
|
_hwndNotify = hWnd;
|
|
_hwndClock = ClockCtl_Create(_hwndNotify, IDC_CLOCK, hinstCabinet);
|
|
|
|
_hwndPager = CreateWindowEx(0, WC_PAGESCROLLER, NULL,
|
|
WS_CHILD | WS_TABSTOP | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | PGS_HORZ,
|
|
0, 0, 0, 0, _hwndNotify, (HMENU) 0, 0, NULL);
|
|
_hwndToolbar = CreateWindowEx(WS_EX_TOOLWINDOW, TOOLBARCLASSNAME, NULL,
|
|
WS_VISIBLE | WS_CHILD | TBSTYLE_FLAT | TBSTYLE_TOOLTIPS | WS_CLIPCHILDREN | TBSTYLE_TRANSPARENT |
|
|
WS_CLIPSIBLINGS | CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE | TBSTYLE_WRAPABLE,
|
|
0, 0, 0, 0, _hwndPager, 0, hinstCabinet, NULL);
|
|
|
|
_hwndChevron = CreateWindowEx( 0, WC_BUTTON, NULL,
|
|
WS_VISIBLE | WS_CHILD,
|
|
0, 0, 0, 0, _hwndNotify, (HMENU)IDC_TRAYNOTIFY_CHEVRON, hinstCabinet, NULL);
|
|
|
|
if (_hwndNotify)
|
|
{
|
|
DWORD dwExStyle = 0;
|
|
if (IS_WINDOW_RTL_MIRRORED(_hwndNotify))
|
|
dwExStyle |= WS_EX_LAYOUTRTL;
|
|
|
|
_hwndChevronToolTip = CreateWindowEx( dwExStyle, TOOLTIPS_CLASS, NULL,
|
|
WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
|
|
0, 0, 0, 0, _hwndNotify, NULL, hinstCabinet, NULL);
|
|
|
|
_hwndInfoTip = CreateWindowEx( dwExStyle, TOOLTIPS_CLASS, NULL,
|
|
WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON | TTS_CLOSE,
|
|
0, 0, 0, 0, _hwndNotify, NULL, hinstCabinet, NULL);
|
|
|
|
_himlIcons = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
|
|
SHGetImageListFlags(_hwndToolbar), 0, 1);
|
|
}
|
|
|
|
// Check to see if any windows failed to create, if so bail
|
|
if (_himlIcons && _hwndNotify && _hwndClock && _hwndToolbar && _hwndPager && _hwndChevron && _hwndChevronToolTip && _hwndInfoTip)
|
|
{
|
|
// Get the explorer exe name, the complete launch path..
|
|
if (!SUCCEEDED(SHExeNameFromHWND(_hwndNotify, _szExplorerExeName, ARRAYSIZE(_szExplorerExeName))))
|
|
{
|
|
_szExplorerExeName[0] = TEXT('\0');
|
|
}
|
|
|
|
SetWindowTheme(_hwndClock, c_wzTrayNotifyTheme, NULL);
|
|
|
|
SendMessage(_hwndInfoTip, TTM_SETWINDOWTHEME, 0, (LPARAM)c_wzTrayNotifyTheme);
|
|
SendMessage(_hwndToolbar, TB_SETWINDOWTHEME, 0, (LPARAM)c_wzTrayNotifyTheme);
|
|
|
|
SetWindowPos(_hwndInfoTip, HWND_TOPMOST,
|
|
0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
|
|
TOOLINFO ti = {0};
|
|
RECT rc = {0,-2,0,0};
|
|
ti.cbSize = sizeof(ti);
|
|
ti.hwnd = _hwndNotify;
|
|
ti.uFlags = TTF_IDISHWND | TTF_TRACK | TTF_TRANSPARENT;
|
|
ti.uId = (UINT_PTR)_hwndNotify;
|
|
|
|
// set the version so we can have non buggy mouse event forwarding
|
|
SendMessage(_hwndInfoTip, CCM_SETVERSION, COMCTL32_VERSION, 0);
|
|
SendMessage(_hwndInfoTip, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti);
|
|
SendMessage(_hwndInfoTip, TTM_SETMAXTIPWIDTH, 0, (LPARAM)MAX_TIP_WIDTH);
|
|
SendMessage(_hwndInfoTip, TTM_SETMARGIN, 0, (LPARAM)&rc);
|
|
ASSERT(_dpaInfo == NULL);
|
|
_dpaInfo = DPA_Create(10);
|
|
|
|
// Tray toolbar is a child of the pager control
|
|
SendMessage(_hwndPager, PGM_SETCHILD, 0, (LPARAM)_hwndToolbar);
|
|
|
|
// Set the window title to help out accessibility apps
|
|
TCHAR szTitle[64];
|
|
LoadString(hinstCabinet, IDS_TRAYNOTIFYTITLE, szTitle, ARRAYSIZE(szTitle));
|
|
SetWindowText(_hwndToolbar, szTitle);
|
|
|
|
// Toolbar settings - customize the tray toolbar...
|
|
SendMessage(_hwndToolbar, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
|
|
SendMessage(_hwndToolbar, TB_SETPADDING, 0, MAKELONG(2, 2));
|
|
SendMessage(_hwndToolbar, TB_SETMAXTEXTROWS, 0, 0);
|
|
SendMessage(_hwndToolbar, CCM_SETVERSION, COMCTL32_VERSION, 0);
|
|
SendMessage(_hwndToolbar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_INVERTIBLEIMAGELIST | TBSTYLE_EX_DOUBLEBUFFER | TBSTYLE_EX_TOOLTIPSEXCLUDETOOLBAR);
|
|
SendMessage(_hwndToolbar, TB_SETIMAGELIST, 0, (LPARAM)_himlIcons);
|
|
|
|
_hwndToolbarInfoTip = (HWND)SendMessage(_hwndToolbar, TB_GETTOOLTIPS, 0, 0);
|
|
if (_hwndToolbarInfoTip)
|
|
{
|
|
SHSetWindowBits(_hwndToolbarInfoTip, GWL_STYLE, TTS_ALWAYSTIP, TTS_ALWAYSTIP);
|
|
SetWindowZorder(_hwndToolbarInfoTip, HWND_TOPMOST);
|
|
}
|
|
|
|
// if this fails, not that big a deal... we'll still show, but won't handle clicks
|
|
SetWindowSubclass(_hwndToolbar, s_ToolbarWndProc, 0, reinterpret_cast<DWORD_PTR>(this));
|
|
|
|
ti.cbSize = sizeof(ti);
|
|
ti.hwnd = _hwndNotify;
|
|
ti.uFlags = TTF_IDISHWND | TTF_EXCLUDETOOLAREA;
|
|
ti.uId = (UINT_PTR)_hwndChevron;
|
|
ti.lpszText = (LPTSTR)MAKEINTRESOURCE(IDS_SHOWDEMOTEDTIP);
|
|
ti.hinst = hinstCabinet;
|
|
|
|
SetWindowZorder(_hwndChevronToolTip, HWND_TOPMOST);
|
|
// Set the Chevron as the tool for the tooltip
|
|
SendMessage(_hwndChevronToolTip, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti);
|
|
|
|
// Subclass the Chevron button, so we can forward mouse messages to the tooltip
|
|
SetWindowSubclass(_hwndChevron, ChevronSubClassWndProc, 0, reinterpret_cast<DWORD_PTR>(this));
|
|
|
|
_OpenTheme();
|
|
|
|
m_TrayItemRegistry.InitRegistryValues(SHGetImageListFlags(_hwndToolbar));
|
|
|
|
m_TrayItemManager.SetTrayToolbar(_hwndToolbar);
|
|
m_TrayItemManager.SetIconList(_himlIcons);
|
|
|
|
lres = 0; // Yeah we succeeded
|
|
}
|
|
|
|
return lres;
|
|
}
|
|
|
|
|
|
LRESULT CTrayNotify::_Destroy()
|
|
{
|
|
if (!_fNoTrayItemsDisplayPolicyEnabled)
|
|
{
|
|
for (INT_PTR i = m_TrayItemManager.GetItemCount() - 1; i >= 0; i--)
|
|
{
|
|
_DeleteNotify(i, TRUE, TRUE);
|
|
}
|
|
if (_pinfo)
|
|
{
|
|
delete _pinfo;
|
|
_pinfo = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AssertMsg((_pinfo == NULL), TEXT("_pinfo is being leaked"));
|
|
AssertMsg((!_himlIcons || (ImageList_GetImageCount(_himlIcons) == 0)), TEXT("image list info being leaked"));
|
|
}
|
|
|
|
if (_dpaInfo)
|
|
{
|
|
_dpaInfo.DestroyCallback(DeleteDPAPtrCB, NULL);
|
|
}
|
|
|
|
if (_himlIcons)
|
|
{
|
|
ImageList_Destroy(_himlIcons);
|
|
_himlIcons = NULL;
|
|
}
|
|
|
|
if (_hwndClock)
|
|
{
|
|
DestroyWindow(_hwndClock);
|
|
_hwndClock = NULL;
|
|
}
|
|
|
|
if (_hwndToolbar)
|
|
{
|
|
RemoveWindowSubclass(_hwndToolbar, s_ToolbarWndProc, 0);
|
|
DestroyWindow(_hwndToolbar);
|
|
_hwndToolbar = NULL;
|
|
}
|
|
|
|
if (_hwndChevron)
|
|
{
|
|
RemoveWindowSubclass(_hwndChevron, ChevronSubClassWndProc, 0);
|
|
DestroyWindow(_hwndChevron);
|
|
_hwndChevron = NULL;
|
|
}
|
|
|
|
if (_hwndInfoTip)
|
|
{
|
|
DestroyWindow(_hwndInfoTip);
|
|
_hwndInfoTip = NULL;
|
|
}
|
|
|
|
if (_hwndPager)
|
|
{
|
|
DestroyWindow(_hwndPager);
|
|
_hwndPager = NULL;
|
|
}
|
|
|
|
if (_hTheme)
|
|
{
|
|
CloseThemeData(_hTheme);
|
|
_hTheme = NULL;
|
|
}
|
|
|
|
if (_hwndChevronToolTip)
|
|
{
|
|
DestroyWindow(_hwndChevronToolTip);
|
|
_hwndChevronToolTip = NULL;
|
|
}
|
|
|
|
if (_pszCurrentThreadDesktopName)
|
|
{
|
|
LocalFree(_pszCurrentThreadDesktopName);
|
|
}
|
|
|
|
// Takes care of clearing up registry-related data
|
|
m_TrayItemRegistry.Delete();
|
|
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CTrayNotify::_Paint(HDC hdcIn)
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hPaintDC = NULL;
|
|
HDC hMemDC = NULL;
|
|
HBITMAP hMemBm = NULL, hOldBm = NULL;
|
|
|
|
if (hdcIn)
|
|
{
|
|
hPaintDC = hdcIn;
|
|
GetClipBox(hPaintDC, &ps.rcPaint);
|
|
}
|
|
else
|
|
{
|
|
BeginPaint(_hwndNotify, &ps);
|
|
|
|
if (_fRedraw)
|
|
{
|
|
// Create memory surface and map rendering context if double buffering
|
|
// Only make large enough for clipping region
|
|
hMemDC = CreateCompatibleDC(ps.hdc);
|
|
if (hMemDC)
|
|
{
|
|
hMemBm = CreateCompatibleBitmap(ps.hdc, RECTWIDTH(ps.rcPaint), RECTHEIGHT(ps.rcPaint));
|
|
if (hMemBm)
|
|
{
|
|
hOldBm = (HBITMAP) SelectObject(hMemDC, hMemBm);
|
|
|
|
// Offset painting to paint in region
|
|
OffsetWindowOrgEx(hMemDC, ps.rcPaint.left, ps.rcPaint.top, NULL);
|
|
hPaintDC = hMemDC;
|
|
}
|
|
else
|
|
{
|
|
DeleteDC(hMemDC);
|
|
hPaintDC = NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_fRepaint = TRUE;
|
|
hPaintDC = NULL;
|
|
}
|
|
}
|
|
|
|
if (hPaintDC)
|
|
{
|
|
RECT rc;
|
|
GetClientRect(_hwndNotify, &rc);
|
|
|
|
if (_hTheme)
|
|
{
|
|
SHSendPrintRect(GetParent(_hwnd), _hwnd, hPaintDC, &ps.rcPaint);
|
|
|
|
if (_fAnimating)
|
|
{
|
|
if (_fVertical)
|
|
{
|
|
rc.top = rc.bottom - _rcAnimateCurrent.bottom;
|
|
}
|
|
else
|
|
{
|
|
rc.left = rc.right - _rcAnimateCurrent.right;
|
|
}
|
|
}
|
|
|
|
DrawThemeBackground(_hTheme, hPaintDC, TNP_BACKGROUND, 0, &rc, 0);
|
|
|
|
if (_fHasFocus)
|
|
{
|
|
LRESULT lRes = SendMessage(_hwndChevron, WM_QUERYUISTATE, 0, 0);
|
|
if (!(LOWORD(lRes) & UISF_HIDEFOCUS))
|
|
{
|
|
RECT rcFocus = {0};
|
|
GetClientRect(_hwndChevron, &rcFocus);
|
|
MapWindowRect(_hwndChevron, _hwndNotify, &rcFocus);
|
|
// InflateRect(&rcFocus, 2, 2);
|
|
DrawFocusRect(hPaintDC, &rcFocus);
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
FillRect(hPaintDC, &rc, (HBRUSH)(COLOR_3DFACE + 1));
|
|
}
|
|
}
|
|
|
|
if (!hdcIn)
|
|
{
|
|
if (hMemDC)
|
|
{
|
|
BitBlt(ps.hdc, ps.rcPaint.left, ps.rcPaint.top, RECTWIDTH(ps.rcPaint), RECTHEIGHT(ps.rcPaint), hMemDC, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY);
|
|
|
|
SelectObject(hMemDC, hOldBm);
|
|
|
|
DeleteObject(hMemBm);
|
|
DeleteDC(hMemDC);
|
|
}
|
|
|
|
EndPaint(_hwndNotify, &ps);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CTrayNotify::_HandleCustomDraw(LPNMCUSTOMDRAW pcd)
|
|
{
|
|
LRESULT lres = CDRF_DODEFAULT;
|
|
|
|
// If this policy is enabled, the chevron should NEVER be shown, and if it is not
|
|
// shown, no question about its WM_NOTIFY message handler...
|
|
// ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
if (_fNoTrayItemsDisplayPolicyEnabled)
|
|
{
|
|
return lres;
|
|
}
|
|
else if (!_hTheme)
|
|
{
|
|
switch (pcd->dwDrawStage)
|
|
{
|
|
case CDDS_PREERASE:
|
|
{
|
|
DWORD dwFlags = 0;
|
|
if (pcd->uItemState & CDIS_HOT)
|
|
{
|
|
// The chevron is under the pointer, hence the item is hot
|
|
_fChevronSelected = TRUE;
|
|
dwFlags |= DCHF_HOT;
|
|
}
|
|
if (!_fVertical)
|
|
{
|
|
dwFlags |= DCHF_HORIZONTAL;
|
|
}
|
|
if ((!_fBangMenuOpen && _fVertical) || (_fBangMenuOpen && !_fVertical))
|
|
{
|
|
dwFlags |= DCHF_FLIPPED;
|
|
}
|
|
if (pcd->uItemState & CDIS_FOCUS)
|
|
{
|
|
if (_fChevronSelected)
|
|
dwFlags |= DCHF_HOT;
|
|
}
|
|
|
|
if (!(pcd->uItemState & CDIS_FOCUS || pcd->uItemState & CDIS_HOT))
|
|
{
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
|
|
DrawChevron(pcd->hdc, &(pcd->rc), dwFlags);
|
|
lres = CDRF_SKIPDEFAULT;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return lres;
|
|
}
|
|
|
|
void CTrayNotify::_SizeWindows(int nMaxHorz, int nMaxVert, LPRECT prcTotal, BOOL fSizeWindows)
|
|
{
|
|
RECT rcClock, rcPager, rcChevron;
|
|
SIZE szNotify;
|
|
RECT rcBound = { 0, 0, nMaxHorz, nMaxVert };
|
|
RECT rcBorder = rcBound;
|
|
|
|
rcChevron.left = rcChevron.top = 0;
|
|
rcChevron.right = !_fNoTrayItemsDisplayPolicyEnabled && _fHaveDemoted ? _szChevron.cx : 0;
|
|
rcChevron.bottom = !_fNoTrayItemsDisplayPolicyEnabled && _fHaveDemoted ? _szChevron.cy : 0;
|
|
|
|
if (_hTheme)
|
|
{
|
|
GetThemeBackgroundContentRect(_hTheme, NULL, TNP_BACKGROUND, 0, &rcBound, &rcBorder);
|
|
}
|
|
else
|
|
{
|
|
rcBorder.top += g_cyBorder;
|
|
rcBorder.left += g_cxBorder;
|
|
rcBorder.bottom -= g_cyBorder + 2;
|
|
rcBorder.right -= g_cxBorder + 2;
|
|
}
|
|
|
|
static LRESULT s_lRes = 0;
|
|
static int s_nMaxVert = -1;
|
|
if (s_nMaxVert != nMaxVert)
|
|
{
|
|
s_lRes = SendMessage(_hwndClock, WM_CALCMINSIZE, nMaxHorz, nMaxVert);
|
|
}
|
|
rcClock.left = rcClock.top = 0;
|
|
rcClock.right = LOWORD(s_lRes);
|
|
rcClock.bottom = HIWORD(s_lRes);
|
|
|
|
szNotify.cx = RECTWIDTH(rcBorder);
|
|
szNotify.cy = RECTHEIGHT(rcBorder);
|
|
SendMessage(_hwndToolbar, TB_GETIDEALSIZE, _fVertical, (LPARAM)&szNotify);
|
|
|
|
if (_fVertical)
|
|
{
|
|
int cxButtonSize = LOWORD(SendMessage(_hwndToolbar, TB_GETBUTTONSIZE, 0, 0));
|
|
szNotify.cx -= szNotify.cx % (cxButtonSize ? cxButtonSize : 16);
|
|
|
|
// Vertical Taskbar, place the clock on the bottom and icons on the top
|
|
rcChevron.left = rcClock.left = rcBorder.left;
|
|
rcChevron.right = rcClock.right = rcBorder.right;
|
|
rcPager.left = (RECTWIDTH(rcBorder) - szNotify.cx) / 2 + rcBorder.left;
|
|
rcPager.right = rcPager.left + szNotify.cx;
|
|
if (_hTheme)
|
|
{
|
|
rcChevron.left = (nMaxHorz - _szChevron.cx) / 2;
|
|
rcChevron.right = rcChevron.left + _szChevron.cx;
|
|
}
|
|
prcTotal->left = 0;
|
|
prcTotal->right = nMaxHorz;
|
|
|
|
// If Notification Icons take up more space than available then just set them to the maximum available size
|
|
int cyTemp = max(rcChevron.bottom, rcBorder.top);
|
|
int cyTotal = cyTemp + rcClock.bottom + (nMaxVert - rcBorder.bottom);
|
|
rcPager.top = 0;
|
|
rcPager.bottom = min(szNotify.cy, nMaxVert - cyTemp);
|
|
|
|
OffsetRect(&rcPager, 0, cyTemp);
|
|
OffsetRect(&rcClock, 0, rcPager.bottom);
|
|
|
|
prcTotal->top = 0;
|
|
prcTotal->bottom = rcClock.bottom + (nMaxVert - rcBorder.bottom);
|
|
}
|
|
else
|
|
{
|
|
int cyButtonSize = HIWORD(SendMessage(_hwndToolbar, TB_GETBUTTONSIZE, 0, 0));
|
|
szNotify.cy -= szNotify.cy % (cyButtonSize ? cyButtonSize : 16);
|
|
|
|
// Horizontal Taskbar, place the clock on the right and icons on the left
|
|
rcChevron.top = rcClock.top = rcBorder.top;
|
|
rcChevron.bottom = rcClock.bottom = rcBorder.bottom;
|
|
rcPager.top = ((RECTHEIGHT(rcBorder) - szNotify.cy) / 2) + rcBorder.top;
|
|
rcPager.bottom = rcPager.top + szNotify.cy;
|
|
if (_hTheme)
|
|
{
|
|
rcChevron.top = ((RECTHEIGHT(rcBorder) - _szChevron.cy) / 2) + rcBorder.top;
|
|
rcChevron.bottom = rcChevron.top + _szChevron.cy;
|
|
}
|
|
prcTotal->top = 0;
|
|
prcTotal->bottom = nMaxVert;
|
|
|
|
// If Notification Icons take up more space than available then just set them to the maximum available size
|
|
int cxTemp = max(rcChevron.right, rcBorder.left);
|
|
int cxTotal = cxTemp + rcClock.right + (nMaxHorz - rcBorder.right);
|
|
rcPager.left = 0;
|
|
rcPager.right = min(szNotify.cx, nMaxHorz - cxTemp);
|
|
|
|
OffsetRect(&rcPager, cxTemp, 0);
|
|
OffsetRect(&rcClock, rcPager.right, 0);
|
|
|
|
prcTotal->left = 0;
|
|
prcTotal->right = rcClock.right + (nMaxHorz - rcBorder.right);
|
|
}
|
|
|
|
if (fSizeWindows)
|
|
{
|
|
if (_fAnimating)
|
|
{
|
|
RECT rcWin;
|
|
GetWindowRect(_hwndNotify, &rcWin);
|
|
int offsetX = _fVertical ? 0: RECTWIDTH(rcWin) - RECTWIDTH(*prcTotal);
|
|
int offsetY = _fVertical ? RECTHEIGHT(rcWin) - RECTHEIGHT(*prcTotal) : 0;
|
|
OffsetRect(&rcClock, offsetX, offsetY);
|
|
OffsetRect(&rcPager, offsetX, offsetY);
|
|
OffsetRect(&rcChevron, offsetX, offsetY);
|
|
}
|
|
|
|
SetWindowPos(_hwndClock, NULL, rcClock.left, rcClock.top, RECTWIDTH(rcClock), RECTHEIGHT(rcClock), SWP_NOZORDER);
|
|
SetWindowPos(_hwndToolbar, NULL, 0, 0, szNotify.cx, szNotify.cy, SWP_NOZORDER | SWP_NOCOPYBITS);
|
|
SetWindowPos(_hwndPager, NULL, rcPager.left, rcPager.top, RECTWIDTH(rcPager), RECTHEIGHT(rcPager), SWP_NOZORDER | SWP_NOCOPYBITS);
|
|
SetWindowPos(_hwndChevron, NULL, rcChevron.left, rcChevron.top, RECTWIDTH(rcChevron), RECTHEIGHT(rcChevron), SWP_NOZORDER | SWP_NOCOPYBITS);
|
|
|
|
SendMessage(_hwndPager, PGMP_RECALCSIZE, (WPARAM) 0, (LPARAM) 0);
|
|
}
|
|
|
|
if (_fAnimating)
|
|
{
|
|
_rcAnimateCurrent = *prcTotal;
|
|
*prcTotal = _rcAnimateTotal;
|
|
}
|
|
|
|
if (fSizeWindows)
|
|
{
|
|
RECT rcInvalid = *prcTotal;
|
|
if (_fVertical)
|
|
{
|
|
rcInvalid.bottom = rcPager.bottom;
|
|
}
|
|
else
|
|
{
|
|
rcInvalid.right = rcPager.right;
|
|
}
|
|
InvalidateRect(_hwndNotify, &rcInvalid, FALSE);
|
|
UpdateWindow(_hwndNotify);
|
|
}
|
|
}
|
|
|
|
LRESULT CTrayNotify::_CalcMinSize(int nMaxHorz, int nMaxVert)
|
|
{
|
|
RECT rcTotal;
|
|
|
|
_nMaxHorz = nMaxHorz;
|
|
_nMaxVert = nMaxVert;
|
|
|
|
if (!(GetWindowLong(_hwndClock, GWL_STYLE) & WS_VISIBLE) && !m_TrayItemManager.GetItemCount())
|
|
{
|
|
// If we are visible, but have nothing to show, then hide ourselves
|
|
ShowWindow(_hwndNotify, SW_HIDE);
|
|
return 0L;
|
|
}
|
|
else if (!IsWindowVisible(_hwndNotify))
|
|
{
|
|
ShowWindow(_hwndNotify, SW_SHOW);
|
|
}
|
|
|
|
_SizeWindows(nMaxHorz, nMaxVert, &rcTotal, FALSE);
|
|
|
|
// Add on room for borders
|
|
return(MAKELRESULT(rcTotal.right, rcTotal.bottom));
|
|
}
|
|
|
|
LRESULT CTrayNotify::_Size()
|
|
{
|
|
RECT rcTotal;
|
|
// use GetWindowRect because _SizeWindows includes the borders
|
|
GetWindowRect(_hwndNotify, &rcTotal);
|
|
// Account for borders on the left and right
|
|
_SizeWindows(RECTWIDTH(rcTotal), RECTHEIGHT(rcTotal), &rcTotal, TRUE);
|
|
|
|
return(0);
|
|
}
|
|
|
|
void CTrayNotify::_OnInfoTipTimer()
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
_KillTimer(TF_INFOTIP_TIMER, _uInfoTipTimer);
|
|
_uInfoTipTimer = 0;
|
|
if (_pinfo)
|
|
{
|
|
_beLastBalloonEvent = BALLOONEVENT_TIMEOUT;
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, FALSE, FALSE, NIN_BALLOONTIMEOUT); // hide this balloon and show new one
|
|
}
|
|
}
|
|
|
|
LRESULT CTrayNotify::_OnTimer(UINT_PTR uTimerID)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
if (uTimerID == TID_DEMOTEDMENU)
|
|
{
|
|
if (_fBangMenuOpen)
|
|
_ToggleDemotedMenu();
|
|
}
|
|
else if (uTimerID == TID_BALLOONPOP)
|
|
{
|
|
// When the user clicks the 'X' to close a balloon tip, this timer is set.
|
|
// Ensure that the currently showing balloon tip (the one on which the user
|
|
// clicked the 'X') is completely hidden, before showing the next balloon in
|
|
// the queue.
|
|
//
|
|
// Tooltips are layered windows, and comctl32 implements a fadeout effect on
|
|
// them. So there is a time period during which a tooltip is still visible
|
|
// after it has been asked to be deleted/hidden.
|
|
if (IsWindowVisible(_hwndInfoTip))
|
|
{
|
|
SetTimer(_hwndNotify, TID_BALLOONPOP, TT_BALLOONPOP_INTERVAL_INCREMENT, NULL);
|
|
}
|
|
else
|
|
{
|
|
KillTimer(_hwndNotify, TID_BALLOONPOP);
|
|
if (_pinfo)
|
|
{
|
|
_beLastBalloonEvent = BALLOONEVENT_USERXCLICK;
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, FALSE, FALSE, NIN_BALLOONTIMEOUT);
|
|
}
|
|
// This is called only when the user has clicked the 'X'.
|
|
_litsLastInfoTip = LITS_BALLOONXCLICKED;
|
|
}
|
|
}
|
|
else if (uTimerID == TID_BALLOONPOPWAIT)
|
|
{
|
|
KillTimer(_hwndNotify, TID_BALLOONPOPWAIT);
|
|
_bWaitingBetweenBalloons = FALSE;
|
|
if (_pinfo)
|
|
{
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, TRUE, TRUE, 0);
|
|
}
|
|
}
|
|
else if (uTimerID == TID_BALLOONSHOW)
|
|
{
|
|
KillTimer(_hwndNotify, TID_BALLOONSHOW);
|
|
|
|
_bStartMenuAllowsTrayBalloon = TRUE;
|
|
if (_pinfo)
|
|
{
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, TRUE, TRUE, NIN_BALLOONSHOW);
|
|
}
|
|
}
|
|
else if (uTimerID == TID_RUDEAPPHIDE)
|
|
{
|
|
KillTimer(_hwndNotify, TID_RUDEAPPHIDE);
|
|
|
|
if (_pinfo && _bWaitAfterRudeAppHide)
|
|
{
|
|
_bWaitAfterRudeAppHide = FALSE;
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, TRUE, TRUE, NIN_BALLOONSHOW);
|
|
}
|
|
|
|
_bWaitAfterRudeAppHide = FALSE;
|
|
}
|
|
else
|
|
{
|
|
AssertMsg(FALSE, TEXT("CTrayNotify::_OnTimer() not possible"));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BOOL _IsClickDown(UINT uMsg)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_MBUTTONDBLCLK:
|
|
case WM_RBUTTONDBLCLK:
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL _UseCachedIcon(UINT uMsg)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
case WM_MOUSEMOVE:
|
|
case WM_MOUSEWHEEL:
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CTrayNotify::_OnMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
// Icons can jump around between the time we get a down-click message and
|
|
// the time we get a double-click or up-click message. E.g. clicking on
|
|
// the bang icon expands the hidden stuff, or an icon might delete itself
|
|
// in response to the down-click.
|
|
//
|
|
// It's undesirable for a different icon to get the corresponding double-
|
|
// or up-click in this case (very annoying to the user).
|
|
//
|
|
// To deal with this, cache the icon down-clicked and use that cached value
|
|
// (instead of the button the mouse is currently over) on double- or up-click.
|
|
|
|
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
// The mouse cursor has moved over the toolbar, so if the chevron was selected
|
|
// earlier, it should not be anymore.
|
|
_fChevronSelected = FALSE;
|
|
|
|
BOOL fClickDown = _IsClickDown(uMsg);
|
|
BOOL fUseCachedIcon = _UseCachedIcon(uMsg);
|
|
|
|
INT_PTR i = -1;
|
|
|
|
if (fUseCachedIcon)
|
|
{
|
|
i = ToolBar_CommandToIndex(_hwndToolbar, _idMouseActiveIcon);
|
|
}
|
|
|
|
if (i == -1)
|
|
{
|
|
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
i = SendMessage(_hwndToolbar, TB_HITTEST, 0, (LPARAM)&pt);
|
|
if (fClickDown)
|
|
_idMouseActiveIcon = ToolBar_IndexToCommand(_hwndToolbar, i);
|
|
}
|
|
|
|
CTrayItem *pti = m_TrayItemManager.GetItemDataByIndex(i);
|
|
if (pti)
|
|
{
|
|
if (IsWindow(pti->hWnd))
|
|
{
|
|
if (fClickDown)
|
|
{
|
|
SHAllowSetForegroundWindow(pti->hWnd);
|
|
|
|
if (_pinfo && _pinfo->hWnd == pti->hWnd && _pinfo->uID == pti->uID)
|
|
{
|
|
if (uMsg == WM_RBUTTONDOWN || uMsg == WM_RBUTTONDBLCLK)
|
|
_beLastBalloonEvent = BALLOONEVENT_USERRIGHTCLICK;
|
|
else
|
|
_beLastBalloonEvent = BALLOONEVENT_USERLEFTCLICK;
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, FALSE, FALSE, NIN_BALLOONUSERCLICK);
|
|
}
|
|
|
|
if (fClickDown)
|
|
{
|
|
// down clicks count as activation
|
|
_PlaceItem(i, pti, TRAYEVENT_ONITEMCLICK);
|
|
}
|
|
|
|
_fItemClicked = TRUE;
|
|
_ActivateTips(FALSE);
|
|
}
|
|
|
|
_SendNotify(pti, uMsg);
|
|
}
|
|
else
|
|
{
|
|
_DeleteNotify(i, FALSE, TRUE);
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CTrayNotify::_OnCDNotify(LPNMTBCUSTOMDRAW pnm)
|
|
{
|
|
// if (_fNoTrayItemsDisplayPolicyEnabled)
|
|
// return CDRF_DODEFAULT;
|
|
|
|
switch (pnm->nmcd.dwDrawStage)
|
|
{
|
|
case CDDS_PREPAINT:
|
|
return CDRF_NOTIFYITEMDRAW;
|
|
|
|
case CDDS_ITEMPREPAINT:
|
|
{
|
|
LRESULT lRet = TBCDRF_NOOFFSET;
|
|
|
|
// notify us for the hot tracked item please
|
|
if (pnm->nmcd.uItemState & CDIS_HOT)
|
|
lRet |= CDRF_NOTIFYPOSTPAINT;
|
|
|
|
// we want the buttons to look totally flat all the time
|
|
pnm->nmcd.uItemState = 0;
|
|
|
|
return lRet;
|
|
}
|
|
|
|
case CDDS_ITEMPOSTPAINT:
|
|
{
|
|
// draw the hot tracked item as a focus rect, since
|
|
// the tray notify area doesn't behave like a button:
|
|
// you can SINGLE click or DOUBLE click or RCLICK
|
|
// (kybd equiv: SPACE, ENTER, SHIFT F10)
|
|
//
|
|
LRESULT lRes = SendMessage(_hwndNotify, WM_QUERYUISTATE, 0, 0);
|
|
if (!(LOWORD(lRes) & UISF_HIDEFOCUS) && _hwndToolbar == GetFocus())
|
|
{
|
|
DrawFocusRect(pnm->nmcd.hdc, &pnm->nmcd.rc);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return CDRF_DODEFAULT;
|
|
}
|
|
|
|
LRESULT CTrayNotify::_Notify(LPNMHDR pNmhdr)
|
|
{
|
|
LRESULT lRes = 0;
|
|
switch (pNmhdr->code)
|
|
{
|
|
case TTN_POP: // a balloontip/tooltip is about to be hidden...
|
|
if (pNmhdr->hwndFrom == _hwndInfoTip)
|
|
{
|
|
// If this infotip was hidden by means other than the user click on the
|
|
// 'X', the code path sets _litsLastInfoTip to LITS_BALLOONDESTROYED
|
|
// before the infotip is to be hidden...
|
|
//
|
|
// If _litsLastInfoTip is not set to LITS_BALLOONDESTROYED, the infotip
|
|
// was deleted by the user click on the 'X' (that being the only other
|
|
// way to close the infotip). comctl32 sends us a TTN_POP *before* it
|
|
// hides the infotip. Don't set the next infotip to show immediately.
|
|
// (The hiding code would then hide the infotip for the next tool, since the
|
|
// hwnds are the same). Set a timer in this case, and show the
|
|
// next infotip, after ensuring that the current one is truly hidden...
|
|
if ( (_litsLastInfoTip == LITS_BALLOONXCLICKED) ||
|
|
(_litsLastInfoTip == LITS_BALLOONNONE) )
|
|
{
|
|
_KillTimer(TF_INFOTIP_TIMER, _uInfoTipTimer);
|
|
SetTimer(_hwndNotify, TID_BALLOONPOP, TT_BALLOONPOP_INTERVAL, NULL);
|
|
}
|
|
_litsLastInfoTip = LITS_BALLOONXCLICKED;
|
|
}
|
|
break;
|
|
|
|
case NM_KEYDOWN:
|
|
_fKey = TRUE;
|
|
break;
|
|
|
|
case TBN_ENDDRAG:
|
|
_fKey = FALSE;
|
|
break;
|
|
|
|
case TBN_DELETINGBUTTON:
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
TBNOTIFY* ptbn = (TBNOTIFY*)pNmhdr;
|
|
CTrayItem *pti = (CTrayItem *)(void *)ptbn->tbButton.dwData;
|
|
// can be null if its a blank button used for the animation
|
|
if (pti)
|
|
{
|
|
//if it wasn't sharing an icon with another guy, go ahead and delete it
|
|
if (!pti->IsIconShared())
|
|
_RemoveImage(ptbn->tbButton.iBitmap);
|
|
|
|
delete pti;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case BCN_HOTITEMCHANGE:
|
|
case TBN_HOTITEMCHANGE:
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
DWORD dwFlags = (pNmhdr->code == BCN_HOTITEMCHANGE) ? ((LPNMBCHOTITEM)pNmhdr)->dwFlags : ((LPNMTBHOTITEM)pNmhdr)->dwFlags;
|
|
|
|
if (dwFlags & HICF_LEAVING)
|
|
{
|
|
_fItemClicked = FALSE;
|
|
_ActivateTips(TRUE);
|
|
}
|
|
|
|
if (_fBangMenuOpen)
|
|
{
|
|
if (dwFlags & HICF_LEAVING)
|
|
{
|
|
//
|
|
// When hottracking moves between button and toolbar,
|
|
// we get the HICF_ENTERING for one before we get the
|
|
// HICF_LEAVING for the other. So before setting the
|
|
// timer to hide the bang menu, we check to see if the
|
|
// other control has a hot item.
|
|
//
|
|
BOOL fOtherHot;
|
|
if (pNmhdr->code == BCN_HOTITEMCHANGE)
|
|
{
|
|
fOtherHot = (SendMessage(_hwndToolbar, TB_GETHOTITEM, 0, 0) != -1);
|
|
}
|
|
else
|
|
{
|
|
fOtherHot = BOOLIFY(SendMessage(_hwndChevron, BM_GETSTATE, 0, 0) & BST_HOT);
|
|
}
|
|
|
|
if (!fOtherHot)
|
|
{
|
|
SetTimer(_hwndNotify, TID_DEMOTEDMENU, TT_DEMOTEDMENU_INTERVAL, NULL);
|
|
}
|
|
}
|
|
else if (dwFlags & HICF_ENTERING)
|
|
{
|
|
KillTimer(_hwndNotify, TID_DEMOTEDMENU);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TBN_WRAPHOTITEM:
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
NMTBWRAPHOTITEM * pnmWrapHotItem = (NMTBWRAPHOTITEM *) pNmhdr;
|
|
|
|
// If the user hit a key on the tray toolbar icon and it was the first
|
|
// visible item in the tray toolbar, then maybe we want to go to the
|
|
// chevron button...
|
|
switch (pnmWrapHotItem->iDir)
|
|
{
|
|
// Left/Up
|
|
case -1:
|
|
if (_fHaveDemoted)
|
|
{
|
|
SetFocus(_hwndChevron);
|
|
_fChevronSelected = TRUE;
|
|
}
|
|
else if (_hwndClock)
|
|
{
|
|
SetFocus(_hwndClock);
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
else
|
|
// do nothing
|
|
{
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
break;
|
|
|
|
// Right/Down
|
|
case 1:
|
|
if (_hwndClock)
|
|
{
|
|
SetFocus(_hwndClock);
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
else if (_fHaveDemoted)
|
|
{
|
|
SetFocus(_hwndChevron);
|
|
_fChevronSelected = TRUE;
|
|
}
|
|
else
|
|
{
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
// NOTENOTE: This notification DOESNT need to be checked. Pager forwards its notifications
|
|
// to our child toolbar control, and TBN_HOTITEMCHANGE above handles this case..
|
|
case PGN_HOTITEMCHANGE:
|
|
{
|
|
LPNMTBHOTITEM pnmhot = (LPNMTBHOTITEM)pNmhdr;
|
|
|
|
if (pnmhot->dwFlags & HICF_LEAVING)
|
|
{
|
|
_fItemClicked = FALSE;
|
|
_ActivateTips(TRUE);
|
|
}
|
|
|
|
if (_fBangMenuOpen)
|
|
{
|
|
if (pnmhot->dwFlags & HICF_LEAVING)
|
|
{
|
|
SetTimer(_hwndNotify, TID_DEMOTEDMENU, TT_DEMOTEDMENU_INTERVAL, NULL);
|
|
}
|
|
else if (pnmhot->dwFlags & HICF_ENTERING)
|
|
{
|
|
KillTimer(_hwndNotify, TID_DEMOTEDMENU);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PGN_CALCSIZE:
|
|
{
|
|
LPNMPGCALCSIZE pCalcSize = (LPNMPGCALCSIZE)pNmhdr;
|
|
|
|
switch(pCalcSize->dwFlag)
|
|
{
|
|
case PGF_CALCWIDTH:
|
|
{
|
|
//Get the optimum WIDTH of the toolbar.
|
|
RECT rcToolBar;
|
|
GetWindowRect(_hwndToolbar, &rcToolBar);
|
|
pCalcSize->iWidth = RECTWIDTH(rcToolBar);
|
|
}
|
|
break;
|
|
|
|
case PGF_CALCHEIGHT:
|
|
{
|
|
//Get the optimum HEIGHT of the toolbar.
|
|
RECT rcToolBar;
|
|
GetWindowRect(_hwndToolbar, &rcToolBar);
|
|
pCalcSize->iHeight = RECTHEIGHT(rcToolBar);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
case NM_CUSTOMDRAW:
|
|
if (pNmhdr->hwndFrom == _hwndChevron)
|
|
{
|
|
return _HandleCustomDraw((LPNMCUSTOMDRAW)pNmhdr);
|
|
}
|
|
else
|
|
{
|
|
return _OnCDNotify((LPNMTBCUSTOMDRAW)pNmhdr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return lRes;
|
|
}
|
|
|
|
void CTrayNotify::_OnSysChange(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (uMsg == WM_WININICHANGE)
|
|
{
|
|
_CheckAndResizeImages();
|
|
if (lParam == SPI_SETMENUANIMATION || lParam == SPI_SETUIEFFECTS || (!wParam &&
|
|
(!lParam || (lstrcmpi((LPTSTR)lParam, TEXT("Windows")) == 0))))
|
|
{
|
|
_fAnimateMenuOpen = ShouldTaskbarAnimate();
|
|
}
|
|
}
|
|
|
|
if (_hwndClock)
|
|
SendMessage(_hwndClock, uMsg, wParam, lParam);
|
|
}
|
|
|
|
void CTrayNotify::_OnCommand(UINT id, UINT uCmd)
|
|
{
|
|
if (id == IDC_TRAYNOTIFY_CHEVRON)
|
|
{
|
|
AssertMsg(!_fNoTrayItemsDisplayPolicyEnabled, TEXT("Impossible-the chevron shouldnt be shown"));
|
|
switch(uCmd)
|
|
{
|
|
case BN_SETFOCUS:
|
|
break;
|
|
|
|
default:
|
|
_ToggleDemotedMenu();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (uCmd)
|
|
{
|
|
case BN_CLICKED:
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
CTrayItem *pti = m_TrayItemManager.GetItemData(id, FALSE, _hwndToolbar);
|
|
if (pti)
|
|
{
|
|
if (_fKey)
|
|
_SetCursorPos(SendMessage(_hwndToolbar, TB_COMMANDTOINDEX, id, 0));
|
|
|
|
SHAllowSetForegroundWindow(pti->hWnd);
|
|
if (pti->uVersion >= KEYBOARD_VERSION)
|
|
{
|
|
// if they are a new version that understands the keyboard messages,
|
|
// send the real message to them.
|
|
_SendNotify(pti, _fKey ? NIN_KEYSELECT : NIN_SELECT);
|
|
// Hitting RETURN is like double-clicking (which in the new
|
|
// style means keyselecting twice)
|
|
if (_fKey && _fReturn)
|
|
_SendNotify(pti, NIN_KEYSELECT);
|
|
}
|
|
else
|
|
{
|
|
// otherwise mock up a mouse event if it was a keyboard select
|
|
// (if it wasn't a keyboard select, we assume they handled it already on
|
|
// the WM_MOUSE message
|
|
if (_fKey)
|
|
{
|
|
_SendNotify(pti, WM_LBUTTONDOWN);
|
|
_SendNotify(pti, WM_LBUTTONUP);
|
|
if (_fReturn)
|
|
{
|
|
_SendNotify(pti, WM_LBUTTONDBLCLK);
|
|
_SendNotify(pti, WM_LBUTTONUP);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_OnSizeChanged(BOOL fForceRepaint)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
if (_pinfo)
|
|
{
|
|
// if balloon is up we have to move it, but we cannot straight up
|
|
// position it because traynotify will be moved around by tray
|
|
// so we do it async
|
|
|
|
PostMessage(_hwndNotify, TNM_ASYNCINFOTIPPOS, 0, 0);
|
|
}
|
|
c_tray.VerifySize(TRUE);
|
|
|
|
if (fForceRepaint)
|
|
UpdateWindow(_hwndToolbar);
|
|
}
|
|
|
|
#define TT_ANIMATIONLENGTH 20 // sum of all the animation steps
|
|
#define TT_ANIMATIONPAUSE 30 // extra pause for last step
|
|
|
|
DWORD CTrayNotify::_GetStepTime(int iStep, int cSteps)
|
|
{
|
|
// our requirements here are:
|
|
//
|
|
// - animation velocity should decrease linearly with time
|
|
//
|
|
// - total animation time should be a constant, TT_ANIMATIONLENGTH
|
|
// (it should not vary with number of icons)
|
|
//
|
|
// - figure this out without using floating point math
|
|
//
|
|
// hence the following formula
|
|
//
|
|
|
|
if (cSteps == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
else if (iStep == cSteps && cSteps > 2)
|
|
{
|
|
return TT_ANIMATIONPAUSE;
|
|
}
|
|
else
|
|
{
|
|
int iNumerator = (TT_ANIMATIONLENGTH - cSteps) * iStep;
|
|
int iDenominator = (cSteps + 1) * cSteps;
|
|
|
|
return (iNumerator / iDenominator);
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_ToggleDemotedMenu()
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
_ActivateTips(FALSE);
|
|
|
|
int iAnimStep = 1; // animation steps are 1-based
|
|
int cNumberDemoted = (int) m_TrayItemManager.GetDemotedItemCount();
|
|
|
|
if (_fAnimateMenuOpen)
|
|
{
|
|
if (!_fBangMenuOpen)
|
|
{
|
|
_BlankButtons(0, cNumberDemoted, TRUE);
|
|
}
|
|
|
|
GetWindowRect(_hwndNotify, &_rcAnimateTotal);
|
|
_SizeWindows(_fVertical ? RECTWIDTH(_rcAnimateTotal) : _nMaxHorz, _fVertical ? _nMaxVert : RECTHEIGHT(_rcAnimateTotal), &_rcAnimateTotal, FALSE);
|
|
|
|
if (!_fBangMenuOpen)
|
|
{
|
|
_BlankButtons(0, cNumberDemoted, FALSE);
|
|
}
|
|
|
|
_fAnimating = TRUE; // Begin Animation loop
|
|
|
|
if (!_fBangMenuOpen)
|
|
{
|
|
_OnSizeChanged(TRUE);
|
|
}
|
|
}
|
|
|
|
for (INT_PTR i = m_TrayItemManager.GetItemCount() - 1; i >= 0; i--)
|
|
{
|
|
CTrayItem * pti = m_TrayItemManager.GetItemDataByIndex(i);
|
|
if (!pti->IsHidden() && pti->IsDemoted())
|
|
{
|
|
DWORD dwSleep = _GetStepTime(iAnimStep, cNumberDemoted);
|
|
iAnimStep++;
|
|
|
|
if (_fBangMenuOpen)
|
|
{
|
|
m_TrayItemManager.SetTBBtnStateHelper(i, TBSTATE_HIDDEN, TRUE);
|
|
}
|
|
|
|
if (_fAnimateMenuOpen)
|
|
{
|
|
_AnimateButtons((int) i, dwSleep, cNumberDemoted, !_fBangMenuOpen);
|
|
}
|
|
|
|
if (!_fBangMenuOpen)
|
|
{
|
|
m_TrayItemManager.SetTBBtnStateHelper(i, TBSTATE_HIDDEN, FALSE);
|
|
}
|
|
|
|
if (_fAnimateMenuOpen)
|
|
{
|
|
_SizeWindows(_fVertical ? RECTWIDTH(_rcAnimateTotal) : _nMaxHorz, _fVertical ? _nMaxVert : RECTHEIGHT(_rcAnimateTotal), &_rcAnimateTotal, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
_fAnimating = FALSE; // End Animation loop
|
|
|
|
if (_fBangMenuOpen)
|
|
{
|
|
KillTimer(_hwndNotify, TID_DEMOTEDMENU);
|
|
}
|
|
|
|
_ActivateTips(TRUE);
|
|
_UpdateChevronState(!_fBangMenuOpen, FALSE, _fBangMenuOpen);
|
|
_OnSizeChanged(TRUE);
|
|
}
|
|
|
|
void CTrayNotify::_BlankButtons(int iPos, int iNumberOfButtons, BOOL fAddButtons)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
BOOL fRedraw = _SetRedraw(FALSE);
|
|
|
|
TBBUTTON tbb;
|
|
tbb.dwData = NULL;
|
|
tbb.iBitmap = -1;
|
|
tbb.fsStyle = BTNS_BUTTON;
|
|
tbb.iString = -1;
|
|
tbb.fsState = TBSTATE_INDETERMINATE;
|
|
|
|
for (int i = 0; i < iNumberOfButtons; i++)
|
|
{
|
|
if (fAddButtons)
|
|
{
|
|
tbb.idCommand = Toolbar_GetUniqueID(_hwndToolbar);
|
|
}
|
|
//insert all blank buttons at the front of the toolbar
|
|
SendMessage(_hwndToolbar, fAddButtons ? TB_INSERTBUTTON : TB_DELETEBUTTON, iPos, fAddButtons ? (LPARAM)&tbb : 0);
|
|
}
|
|
|
|
_SetRedraw(fRedraw);
|
|
}
|
|
|
|
#define TT_ANIMATIONSTEP 3
|
|
#define TT_ANIMATIONSTEPBASE 100
|
|
#define TT_ANIMATIONWRAPPAUSE 25 // pause for no animation for row wraps
|
|
|
|
void CTrayNotify::_AnimateButtons(int iIndex, DWORD dwSleep, int iNumberItems, BOOL fGrow)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
BOOL fInSameRow = TRUE;
|
|
|
|
_BlankButtons((int) iIndex, 1, TRUE);
|
|
|
|
if ((iIndex + 2 < m_TrayItemManager.GetItemCount()) && (iIndex > 0))
|
|
{
|
|
RECT rcItem1, rcItem2;
|
|
SendMessage(_hwndToolbar, TB_GETITEMRECT, fGrow ? iIndex + 2 : iIndex - 1, (LPARAM)&rcItem1);
|
|
SendMessage(_hwndToolbar, TB_GETITEMRECT, iIndex, (LPARAM)&rcItem2);
|
|
fInSameRow = (rcItem1.top == rcItem2.top);
|
|
}
|
|
|
|
if (fInSameRow)
|
|
{
|
|
// target width of button
|
|
WORD wWidth = LOWORD(SendMessage(_hwndToolbar, TB_GETBUTTONSIZE, 0, 0));
|
|
|
|
int iAnimationStep = (iNumberItems * iNumberItems) / TT_ANIMATIONSTEPBASE;
|
|
iAnimationStep = max(iAnimationStep, TT_ANIMATIONSTEP);
|
|
|
|
TBBUTTONINFO tbbi;
|
|
tbbi.cbSize = sizeof(TBBUTTONINFO);
|
|
tbbi.dwMask = TBIF_SIZE | TBIF_BYINDEX;
|
|
|
|
// Set the size of the buttons
|
|
for (WORD cx = 1; cx < wWidth; cx += (WORD) iAnimationStep)
|
|
{
|
|
tbbi.cx = fGrow ? cx : wWidth - cx;
|
|
SendMessage(_hwndToolbar, TB_SETBUTTONINFO, iIndex, (LPARAM) &tbbi);
|
|
|
|
RECT rcBogus;
|
|
_SizeWindows(_fVertical ? RECTWIDTH(_rcAnimateTotal) : _nMaxHorz, _fVertical ? _nMaxVert : RECTHEIGHT(_rcAnimateTotal), &rcBogus, TRUE);
|
|
|
|
Sleep(dwSleep);
|
|
}
|
|
|
|
if (fGrow)
|
|
{
|
|
// set the grow button back to normal size
|
|
tbbi.cx = 0;
|
|
SendMessage(_hwndToolbar, TB_SETBUTTONINFO, iIndex, (LPARAM) &tbbi);
|
|
}
|
|
}
|
|
|
|
_BlankButtons((int) iIndex, 1, FALSE);
|
|
}
|
|
|
|
BOOL CTrayNotify::_SetRedraw(BOOL fRedraw)
|
|
{
|
|
BOOL fOldRedraw = _fRedraw;
|
|
_fRedraw = fRedraw;
|
|
|
|
SendMessage(_hwndToolbar, WM_SETREDRAW, fRedraw, 0);
|
|
if (_fRedraw)
|
|
{
|
|
if (_fRepaint)
|
|
{
|
|
InvalidateRect(_hwndNotify, NULL, FALSE);
|
|
UpdateWindow(_hwndNotify);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_fRepaint = FALSE;
|
|
}
|
|
|
|
return fOldRedraw;
|
|
}
|
|
|
|
void CTrayNotify::_OnIconDemoteTimer(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
INT_PTR nIcon = m_TrayItemManager.FindItemAssociatedWithTimer(lParam);
|
|
if (nIcon >= 0)
|
|
{
|
|
CTrayItem *pti = m_TrayItemManager.GetItemDataByIndex(nIcon);
|
|
ASSERT(pti);
|
|
|
|
_PlaceItem(nIcon, pti, TRAYEVENT_ONICONDEMOTETIMER);
|
|
}
|
|
else
|
|
{
|
|
// It looks like a timer for a now-defunct icon. Go ahead and kill it.
|
|
// Though we do handle this case, it's odd for it to happen, so spew a
|
|
// warning.
|
|
TraceMsg(TF_WARNING, "CTrayNotify::_OnIconDemoteTimer -- killing zombie timer %x", lParam);
|
|
_KillTimer(TF_ICONDEMOTE_TIMER, (UINT) lParam);
|
|
}
|
|
}
|
|
|
|
BOOL CTrayNotify::_UpdateTrayItems(BOOL bUpdateDemotedItems)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
BOOL bDemoteItemsOverThreshold = ( m_TrayItemRegistry.IsAutoTrayEnabled() ?
|
|
m_TrayItemManager.DemotedItemsPresent(MIN_DEMOTED_ITEMS_THRESHOLD) :
|
|
FALSE );
|
|
|
|
if (bUpdateDemotedItems || !m_TrayItemRegistry.IsAutoTrayEnabled())
|
|
{
|
|
_HideAllDemotedItems(bDemoteItemsOverThreshold);
|
|
}
|
|
|
|
return bDemoteItemsOverThreshold;
|
|
}
|
|
|
|
void CTrayNotify::_HideAllDemotedItems(BOOL bHide)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
for (INT_PTR i = m_TrayItemManager.GetItemCount()-1; i >= 0; i--)
|
|
{
|
|
CTrayItem * pti = m_TrayItemManager.GetItemDataByIndex(i);
|
|
ASSERT(pti);
|
|
|
|
if (!pti->IsHidden() && pti->IsDemoted() && (pti->dwUserPref == TNUP_AUTOMATIC))
|
|
{
|
|
m_TrayItemManager.SetTBBtnStateHelper(i, TBSTATE_HIDDEN, bHide);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL CTrayNotify::_PlaceItem(INT_PTR nIcon, CTrayItem * pti, TRAYEVENT tTrayEvent)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
BOOL bDemoteStatusChange = FALSE;
|
|
if (!pti)
|
|
return bDemoteStatusChange;
|
|
|
|
TRAYITEMPOS tiPos = _TrayItemPos(pti, tTrayEvent, &bDemoteStatusChange);
|
|
|
|
if (bDemoteStatusChange || tiPos == TIPOS_HIDDEN)
|
|
{
|
|
if (pti->IsStartupIcon() && (pti->IsDemoted() || tiPos == TIPOS_HIDDEN))
|
|
pti->uNumSeconds = 0;
|
|
|
|
if (!_fBangMenuOpen || pti->IsHidden())
|
|
{
|
|
if ( (pti->IsDemoted() || tiPos == TIPOS_HIDDEN) &&
|
|
_pinfo && (_pinfo->hWnd == pti->hWnd) && (_pinfo->uID == pti->uID) )
|
|
{
|
|
//hide the balloon
|
|
_beLastBalloonEvent = BALLOONEVENT_APPDEMOTE;
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, FALSE, FALSE, NIN_BALLOONHIDE);
|
|
}
|
|
|
|
// hide/show
|
|
m_TrayItemManager.SetTBBtnStateHelper( nIcon,
|
|
TBSTATE_HIDDEN,
|
|
(pti->IsHidden() || (m_TrayItemRegistry.IsAutoTrayEnabled() && pti->IsDemoted())) );
|
|
|
|
if (bDemoteStatusChange)
|
|
{
|
|
_UpdateChevronState(_fBangMenuOpen, FALSE, TRUE);
|
|
_OnSizeChanged(FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
_SetOrKillIconDemoteTimer(pti, tiPos);
|
|
|
|
return bDemoteStatusChange;
|
|
}
|
|
|
|
TRAYITEMPOS CTrayNotify::_TrayItemPos(CTrayItem * pti, TRAYEVENT tTrayEvent, BOOL *bDemoteStatusChange)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
TRAYITEMPOS tiPos = TIPOS_STATUSQUO;
|
|
|
|
*bDemoteStatusChange = FALSE;
|
|
|
|
if (!pti)
|
|
return tiPos;
|
|
|
|
switch(tTrayEvent)
|
|
{
|
|
case TRAYEVENT_ONDISABLEAUTOTRAY:
|
|
if (!pti->IsHidden())
|
|
{
|
|
tiPos = TIPOS_ALWAYS_PROMOTED;
|
|
|
|
if (pti->IsStartupIcon() && !pti->IsDemoted() && pti->dwUserPref == TNUP_AUTOMATIC)
|
|
pti->uNumSeconds = _GetAccumulatedTime(pti);
|
|
|
|
*bDemoteStatusChange = TRUE;
|
|
|
|
pti->SetOnceVisible(TRUE);
|
|
pti->SetItemClicked(FALSE);
|
|
}
|
|
break;
|
|
|
|
case TRAYEVENT_ONITEMCLICK:
|
|
case TRAYEVENT_ONICONMODIFY:
|
|
case TRAYEVENT_ONINFOTIP:
|
|
if (!pti->IsHidden())
|
|
pti->SetOnceVisible(TRUE);
|
|
|
|
if (m_TrayItemRegistry.IsAutoTrayEnabled() && !pti->IsHidden())
|
|
{
|
|
if ( (tTrayEvent == TRAYEVENT_ONICONMODIFY) &&
|
|
(pti->IsItemSameIconModify()) )
|
|
{
|
|
break;
|
|
}
|
|
else if (pti->dwUserPref == TNUP_AUTOMATIC)
|
|
{
|
|
// If the item has been clicked on, note it...
|
|
if (tTrayEvent == TRAYEVENT_ONITEMCLICK)
|
|
{
|
|
pti->SetItemClicked(TRUE);
|
|
}
|
|
|
|
tiPos = TIPOS_PROMOTED;
|
|
if (pti->IsDemoted())
|
|
{
|
|
pti->SetDemoted(FALSE);
|
|
*bDemoteStatusChange = TRUE;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TRAYEVENT_ONAPPLYUSERPREF:
|
|
case TRAYEVENT_ONNEWITEMINSERT:
|
|
if (!pti->IsHidden())
|
|
pti->SetOnceVisible(TRUE);
|
|
|
|
if (m_TrayItemRegistry.IsAutoTrayEnabled() && !pti->IsHidden())
|
|
{
|
|
if (pti->dwUserPref == TNUP_AUTOMATIC)
|
|
{
|
|
tiPos = (pti->IsDemoted() ? TIPOS_DEMOTED : TIPOS_PROMOTED);
|
|
if (pti->IsDemoted())
|
|
{
|
|
// (1) New Item Insert : The new item is inserted. If it was demoted in the
|
|
// previous session, the setting has carried over, and is copied over
|
|
// before this function is called (in InsertNotify->_PlaceItem). Use this
|
|
// setting to determine if the item is to be demoted.
|
|
// (2) Apply User Pref : This is called in two cases :
|
|
// (a) SetPreference : When the user clicks OK on the Notifications Prop
|
|
// dialog. Since dwUserPref is TNUP_AUTOMATIC, use the current demoted
|
|
// setting of the item. Do not change the demoted setting of the item
|
|
// (b) EnableAutoTray : The AutoTray feature has been enabled. Demote the
|
|
// icon only if it was already demoted before. When the icon was
|
|
// inserted, its previous demote setting was copied. So if its previous
|
|
// demote setting was TRUE, then the item should be demoted, otherwise
|
|
// it shouldnt be.
|
|
// So, in effect, in all these cases, if dwUserPref was TNUP_AUTOMATIC, there
|
|
// is no necessity to change the demote setting, but some cases, it is necessary
|
|
// to hide the icon.
|
|
*bDemoteStatusChange = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pti->SetDemoted(pti->dwUserPref == TNUP_DEMOTED);
|
|
|
|
tiPos = ((pti->dwUserPref == TNUP_DEMOTED) ? TIPOS_ALWAYS_DEMOTED : TIPOS_ALWAYS_PROMOTED);
|
|
|
|
*bDemoteStatusChange = TRUE;
|
|
|
|
pti->SetItemClicked(FALSE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TRAYEVENT_ONICONDEMOTETIMER:
|
|
// Hidden items cannot have timers, and we will never get this event if
|
|
// the item was hidden...
|
|
ASSERT(!pti->IsHidden());
|
|
ASSERT(m_TrayItemRegistry.IsAutoTrayEnabled());
|
|
|
|
tiPos = TIPOS_DEMOTED;
|
|
if (!pti->IsDemoted())
|
|
{
|
|
pti->SetDemoted(TRUE);
|
|
*bDemoteStatusChange = TRUE;
|
|
}
|
|
pti->SetItemClicked(FALSE);
|
|
break;
|
|
|
|
case TRAYEVENT_ONICONHIDE:
|
|
tiPos = TIPOS_HIDDEN;
|
|
if (pti->IsDemoted() || pti->dwUserPref == TNUP_DEMOTED)
|
|
{
|
|
pti->SetDemoted(FALSE);
|
|
*bDemoteStatusChange = TRUE;
|
|
}
|
|
pti->SetItemClicked(FALSE);
|
|
break;
|
|
|
|
case TRAYEVENT_ONICONUNHIDE:
|
|
pti->SetOnceVisible(TRUE);
|
|
*bDemoteStatusChange = TRUE;
|
|
|
|
if (m_TrayItemRegistry.IsAutoTrayEnabled())
|
|
{
|
|
if ((pti->dwUserPref == TNUP_AUTOMATIC) || (pti->dwUserPref == TNUP_PROMOTED))
|
|
{
|
|
tiPos = ((pti->dwUserPref == TNUP_AUTOMATIC) ? TIPOS_PROMOTED : TIPOS_ALWAYS_PROMOTED);
|
|
if (pti->IsDemoted())
|
|
{
|
|
pti->SetDemoted(FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(pti->dwUserPref == TNUP_DEMOTED);
|
|
tiPos = TIPOS_ALWAYS_DEMOTED;
|
|
if (!pti->IsDemoted())
|
|
{
|
|
pti->SetDemoted(TRUE);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
// NO-AUTO-TRAY mode...
|
|
{
|
|
tiPos = TIPOS_ALWAYS_PROMOTED;
|
|
}
|
|
pti->SetItemClicked(FALSE);
|
|
break;
|
|
}
|
|
|
|
return tiPos;
|
|
}
|
|
|
|
|
|
void CTrayNotify::_SetOrKillIconDemoteTimer(CTrayItem * pti, TRAYITEMPOS tiPos)
|
|
{
|
|
switch(tiPos)
|
|
{
|
|
case TIPOS_PROMOTED:
|
|
_SetItemTimer(pti);
|
|
break;
|
|
|
|
case TIPOS_DEMOTED:
|
|
case TIPOS_HIDDEN:
|
|
case TIPOS_ALWAYS_DEMOTED:
|
|
case TIPOS_ALWAYS_PROMOTED:
|
|
_KillItemTimer(pti);
|
|
break;
|
|
|
|
case TIPOS_STATUSQUO:
|
|
break;
|
|
}
|
|
}
|
|
|
|
LRESULT CTrayNotify::_OnKeyDown(WPARAM wChar, LPARAM lFlags)
|
|
{
|
|
if (_hwndClock && _hwndClock == GetFocus())
|
|
{
|
|
BOOL fLastHot = FALSE;
|
|
|
|
//
|
|
// handle keyboard messages forwarded by clock
|
|
//
|
|
switch (wChar)
|
|
{
|
|
case VK_UP:
|
|
case VK_LEFT:
|
|
fLastHot = TRUE;
|
|
//
|
|
// fall through
|
|
//
|
|
|
|
case VK_DOWN:
|
|
case VK_RIGHT:
|
|
{
|
|
if (_fNoTrayItemsDisplayPolicyEnabled)
|
|
{
|
|
SetFocus(_hwndClock);
|
|
// this is moot, since the chevron will not be shown
|
|
_fChevronSelected = FALSE;
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
INT_PTR nToolbarIconSelected = -1;
|
|
if (fLastHot || !_fHaveDemoted)
|
|
{
|
|
nToolbarIconSelected = _GetToolbarFirstVisibleItem(_hwndToolbar, fLastHot);
|
|
}
|
|
|
|
if (nToolbarIconSelected != -1)
|
|
{
|
|
//
|
|
// make it the hot item
|
|
//
|
|
_SetToolbarHotItem(_hwndToolbar, nToolbarIconSelected);
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
else if (_fHaveDemoted)
|
|
{
|
|
SetFocus(_hwndChevron);
|
|
_fChevronSelected = TRUE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
case VK_RETURN:
|
|
case VK_SPACE:
|
|
//
|
|
// run the default applet in timedate.cpl
|
|
//
|
|
SHRunControlPanel(TEXT("timedate.cpl"), _hwnd);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void CTrayNotify::_OnWorkStationLocked(BOOL bLocked)
|
|
{
|
|
_bWorkStationLocked = bLocked;
|
|
|
|
if (!_bWorkStationLocked && !_fNoTrayItemsDisplayPolicyEnabled &&
|
|
_fEnableUserTrackedInfoTips && _pinfo)
|
|
{
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, TRUE, TRUE, NIN_BALLOONSHOW);
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_OnRudeApp(BOOL bRudeApp)
|
|
{
|
|
if (_bRudeAppLaunched != bRudeApp)
|
|
{
|
|
_bWaitAfterRudeAppHide = FALSE;
|
|
|
|
_bRudeAppLaunched = bRudeApp;
|
|
|
|
if (!bRudeApp)
|
|
{
|
|
if (_pinfo)
|
|
{
|
|
SetTimer(_hwndNotify, TID_RUDEAPPHIDE, TT_RUDEAPPHIDE_INTERVAL, 0);
|
|
_bWaitAfterRudeAppHide = TRUE;
|
|
|
|
// _ShowInfoTip(_pinfo->hWnd, _pinfo->uID, TRUE, TRUE, NIN_BALLOONSHOW);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_KillTimer(TF_INFOTIP_TIMER, _uInfoTipTimer);
|
|
_uInfoTipTimer = 0;
|
|
|
|
// NOTENOTE : *DO NOT* delete _pinfo, we will show the balloon tip after the fullscreen app has
|
|
// gone away.
|
|
_HideBalloonTip();
|
|
}
|
|
}
|
|
}
|
|
|
|
// WndProc as defined in CImpWndProc. s_WndProc function in base class calls
|
|
// virtual v_WndProc, which handles all the messages in the derived class.
|
|
LRESULT CTrayNotify::v_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
//
|
|
// protect against re-entrancy after we've been partially destroyed
|
|
//
|
|
if (_hwndToolbar == NULL)
|
|
{
|
|
if (uMsg != WM_CREATE &&
|
|
uMsg != WM_DESTROY)
|
|
{
|
|
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_CREATE:
|
|
return _Create(hWnd);
|
|
|
|
case WM_DESTROY:
|
|
return _Destroy();
|
|
|
|
case WM_COMMAND:
|
|
if (!_fNoTrayItemsDisplayPolicyEnabled)
|
|
_OnCommand(GET_WM_COMMAND_ID(wParam, lParam), GET_WM_COMMAND_CMD(wParam, lParam));
|
|
break;
|
|
|
|
case WM_SETFOCUS:
|
|
{
|
|
if (_fNoTrayItemsDisplayPolicyEnabled)
|
|
{
|
|
SetFocus(_hwndClock);
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
else
|
|
{
|
|
BOOL bFocusSet = FALSE;
|
|
//
|
|
// if there's a balloon tip up, start with focus on that icon
|
|
//
|
|
if (_pinfo)
|
|
{
|
|
INT_PTR nIcon = m_TrayItemManager.FindItemAssociatedWithHwndUid(_pinfo->hWnd, _pinfo->uID);
|
|
if (nIcon != -1 && ToolBar_IsVisible(_hwndToolbar, nIcon))
|
|
{
|
|
_SetToolbarHotItem(_hwndToolbar, nIcon);
|
|
_fChevronSelected = FALSE;
|
|
bFocusSet = TRUE;
|
|
}
|
|
}
|
|
if (!bFocusSet && _fHaveDemoted)
|
|
{
|
|
SetFocus(_hwndChevron);
|
|
_fChevronSelected = TRUE;
|
|
bFocusSet = TRUE;
|
|
}
|
|
|
|
if (!bFocusSet)
|
|
{
|
|
INT_PTR nToolbarIcon = _GetToolbarFirstVisibleItem(_hwndToolbar, FALSE);
|
|
if (nToolbarIcon != -1)
|
|
{
|
|
_SetToolbarHotItem(_hwndToolbar, nToolbarIcon);
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
else
|
|
{
|
|
SetFocus(_hwndClock);
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_SETREDRAW:
|
|
return _SetRedraw((BOOL) wParam);
|
|
|
|
case WM_ERASEBKGND:
|
|
if (_hTheme)
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
_Paint((HDC)wParam);
|
|
}
|
|
break;
|
|
|
|
|
|
case WM_PAINT:
|
|
case WM_PRINTCLIENT:
|
|
return _Paint((HDC)wParam);
|
|
|
|
case WM_CALCMINSIZE:
|
|
return _CalcMinSize((int)wParam, (int)lParam);
|
|
|
|
case WM_KEYDOWN:
|
|
return _OnKeyDown(wParam, lParam);
|
|
|
|
case WM_NCHITTEST:
|
|
return(IsPosInHwnd(lParam, _hwndClock) ? HTTRANSPARENT : HTCLIENT);
|
|
|
|
case WM_NOTIFY:
|
|
return(_Notify((LPNMHDR)lParam));
|
|
|
|
case TNM_GETCLOCK:
|
|
return (LRESULT)_hwndClock;
|
|
|
|
case TNM_TRAYHIDE:
|
|
if (lParam && IsWindowVisible(_hwndClock))
|
|
SendMessage(_hwndClock, TCM_RESET, 0, 0);
|
|
break;
|
|
|
|
case TNM_HIDECLOCK:
|
|
ShowWindow(_hwndClock, lParam ? SW_HIDE : SW_SHOW);
|
|
break;
|
|
|
|
case TNM_TRAYPOSCHANGED:
|
|
if (_pinfo && !_fNoTrayItemsDisplayPolicyEnabled)
|
|
PostMessage(_hwndNotify, TNM_ASYNCINFOTIPPOS, 0, 0);
|
|
break;
|
|
|
|
case TNM_ASYNCINFOTIPPOS:
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
_PositionInfoTip();
|
|
break;
|
|
|
|
case TNM_ASYNCINFOTIP:
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
_ShowInfoTip((HWND)wParam, (UINT)lParam, TRUE, FALSE, 0);
|
|
break;
|
|
|
|
case TNM_NOTIFY:
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
CNotificationItem* pni = (CNotificationItem*)lParam;
|
|
if (pni)
|
|
{
|
|
if (_pNotifyCB)
|
|
{
|
|
_pNotifyCB->Notify((UINT)wParam, pni);
|
|
if (wParam == NIM_ADD)
|
|
{
|
|
_TickleForTooltip(pni);
|
|
}
|
|
}
|
|
delete pni;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_SIZE:
|
|
_Size();
|
|
break;
|
|
|
|
case WM_TIMER:
|
|
_OnTimer(wParam);
|
|
break;
|
|
|
|
case TNM_UPDATEVERTICAL:
|
|
{
|
|
_UpdateVertical((BOOL)lParam);
|
|
}
|
|
break;
|
|
|
|
// only button down, mouse move msgs are forwarded down to us from info tip
|
|
//case WM_LBUTTONUP:
|
|
//case WM_MBUTTONUP:
|
|
//case WM_RBUTTONUP:
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
_InfoTipMouseClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (uMsg == WM_RBUTTONDOWN));
|
|
break;
|
|
|
|
case TNM_ICONDEMOTETIMER:
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
_OnIconDemoteTimer(wParam, lParam);
|
|
break;
|
|
|
|
case TNM_INFOTIPTIMER:
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
_OnInfoTipTimer();
|
|
break;
|
|
|
|
case TNM_SAVESTATE:
|
|
if (!_fNoTrayItemsDisplayPolicyEnabled)
|
|
{
|
|
_SetUsedTime();
|
|
m_TrayItemRegistry.InitTrayItemStream(STGM_WRITE, this->GetTrayItemCB, this);
|
|
}
|
|
break;
|
|
|
|
case TNM_STARTUPAPPSLAUNCHED:
|
|
_bStartupIcon = FALSE;
|
|
break;
|
|
|
|
// This message is sent by a shimmed Winstone app, to prevent the launching of
|
|
// user-tracked balloons. These "new" balloons last till the user has been at
|
|
// the machine for a minimum of 10 seconds. But automated Winstone tests cause
|
|
// this balloon to stay up forever, and screws up the tests. So we shim Winstone
|
|
// to pass us this message, and allow normal balloon tips for such a machine.
|
|
case TNM_ENABLEUSERTRACKINGINFOTIPS:
|
|
if ((BOOL) wParam == FALSE)
|
|
{
|
|
if (!_fNoTrayItemsDisplayPolicyEnabled && _fEnableUserTrackedInfoTips && _pinfo)
|
|
{
|
|
_fEnableUserTrackedInfoTips = (BOOL) wParam;
|
|
_beLastBalloonEvent = BALLOONEVENT_NONE;
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, FALSE, FALSE, NIN_BALLOONHIDE);
|
|
}
|
|
}
|
|
_fEnableUserTrackedInfoTips = (BOOL) wParam;
|
|
break;
|
|
|
|
case TNM_WORKSTATIONLOCKED:
|
|
_OnWorkStationLocked((BOOL)wParam);
|
|
break;
|
|
|
|
case TNM_RUDEAPP:
|
|
_OnRudeApp((BOOL)wParam);
|
|
break;
|
|
|
|
case TNM_SHOWTRAYBALLOON:
|
|
// If we enable display of tray balloons...
|
|
if (wParam)
|
|
{
|
|
// If we had disabled display of tray balloons earlier...
|
|
if (!_bStartMenuAllowsTrayBalloon)
|
|
{
|
|
SetTimer(_hwndNotify, TID_BALLOONSHOW, TT_BALLOONSHOW_INTERVAL, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
KillTimer(_hwndNotify, TID_BALLOONSHOW);
|
|
_bStartMenuAllowsTrayBalloon = FALSE;
|
|
|
|
// TO DO : Should we hide the balloon ?
|
|
}
|
|
break;
|
|
|
|
case WM_THEMECHANGED:
|
|
_OpenTheme();
|
|
break;
|
|
|
|
case WM_TIMECHANGE:
|
|
case WM_WININICHANGE:
|
|
case WM_POWERBROADCAST:
|
|
case WM_POWER:
|
|
_OnSysChange(uMsg, wParam, lParam);
|
|
// Fall through...
|
|
|
|
default:
|
|
return (DefWindowProc(hWnd, uMsg, wParam, lParam));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
INT_PTR CTrayNotify::_GetToolbarFirstVisibleItem(HWND hWndToolbar, BOOL bFromLast)
|
|
{
|
|
INT_PTR nToolbarIconSelected = -1;
|
|
|
|
if (_fNoTrayItemsDisplayPolicyEnabled)
|
|
return -1;
|
|
|
|
INT_PTR nTrayItemCount = m_TrayItemManager.GetItemCount()-1;
|
|
|
|
INT_PTR i = ((nTrayItemCount > 0) ? ((bFromLast) ? nTrayItemCount : 0) : -1);
|
|
|
|
if (i == -1)
|
|
return i;
|
|
|
|
do
|
|
{
|
|
if (ToolBar_IsVisible(hWndToolbar, i))
|
|
{
|
|
nToolbarIconSelected = i;
|
|
break;
|
|
}
|
|
i = (bFromLast ? ((i > 0) ? i-1 : -1) : ((i < nTrayItemCount) ? i+1 : -1));
|
|
}
|
|
while (i != -1);
|
|
|
|
return nToolbarIconSelected;
|
|
}
|
|
|
|
BOOL CTrayNotify::_TrayNotifyIcon(PTRAYNOTIFYDATA pnid, BOOL *pbRefresh)
|
|
{
|
|
// we want to refrain from re-painting if possible...
|
|
if (pbRefresh)
|
|
*pbRefresh = FALSE;
|
|
|
|
PNOTIFYICONDATA32 pNID = &pnid->nid;
|
|
if (pNID->cbSize < sizeof(NOTIFYICONDATA32))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (_fNoTrayItemsDisplayPolicyEnabled)
|
|
{
|
|
if (pnid->dwMessage == NIM_SETFOCUS)
|
|
{
|
|
if (_hwndClock)
|
|
{
|
|
SetFocus(_hwndClock);
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
INT_PTR nIcon = m_TrayItemManager.FindItemAssociatedWithHwndUid(GetHWnd(pNID), pNID->uID);
|
|
|
|
BOOL bRet = FALSE;
|
|
switch (pnid->dwMessage)
|
|
{
|
|
case NIM_SETFOCUS:
|
|
// the notify client is trying to return focus to us
|
|
if (nIcon >= 0)
|
|
{
|
|
if (!(_bRudeAppLaunched || IsDirectXAppRunningFullScreen()))
|
|
{
|
|
SetForegroundWindow(v_hwndTray);
|
|
if (ToolBar_IsVisible(_hwndToolbar, nIcon))
|
|
{
|
|
_SetToolbarHotItem(_hwndToolbar, nIcon);
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
else if (_fHaveDemoted)
|
|
{
|
|
SetFocus(_hwndChevron);
|
|
_fChevronSelected = TRUE;
|
|
}
|
|
else
|
|
{
|
|
INT_PTR nToolbarIcon = _GetToolbarFirstVisibleItem(_hwndToolbar, FALSE);
|
|
if (nToolbarIcon != -1)
|
|
{
|
|
_SetToolbarHotItem(_hwndToolbar, nToolbarIcon);
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
else
|
|
{
|
|
SetFocus(_hwndClock);
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SHAllowSetForegroundWindow(v_hwndTray);
|
|
}
|
|
|
|
if (pbRefresh)
|
|
*pbRefresh = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (_hwndClock)
|
|
{
|
|
SetFocus(_hwndClock);
|
|
_fChevronSelected = FALSE;
|
|
}
|
|
}
|
|
bRet = TRUE;
|
|
break;
|
|
|
|
case NIM_ADD:
|
|
// The icon doesnt already exist, and we dont insert again...
|
|
if (nIcon < 0)
|
|
{
|
|
bRet = _InsertNotify(pNID);
|
|
if (bRet && pbRefresh)
|
|
*pbRefresh = TRUE;
|
|
}
|
|
break;
|
|
|
|
case NIM_MODIFY:
|
|
if (nIcon >= 0)
|
|
{
|
|
BOOL bRefresh;
|
|
int nCountBefore = -1, nCountAfter = -1;
|
|
if (pbRefresh)
|
|
{
|
|
nCountBefore = m_TrayItemManager.GetPromotedItemCount();
|
|
if (m_TrayItemManager.GetDemotedItemCount() > 0)
|
|
nCountBefore ++;
|
|
}
|
|
|
|
bRet = _ModifyNotify(pNID, nIcon, &bRefresh, FALSE);
|
|
|
|
if (bRet && pbRefresh)
|
|
{
|
|
nCountAfter = m_TrayItemManager.GetPromotedItemCount();
|
|
if (m_TrayItemManager.GetDemotedItemCount() > 0)
|
|
nCountAfter ++;
|
|
|
|
*pbRefresh = (nCountBefore != nCountAfter);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NIM_DELETE:
|
|
if (nIcon >= 0)
|
|
{
|
|
bRet = _DeleteNotify(nIcon, FALSE, TRUE);
|
|
if (bRet)
|
|
{
|
|
if (pbRefresh)
|
|
{
|
|
*pbRefresh = TRUE;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NIM_SETVERSION:
|
|
if (nIcon >= 0)
|
|
{
|
|
// There is no point in handling NIM_SETVERSION if the "No-Tray-Items-Display"
|
|
// policy is in effect. The version enables proper keyboard and mouse notification
|
|
// messages to be sent to the apps, depending on the version of the shell
|
|
// specified.
|
|
// Since the policy prevents the display of any icons, there is no point in
|
|
// setting the correct version..
|
|
bRet = _SetVersionNotify(pNID, nIcon);
|
|
|
|
// No activity occurs in SetVersionNotify, so no need to refresh
|
|
// screen - pbRefresh is not set to TRUE...
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
// Public
|
|
LRESULT CTrayNotify::TrayNotify(HWND hwndNotify, HWND hwndFrom, PCOPYDATASTRUCT pcds, BOOL *pbRefresh)
|
|
{
|
|
PTRAYNOTIFYDATA pnid;
|
|
|
|
if (!hwndNotify || !pcds)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (pcds->cbData < sizeof(TRAYNOTIFYDATA))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// We'll add a signature just in case
|
|
pnid = (PTRAYNOTIFYDATA)pcds->lpData;
|
|
if (pnid->dwSignature != NI_SIGNATURE)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return _TrayNotifyIcon(pnid, pbRefresh);
|
|
}
|
|
|
|
// Public
|
|
HWND CTrayNotify::TrayNotifyCreate(HWND hwndParent, UINT uID, HINSTANCE hInst)
|
|
{
|
|
WNDCLASSEX wc;
|
|
|
|
ZeroMemory(&wc, sizeof(wc));
|
|
wc.cbSize = sizeof(WNDCLASSEX);
|
|
|
|
if (!GetClassInfoEx(hInst, c_szTrayNotify, &wc))
|
|
{
|
|
wc.lpszClassName = c_szTrayNotify;
|
|
wc.style = CS_DBLCLKS;
|
|
wc.lpfnWndProc = s_WndProc;
|
|
wc.hInstance = hInst;
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.hbrBackground = NULL;
|
|
wc.cbWndExtra = sizeof(CTrayNotify *);
|
|
|
|
if (!RegisterClassEx(&wc))
|
|
{
|
|
return(NULL);
|
|
}
|
|
|
|
if (!ClockCtl_Class(hInst))
|
|
{
|
|
return(NULL);
|
|
}
|
|
}
|
|
|
|
return (CreateWindowEx(0, c_szTrayNotify,
|
|
NULL, WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | WS_CLIPCHILDREN, 0, 0, 0, 0,
|
|
hwndParent, IntToPtr_(HMENU,uID), hInst, (void *)this));
|
|
}
|
|
|
|
void CTrayNotify::_UpdateChevronSize()
|
|
{
|
|
if (_hTheme)
|
|
{
|
|
HTHEME hTheme = OpenThemeData(_hwndChevron, L"Button");
|
|
if (hTheme)
|
|
{
|
|
HDC hdc = GetDC(_hwndChevron);
|
|
GetThemePartSize(hTheme, hdc, BP_PUSHBUTTON, PBS_DEFAULTED, NULL, TS_TRUE, &_szChevron);
|
|
ReleaseDC(_hwndChevron, hdc);
|
|
CloseThemeData(hTheme);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_szChevron.cx = GetSystemMetrics(SM_CXSMICON);
|
|
_szChevron.cy = GetSystemMetrics(SM_CYSMICON);
|
|
}
|
|
}
|
|
|
|
void CTrayNotify::_UpdateChevronState( BOOL fBangMenuOpen,
|
|
BOOL fTrayOrientationChanged, BOOL fUpdateDemotedItems)
|
|
{
|
|
BOOL fChange = FALSE;
|
|
|
|
if (_fNoTrayItemsDisplayPolicyEnabled)
|
|
return;
|
|
|
|
BOOL fHaveDemoted = _UpdateTrayItems(fUpdateDemotedItems);
|
|
|
|
if (_fHaveDemoted != fHaveDemoted)
|
|
{
|
|
_fHaveDemoted = fHaveDemoted;
|
|
ShowWindow(_hwndChevron, _fHaveDemoted ? SW_SHOW : SW_HIDE);
|
|
if (!_fHaveDemoted)
|
|
{
|
|
if (_fBangMenuOpen)
|
|
{
|
|
fBangMenuOpen = FALSE;
|
|
}
|
|
}
|
|
fChange = TRUE;
|
|
|
|
if (_fHaveDemoted && !_fBangMenuOpen)
|
|
{
|
|
_ShowChevronInfoTip();
|
|
}
|
|
else if ( (!_fHaveDemoted || (_fBangMenuOpen != fBangMenuOpen)) &&
|
|
_pinfo && _IsChevronInfoTip(_pinfo->hWnd, _pinfo->uID) )
|
|
{
|
|
_beLastBalloonEvent = BALLOONEVENT_NONE;
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, FALSE, FALSE, NIN_BALLOONHIDE);
|
|
}
|
|
}
|
|
|
|
if ( fChange || fTrayOrientationChanged ||
|
|
( _fHaveDemoted && (_fBangMenuOpen != fBangMenuOpen) )
|
|
)
|
|
{
|
|
if ((_fBangMenuOpen != fBangMenuOpen) && _pinfo && _IsChevronInfoTip(_pinfo->hWnd, _pinfo->uID))
|
|
{
|
|
_beLastBalloonEvent = BALLOONEVENT_NONE;
|
|
_ShowInfoTip(_pinfo->hWnd, _pinfo->uID, FALSE, FALSE, NIN_BALLOONHIDE);
|
|
}
|
|
|
|
_fBangMenuOpen = fBangMenuOpen;
|
|
LPCWSTR pwzTheme;
|
|
|
|
if (_fBangMenuOpen)
|
|
{
|
|
pwzTheme = _fVertical ? c_wzTrayNotifyVertOpenTheme : c_wzTrayNotifyHorizOpenTheme;
|
|
}
|
|
else
|
|
{
|
|
pwzTheme = _fVertical ? c_wzTrayNotifyVertTheme : c_wzTrayNotifyHorizTheme;
|
|
}
|
|
|
|
SetWindowTheme(_hwndChevron, pwzTheme, NULL);
|
|
_UpdateChevronSize();
|
|
}
|
|
}
|
|
|
|
|
|
void CTrayNotify::_UpdateVertical(BOOL fVertical)
|
|
{
|
|
_fVertical = fVertical;
|
|
LPCWSTR pwzTheme = _fVertical ? c_wzTrayNotifyVertTheme : c_wzTrayNotifyHorizTheme;
|
|
|
|
SetWindowTheme(_hwndNotify, pwzTheme, NULL);
|
|
_UpdateChevronState(_fBangMenuOpen, TRUE, TRUE);
|
|
}
|
|
|
|
void CTrayNotify::_OpenTheme()
|
|
{
|
|
if (_hTheme)
|
|
{
|
|
CloseThemeData(_hTheme);
|
|
_hTheme = NULL;
|
|
}
|
|
_hTheme = OpenThemeData(_hwndNotify, L"TrayNotify");
|
|
|
|
_UpdateChevronSize();
|
|
SetWindowStyleEx(_hwndNotify, WS_EX_STATICEDGE, !_hTheme);
|
|
|
|
InvalidateRect(_hwndNotify, NULL, FALSE);
|
|
}
|
|
|
|
// *** Helper functions for UserEventTimer..
|
|
HRESULT CTrayNotify::_SetItemTimer(CTrayItem * pti)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
if(pti)
|
|
{
|
|
ASSERT(pti->dwUserPref == TNUP_AUTOMATIC);
|
|
|
|
UINT uTimerInterval = m_TrayItemRegistry._uPrimaryCountdown;
|
|
if (pti->IsItemClicked())
|
|
{
|
|
// If the item has been clicked on, add 8 hours to its staying time
|
|
// in the tray...
|
|
uTimerInterval += TT_ICON_COUNTDOWN_INCREMENT;
|
|
}
|
|
if (pti->IsStartupIcon())
|
|
{
|
|
uTimerInterval -= (pti->uNumSeconds)*1000;
|
|
}
|
|
|
|
hr = _SetTimer(TF_ICONDEMOTE_TIMER, TNM_ICONDEMOTETIMER, uTimerInterval, &(pti->uIconDemoteTimerID));
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CTrayNotify::_SetTimer(int nTimerFlag, UINT uCallbackMessage, UINT uTimerInterval, ULONG * puTimerID)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
if (puTimerID)
|
|
{
|
|
IUserEventTimer * pUserEventTimer = _CreateTimer(nTimerFlag);
|
|
if (pUserEventTimer)
|
|
{
|
|
if (FAILED(hr = pUserEventTimer->SetUserEventTimer( _hwndNotify,
|
|
uCallbackMessage, uTimerInterval, NULL, puTimerID)))
|
|
{
|
|
*puTimerID = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*puTimerID = 0;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CTrayNotify::_KillItemTimer(CTrayItem *pti)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
if (pti)
|
|
{
|
|
hr = _KillTimer(TF_ICONDEMOTE_TIMER, pti->uIconDemoteTimerID);
|
|
// Irrespective of whether the timer ID was valid or not...
|
|
pti->uIconDemoteTimerID = 0;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CTrayNotify::_KillTimer(int nTimerFlag, ULONG uTimerID)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
if (uTimerID)
|
|
{
|
|
IUserEventTimer * pUserEventTimer = _CreateTimer(nTimerFlag);
|
|
if (pUserEventTimer)
|
|
{
|
|
hr = pUserEventTimer->KillUserEventTimer(_hwndNotify, uTimerID);
|
|
|
|
// If we are finished with the user tracking timer, we should release it
|
|
if (_ShouldDestroyTimer(nTimerFlag))
|
|
{
|
|
pUserEventTimer->Release();
|
|
_NullifyTimer(nTimerFlag);
|
|
}
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
void CTrayNotify::_NullifyTimer(int nTimerFlag)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
switch(nTimerFlag)
|
|
{
|
|
case TF_ICONDEMOTE_TIMER:
|
|
m_pIconDemoteTimer = NULL;
|
|
break;
|
|
|
|
case TF_INFOTIP_TIMER:
|
|
m_pInfoTipTimer = NULL;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
BOOL CTrayNotify::_ShouldDestroyTimer(int nTimerFlag)
|
|
{
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
switch(nTimerFlag)
|
|
{
|
|
case TF_ICONDEMOTE_TIMER:
|
|
return (!m_TrayItemRegistry.IsAutoTrayEnabled() || m_TrayItemManager.GetPromotedItemCount() == 0);
|
|
|
|
case TF_INFOTIP_TIMER:
|
|
return (!_GetQueueCount());
|
|
|
|
default:
|
|
AssertMsg(TF_ERROR, TEXT("No other timer ids are possible"));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
IUserEventTimer * CTrayNotify::_CreateTimer(int nTimerFlag)
|
|
{
|
|
IUserEventTimer ** ppUserEventTimer = NULL;
|
|
UINT uTimerTickInterval = 0;
|
|
|
|
ASSERT(!_fNoTrayItemsDisplayPolicyEnabled);
|
|
|
|
switch(nTimerFlag)
|
|
{
|
|
case TF_ICONDEMOTE_TIMER:
|
|
ppUserEventTimer = &m_pIconDemoteTimer;
|
|
break;
|
|
|
|
case TF_INFOTIP_TIMER:
|
|
ppUserEventTimer = &m_pInfoTipTimer;
|
|
break;
|
|
|
|
default:
|
|
AssertMsg(TF_ERROR, TEXT("No other Timers are possible"));
|
|
return NULL;
|
|
}
|
|
|
|
if (ppUserEventTimer && !*ppUserEventTimer)
|
|
{
|
|
if ( !SUCCEEDED(SHCoCreateInstance(NULL, &CLSID_UserEventTimer, NULL,
|
|
IID_PPV_ARG(IUserEventTimer, ppUserEventTimer))) )
|
|
{
|
|
*ppUserEventTimer = NULL;
|
|
}
|
|
else
|
|
{
|
|
uTimerTickInterval = m_TrayItemRegistry.GetTimerTickInterval(nTimerFlag);
|
|
|
|
(*ppUserEventTimer)->InitTimerTickInterval(uTimerTickInterval);
|
|
}
|
|
}
|
|
|
|
return *ppUserEventTimer;
|
|
}
|