#include "cabinet.h" #include "trayclok.h" #include #include "traynot.h" #include "rcids.h" #include "tray.h" #include "util.h" #include "shellapi.h" #include "strsafe.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, public CComCoClass, public ITrayNotify { public: CTrayNotifyStub() {}; virtual ~CTrayNotifyStub() {}; DECLARE_NOT_AGGREGATABLE(CTrayNotifyStub) BEGIN_COM_MAP(CTrayNotifyStub) COM_INTERFACE_ENTRY(ITrayNotify) END_COM_MAP() // *** ITrayNotify method *** 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 *pStub = new CComObject; 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() { ASSERT( 0 != m_cRef ); ULONG cRef = InterlockedDecrement(&m_cRef); if ( 0 == cRef ) { // // TODO: gpease 27-FEB-2002 // // delete this; Why is this statement missing? If on purpose, why even // bother with InterlockedXXX and even the refer counter?!? // } return cRef; } #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]; StringCchPrintf(tempBuf, ARRAYSIZE(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]; StringCchPrintf(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 ( !_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; StringCchCopy(pii->szInfo, ARRAYSIZE(pii->szInfo), pszInfo); StringCchCopy(pii->szTitle, ARRAYSIZE(pii->szTitle), pszInfoTitle); 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); // // pnid - NOTIFYICONDATA struct has an szTip of 64 or 128 // szIconText - CTrayItem has an szTip of MAX_PATH // We ensure that pnid->szTip is NULL terminated, but the right thing to do is ensure that we // copy only as many characters as we need, and dont overflow the buffer. StringCchCopy(pti->szIconText, min(ARRAYSIZE(pnid->szTip), ARRAYSIZE(pti->szIconText)), 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(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(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(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(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) { 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; }