#include "cabinet.h" #include "taskband.h" #include #include "bandsite.h" #include "util.h" #include "tray.h" #include "rcids.h" #include "bandsite.h" #include "startmnu.h" #include "mixer.h" #include #include "uemapp.h" #include "strsafe.h" #define TIF_RENDERFLASHED 0x000000001 #define TIF_SHOULDTIP 0x000000002 #define TIF_ACTIVATEALT 0x000000004 #define TIF_EVERACTIVEALT 0x000000008 #define TIF_FLASHING 0x000000010 #define TIF_TRANSPARENT 0x000000020 #define TIF_CHECKED 0x000000040 #define TIF_ISGLOMMING 0x000000080 #define TIF_NEEDSREDRAW 0x000000100 #define IDT_SYSMENU 2 #define IDT_ASYNCANIMATION 3 #define IDT_REDRAW 4 #define IDT_RECHECKRUDEAPP1 5 #define IDT_RECHECKRUDEAPP2 6 #define IDT_RECHECKRUDEAPP3 7 #define IDT_RECHECKRUDEAPP4 8 #define IDT_RECHECKRUDEAPP5 9 #define TIMEOUT_SYSMENU 2000 #define TIMEOUT_SYSMENU_HUNG 125 #define GLOM_OLDEST 0 #define GLOM_BIGGEST 1 #define GLOM_SIZE 2 #define ANIMATE_INSERT 0 #define ANIMATE_DELETE 1 #define ANIMATE_GLOM 2 #define IL_NORMAL 0 #define IL_SHIL 1 #define MAX_WNDTEXT 80 // arbitrary, matches NMTTDISPINFO.szText #define INVALID_PRIORITY (THREAD_PRIORITY_LOWEST - 1) const TCHAR c_szTaskSwClass[] = TEXT("MSTaskSwWClass"); const TCHAR c_wzTaskBandTheme[] = TEXT("TaskBand"); const TCHAR c_wzTaskBandThemeVert[] = TEXT("TaskBandVert"); const TCHAR c_wzTaskBandGroupMenuTheme[] = TEXT("TaskBandGroupMenu"); typedef struct { WCHAR szExeName[MAX_PATH]; } EXCLUDELIST; static const EXCLUDELIST g_rgNoGlom[] = { { L"rundll32.exe" } // Add any future apps that shouldn't be glommed }; void _RestoreWindow(HWND hwnd, DWORD dwFlags); HMENU _GetSystemMenu(HWND hwnd); BOOL _IsRudeWindowActive(HWND hwnd); //////////////////////////////////////////////////////////////////////////// // // BEGIN CTaskBandSMC // // CTaskBand can't implement IShellMenuCallback itself because menuband // sets itself as the callback's site. Hence this class. // // //////////////////////////////////////////////////////////////////////////// class CTaskBandSMC : public IShellMenuCallback , public IContextMenu , public IObjectWithSite { public: // *** IUnknown methods *** STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj) { static const QITAB qit[] = { QITABENT(CTaskBandSMC, IShellMenuCallback), QITABENT(CTaskBandSMC, IContextMenu), QITABENT(CTaskBandSMC, IObjectWithSite), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } STDMETHODIMP_(ULONG) AddRef() { return ++_cRef; } STDMETHODIMP_(ULONG) Release() { ASSERT(_cRef > 0); if (--_cRef > 0) { return _cRef; } delete this; return 0; } // *** IShellMenuCallback methods *** STDMETHODIMP CallbackSM(LPSMDATA smd, UINT uMsg, WPARAM wParam, LPARAM lParam); // *** IContextMenu methods *** STDMETHODIMP QueryContextMenu(HMENU hmenu, UINT iIndexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags); STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO lpici); STDMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax) { return E_NOTIMPL; } // *** IObjectWithSite methods *** STDMETHODIMP SetSite(IUnknown* punkSite) { ATOMICRELEASE(_punkSite); if (punkSite != NULL) { _punkSite = punkSite; _punkSite->AddRef(); } return S_OK; } STDMETHODIMP GetSite(REFIID riid, void** ppvSite) { return E_NOTIMPL; }; CTaskBandSMC(CTaskBand* ptb) : _cRef(1) { _ptb = ptb; _ptb->AddRef(); } private: virtual ~CTaskBandSMC() { ATOMICRELEASE(_ptb); } ULONG _cRef; CTaskBand* _ptb; IUnknown* _punkSite; HWND _hwndSelected; }; STDMETHODIMP CTaskBandSMC::CallbackSM(LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ASSERT(_ptb); HRESULT hres = S_FALSE; if (!_ptb->_IsButtonChecked(_ptb->_iIndexPopup) && (SMC_EXITMENU != uMsg)) { _ptb->_SetCurSel(_ptb->_iIndexPopup, TRUE); } switch (uMsg) { case SMC_EXEC: { PTASKITEM pti = _ptb->_GetItem(psmd->uId); if (pti) { _ptb->_SetCurSel(psmd->uId, FALSE); _ptb->_OnButtonPressed(psmd->uId, pti, lParam); hres = S_OK; } } break; case SMC_GETINFO: { SMINFO* psminfo = (SMINFO*)lParam; hres = S_OK; if (psminfo->dwMask & SMIM_TYPE) { psminfo->dwType = SMIT_STRING; } if (psminfo->dwMask & SMIM_FLAGS) { psminfo->dwFlags = SMIF_ICON | SMIF_DRAGNDROP; } if (psminfo->dwMask & SMIM_ICON) { TBBUTTONINFO tbbi; tbbi.iImage = I_IMAGENONE; PTASKITEM pti = _ptb->_GetItem(psmd->uId, &tbbi); if (pti && tbbi.iImage == I_IMAGECALLBACK) { _ptb->_UpdateItemIcon(psmd->uId); _ptb->_GetItem(psmd->uId, &tbbi); } psminfo->iIcon = tbbi.iImage; } } break; case SMC_CUSTOMDRAW: { PTASKITEM pti = _ptb->_GetItem(psmd->uId); if (pti) { *(LRESULT*)wParam = _ptb->_HandleCustomDraw((NMTBCUSTOMDRAW*)lParam, pti); hres = S_OK; } } break; case SMC_SELECTITEM: { PTASKITEM pti = _ptb->_GetItem(psmd->uId); _hwndSelected = pti ? pti->hwnd : NULL; } break; case SMC_GETOBJECT: { GUID *pguid = (GUID*)wParam; if (IsEqualIID(*pguid, IID_IContextMenu) && !SHRestricted(REST_NOTRAYCONTEXTMENU)) { hres = QueryInterface(*pguid, (void **)lParam); } else { hres = E_FAIL; } } break; case SMC_GETINFOTIP: { PTASKITEM pti = _ptb->_GetItem(psmd->uId); if (pti) { _ptb->_GetItemTitle(psmd->uId, (TCHAR*)wParam, (int)lParam, TRUE); hres = S_OK; } } break; case SMC_GETIMAGELISTS: { HIMAGELIST himl = (HIMAGELIST)_ptb->_tb.SendMessage(TB_GETIMAGELIST, psmd->uId, 0); if (himl) { *((HIMAGELIST*)lParam) = *((HIMAGELIST*)wParam) = himl; hres = S_OK; } } break; case SMC_EXITMENU: { _hwndSelected = NULL; CToolTipCtrl ttc = _ptb->_tb.GetToolTips(); ttc.Activate(TRUE); _ptb->_iIndexPopup = -1; } break; } return hres; } // *** IContextMenu methods *** STDMETHODIMP CTaskBandSMC::QueryContextMenu(HMENU hmenu, UINT iIndexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { ASSERT(_ptb); HRESULT hr = ResultFromShort(0); if (_hwndSelected != NULL) { HMENU hmenuTemp = _GetSystemMenu(_hwndSelected); if (hmenuTemp) { if (Shell_MergeMenus(hmenu, hmenuTemp, 0, iIndexMenu, idCmdLast, uFlags)) { SetMenuDefaultItem(hmenu, 0, MF_BYPOSITION); hr = ResultFromShort(GetMenuItemCount(hmenuTemp)); } DestroyMenu(hmenuTemp); } } return hr; } STDMETHODIMP CTaskBandSMC::InvokeCommand(LPCMINVOKECOMMANDINFO lpici) { ASSERT(_ptb); PTASKITEM pti = _ptb->_FindItemByHwnd(_hwndSelected); if (pti) { int iCommand = LOWORD(lpici->lpVerb); if (iCommand) { _RestoreWindow(pti->hwnd, pti->dwFlags); _ptb->_ExecuteMenuOption(pti->hwnd, iCommand); } } return S_OK; } //////////////////////////////////////////////////////////////////////////// // // END CTaskBandSMC // //////////////////////////////////////////////////////////////////////////// ULONG CTaskBand::AddRef() { _cRef++; return _cRef; } ULONG CTaskBand::Release() { ASSERT(_cRef > 0); _cRef--; if (_cRef > 0) return _cRef; delete this; return 0; } HRESULT CTaskBand::GetWindow(HWND * lphwnd) { *lphwnd = _hwnd; if (_hwnd) return S_OK; return E_FAIL; } CTaskBand::CTaskBand() : _dwBandID((DWORD)-1), _iDropItem(-2), _iIndexActiveAtLDown(-1), _cRef(1), _iOldPriority(INVALID_PRIORITY) { } CTaskBand::~CTaskBand() { ATOMICRELEASE(_punkSite); ATOMICRELEASE(_pimlSHIL); if (_dsaAII) _dsaAII.Destroy(); if (_hfontCapNormal) DeleteFont(_hfontCapNormal); if (_hfontCapBold) DeleteFont(_hfontCapBold); } HRESULT CTaskBand::QueryInterface(REFIID riid, LPVOID* ppvObj) { static const QITAB qit[] = { QITABENTMULTI(CTaskBand, IDockingWindow, IDeskBand), QITABENTMULTI(CTaskBand, IOleWindow, IDeskBand), QITABENT(CTaskBand, IDeskBand), QITABENT(CTaskBand, IObjectWithSite), QITABENT(CTaskBand, IDropTarget), QITABENT(CTaskBand, IInputObject), QITABENTMULTI(CTaskBand, IPersist, IPersistStream), QITABENT(CTaskBand, IPersistStream), QITABENT(CTaskBand, IWinEventHandler), QITABENT(CTaskBand, IOleCommandTarget), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } HRESULT CTaskBand::Init(CTray* ptray) { HRESULT hr = E_OUTOFMEMORY; if (_dsaAII.Create(2)) { _ptray = ptray; hr = S_OK; } return hr; } // *** IPersistStream methods *** HRESULT CTaskBand::GetClassID(LPCLSID pClassID) { *pClassID = CLSID_TaskBand; return S_OK; } HRESULT CTaskBand::_BandInfoChanged() { if (_dwBandID != (DWORD)-1) { VARIANTARG var = {0}; var.vt = VT_I4; var.lVal = _dwBandID; return IUnknown_Exec(_punkSite, &CGID_DeskBand, DBID_BANDINFOCHANGED, 0, &var, NULL); } else return S_OK; } HRESULT CTaskBand::Load(IStream *ps) { return S_OK; } // *** IOleCommandTarget *** STDMETHODIMP CTaskBand::Exec(const GUID *pguidCmdGroup,DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut) { HRESULT hr = OLECMDERR_E_NOTSUPPORTED; return hr; } STDMETHODIMP CTaskBand::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT *pcmdtext) { if (pguidCmdGroup) { if (IsEqualIID(*pguidCmdGroup, IID_IDockingWindow)) { for (UINT i = 0; i < cCmds; i++) { switch (rgCmds[i].cmdID) { case DBID_PERMITAUTOHIDE: rgCmds[i].cmdf = OLECMDF_SUPPORTED; if (!_fFlashing) { rgCmds[i].cmdf |= OLECMDF_ENABLED; } break; } } return S_OK; } } return OLECMDERR_E_UNKNOWNGROUP; } //*** IInputObject methods *** HRESULT CTaskBand::HasFocusIO() { BOOL f; HWND hwndFocus = GetFocus(); f = IsChildOrHWND(_hwnd, hwndFocus); ASSERT(hwndFocus != NULL || !f); ASSERT(_hwnd != NULL || !f); return f ? S_OK : S_FALSE; } HRESULT CTaskBand::UIActivateIO(BOOL fActivate, LPMSG lpMsg) { ASSERT(NULL == lpMsg || IS_VALID_WRITE_PTR(lpMsg, MSG)); if (fActivate) { // don't show a hot item if we weren't properly tabbed // into/clicked on, in which case we have a NULL lpMsg, // e.g. if the tray just decided to activate us for lack of // anyone better. _fDenyHotItemChange = !lpMsg; IUnknown_OnFocusChangeIS(_punkSite, SAFECAST(this, IInputObject*), TRUE); ::SetFocus(_hwnd); _fDenyHotItemChange = FALSE; } else { // if we don't have focus, we're fine; // if we do have focus, there's nothing we can do about it... } return S_OK; } HRESULT CTaskBand::SetSite(IUnknown* punk) { if (punk && !_hwnd) { _LoadSettings(); _RegisterWindowClass(); HWND hwndParent; IUnknown_GetWindow(punk, &hwndParent); HWND hwnd = CreateWindowEx(0, c_szTaskSwClass, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndParent, NULL, hinstCabinet, (void*)(CImpWndProc*)this); SetWindowTheme(hwnd, c_wzTaskBandTheme, NULL); } ATOMICRELEASE(_punkSite); if (punk) { _punkSite = punk; punk->AddRef(); } return S_OK; } HRESULT CTaskBand::GetBandInfo(DWORD dwBandID, DWORD fViewMode, DESKBANDINFO* pdbi) { _dwBandID = dwBandID; pdbi->ptMaxSize.y = -1; pdbi->ptActual.y = g_cySize + 2*g_cyEdge; LONG lButHeight = _GetCurButtonHeight(); if (fViewMode & DBIF_VIEWMODE_VERTICAL) { pdbi->ptMinSize.x = lButHeight; // The 1.2 gives us enough space for the dropdown arrow pdbi->ptMinSize.y = lButHeight * (_fGlom ? 1.2 : 1); pdbi->ptIntegral.y = 1; } else { TBMETRICS tbm; _GetToolbarMetrics(&tbm); pdbi->ptMinSize.x = lButHeight * 3; pdbi->ptMinSize.y = lButHeight; pdbi->ptIntegral.y = lButHeight + tbm.cyButtonSpacing; } pdbi->dwModeFlags = DBIMF_VARIABLEHEIGHT | DBIMF_UNDELETEABLE | DBIMF_TOPALIGN; pdbi->dwMask &= ~DBIM_TITLE; // no title for us (ever) DWORD dwOldViewMode = _dwViewMode; _dwViewMode = fViewMode; if (_tb && (_dwViewMode != dwOldViewMode)) { SendMessage(_tb, TB_SETWINDOWTHEME, 0, (LPARAM)(_IsHorizontal() ? c_wzTaskBandTheme : c_wzTaskBandThemeVert)); _CheckSize(); } return S_OK; } void _RaiseDesktop(BOOL fRaise) { SendMessage(v_hwndTray, TM_RAISEDESKTOP, fRaise, 0); } // *** IDropTarget methods *** STDMETHODIMP CTaskBand::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect) { _DragEnter(_hwnd, ptl, pdtobj); IUnknown_DragEnter(_punkSite, pdtobj, grfKeyState, ptl, pdwEffect); _iDropItem = -2; // reset to no target *pdwEffect = DROPEFFECT_LINK; return S_OK; } STDMETHODIMP CTaskBand::DragLeave() { IUnknown_DragLeave(_punkSite); DAD_DragLeave(); return S_OK; } STDMETHODIMP CTaskBand::DragOver(DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect) { int iHitNew = _HitTest(ptl); if (iHitNew == -1) { DWORD dwEffect = *pdwEffect; IUnknown_DragOver(_punkSite, grfKeyState, ptl, &dwEffect); } *pdwEffect = DROPEFFECT_LINK; _DragMove(_hwnd, ptl); if (_iDropItem != iHitNew) { _iDropItem = iHitNew; _dwTriggerStart = GetTickCount(); _dwTriggerDelay = 250; if (iHitNew == -1) { _dwTriggerDelay += 250; // make a little longer for minimize all } } else if (GetTickCount() - _dwTriggerStart > _dwTriggerDelay) { DAD_ShowDragImage(FALSE); // unlock the drag sink if we are dragging. if (_iDropItem == -1) { _RaiseDesktop(TRUE); } else if (_iDropItem >= 0 && _iDropItem < _tb.GetButtonCount()) { _iIndexLastPopup = -1; _SwitchToItem(_iDropItem, _GetItem(_iDropItem)->hwnd, TRUE); UpdateWindow(v_hwndTray); } DAD_ShowDragImage(TRUE); // restore the lock state. _dwTriggerDelay += 10000; // don't let this happen again for 10 seconds // simulate a single shot event } if (_iDropItem != -1) *pdwEffect = DROPEFFECT_MOVE; // try to get the move cursor else *pdwEffect = DROPEFFECT_NONE; return S_OK; } STDMETHODIMP CTaskBand::Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect) { IUnknown_DragLeave(_punkSite); DAD_DragLeave(); // // post ourselves a message to put up a message box to explain that you // can't drag to the taskbar. we need to return from the Drop method // now so the DragSource isn't hung while our box is up // PostMessage(_hwnd, TBC_WARNNODROP, 0, 0L); // be sure to clear DROPEFFECT_MOVE so apps don't delete their data *pdwEffect = DROPEFFECT_NONE; return S_OK; } // *** IWinEventHandler methods *** HRESULT CTaskBand::OnWinEvent(HWND hwnd, UINT dwMsg, WPARAM wParam, LPARAM lParam, LRESULT* plres) { *plres = 0; switch (dwMsg) { case WM_WININICHANGE: _HandleWinIniChange(wParam, lParam, FALSE); break; case WM_NOTIFY: if (lParam) { switch (((LPNMHDR)lParam)->code) { case NM_SETFOCUS: IUnknown_OnFocusChangeIS(_punkSite, SAFECAST(this, IInputObject*), TRUE); break; } } break; } return S_OK; } HRESULT CTaskBand::IsWindowOwner(HWND hwnd) { BOOL bRet = IsChildOrHWND(_hwnd, hwnd); ASSERT (_hwnd || !bRet); ASSERT (hwnd || !bRet); return bRet ? S_OK : S_FALSE; } //----------------------------------------------------------------------------- // DESCRIPTION: Returns whether or not the button is hidden // // PARAMETERS: 1. hwndToolBar - handle to the toolbar window // 2. iIndex - item index // // RETURN: TRUE = Item is visible, FALSE = Item is hidden. //----------------------------------------------------------------------------- BOOL ToolBar_IsVisible(HWND hwndToolBar, int iIndex) { TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_STATE | TBIF_BYINDEX; SendMessage(hwndToolBar, TB_GETBUTTONINFO, iIndex, (LPARAM) &tbbi); return !(tbbi.fsState & TBSTATE_HIDDEN); } //***************************************************************************** // // ITEM ANIMATION FUNCTIONS // //***************************************************************************** //----------------------------------------------------------------------------- // DESCRIPTION: Inserts item(s) into the animation list // // PARAMETERS: 1. iIndex - index for item, group index for a group // 2. fExpand - TRUE = Insert or Unglom, FALSE = Delete or Glom // 3. fGlomAnimation - TRUE = this is a glom or unglom animation //----------------------------------------------------------------------------- BOOL CTaskBand::_AnimateItems(int iIndex, BOOL fExpand, BOOL fGlomAnimation) { ANIMATIONITEMINFO aii; _SetAnimationState(&aii, fExpand, fGlomAnimation); // Is item being inserted into glomming group? if (aii.fState == ANIMATE_INSERT) { int iIndexGroup = _GetGroupIndex(iIndex); if (_GetItem(iIndexGroup)->dwFlags & TIF_ISGLOMMING) { aii.fState = ANIMATE_GLOM; } } else if (aii.fState == ANIMATE_GLOM) { _GetItem(iIndex)->dwFlags |= TIF_ISGLOMMING; } // Number of items to animate int cItems = 1; if (fGlomAnimation) { // insert the group cItems = _GetGroupSize(iIndex); iIndex++; } // Insert items into animation list while(cItems) { aii.iIndex = iIndex; aii.pti = _GetItem(iIndex); if (aii.fState == ANIMATE_DELETE) { // NOTE: HWND_TOPMOST is used here to indicate that the deleted // button is being animated. This allows the button to stay // around after its hwnd becomes invalid aii.pti->hwnd = HWND_TOPMOST; aii.pti->dwFlags |= TIF_TRANSPARENT; } //sorts left to right && removes redundant items int iAnimationPos = _GetAnimationInsertPos(iIndex); _dsaAII.InsertItem(iAnimationPos++, &aii); cItems--; iIndex++; } SetTimer(_hwnd, IDT_ASYNCANIMATION, 100, NULL); return TRUE; } //----------------------------------------------------------------------------- // DESCRIPTION: Animates the items in the animtation list by one step. //----------------------------------------------------------------------------- void CTaskBand::_AsyncAnimateItems() { BOOL fRedraw = (BOOL)SendMessage(_tb, WM_SETREDRAW, FALSE, 0); // Glomming is turned off here because in the middle of the animation we // may call _DeleteItem which could cause an unglom\glom. // This is bad because it would modify the contents of animation list that // we are in the middle of processing. BOOL fGlom = _fGlom; _fGlom = FALSE; _UpdateAnimationIndices(); _ResizeAnimationItems(); int iDistanceLeft = _CheckAnimationSize(); _fGlom = fGlom; _CheckSize(); SendMessage(_tb, WM_SETREDRAW, fRedraw, 0); UpdateWindow(_tb); if (_dsaAII.GetItemCount()) { SetTimer(_hwnd, IDT_ASYNCANIMATION, _GetStepTime(iDistanceLeft), NULL); } else { KillTimer(_hwnd, IDT_ASYNCANIMATION); if (_ptray->_hwndLastActive) { int iIndex = _FindIndexByHwnd(_ptray->_hwndLastActive); if ((iIndex != -1) && (_IsButtonChecked(iIndex))) { _ScrollIntoView(iIndex); } } _RestoreThreadPriority(); // Make sure no one was glommed into a group of one // there are certain race conditions where this can happen for (int i = _tb.GetButtonCount() - 1; i >= 0; i--) { PTASKITEM pti = _GetItem(i); if (!pti->hwnd) { int iSize = _GetGroupSize(i); if ((iSize < 2) && (!_IsHidden(i))) { _Glom(i, FALSE); } } } } } //----------------------------------------------------------------------------- // DESCRIPTION: Adjusts the widths of the animating items by the animation // step. // // RETURN: The Total width of all animating items. //----------------------------------------------------------------------------- void CTaskBand::_ResizeAnimationItems() { int cxStep = _GetAnimationStep(); for (int i = _dsaAII.GetItemCount() - 1; i >= 0; i--) { PANIMATIONITEMINFO paii = _dsaAII.GetItemPtr(i); _SetAnimationItemWidth(paii, cxStep); } } //----------------------------------------------------------------------------- // DESCRIPTION: Checks if animation items have reached their target animation // width // // RETURN: The total distance left to animate //----------------------------------------------------------------------------- int CTaskBand::_CheckAnimationSize() { PANIMATIONITEMINFO paii; int iTotDistLeft = 0; int iRemainder = 0; int iNormalWidth = _GetIdealWidth(&iRemainder); int cAnimatingItems = _dsaAII.GetItemCount(); for (int i = cAnimatingItems - 1; i >= 0; i--) { paii = _dsaAII.GetItemPtr(i); if (paii) { int iDistLeft = _GetAnimationDistLeft(paii, iNormalWidth); if (!iDistLeft) { ANIMATIONITEMINFO aiiTemp = *paii; _dsaAII.DeleteItem(i); _FinishAnimation(&aiiTemp); } else { iTotDistLeft += iDistLeft; } } #ifdef DEBUG else { int nCurrentCount = _dsaAII.GetItemCount(); if (i >= nCurrentCount) TraceMsg(TF_ERROR, "Invalid counter %x in the loop, size = %x", i, nCurrentCount); else TraceMsg(TF_ERROR, "NULL paii for %x.", i); } #endif } return iTotDistLeft; } //----------------------------------------------------------------------------- // DESCRIPTION: Sets the animation state for an ANIMATIONITEMINFO struct. // // PARAMETERS: 1. paii - PANIMATIONITEMINFO for the animation item // 2. fExpand - TRUE = Insert or Unglom, FALSE = Delete or Glom // 3. fGlomAnimation - TRUE = this is a glom or unglom animation //----------------------------------------------------------------------------- void CTaskBand::_SetAnimationState(PANIMATIONITEMINFO paii, BOOL fExpand, BOOL fGlomAnimation) { if (fExpand) { paii->fState = ANIMATE_INSERT; } else { if (fGlomAnimation) { paii->fState = ANIMATE_GLOM; } else { paii->fState = ANIMATE_DELETE; } } } //----------------------------------------------------------------------------- // DESCRIPTION: Determines the animation list index that keeps the list in the // same order as the toolbar indexes. // (Duplicate toolbar items are removed from the animation list.) // // PARAMETERS: 1. iIndex - item's index in the toolbar // // RETURN: The position the item should be inserted into the animation list //----------------------------------------------------------------------------- int CTaskBand::_GetAnimationInsertPos(int iIndex) { int iPos = 0; if (_dsaAII.GetItemCount()) { _UpdateAnimationIndices(); for (int i = _dsaAII.GetItemCount() - 1; i >= 0; i--) { PANIMATIONITEMINFO paii = _dsaAII.GetItemPtr(i); if (paii->iIndex == iIndex) { // remove duplicate _dsaAII.DeleteItem(i); iPos = i; break; } else if (paii->iIndex < iIndex) { iPos = i + 1; break; } } } return iPos; } void CTaskBand::_RemoveItemFromAnimationList(PTASKITEM ptiRemove) { for (int i = _dsaAII.GetItemCount() - 1; i >= 0; i--) { PANIMATIONITEMINFO paii = _dsaAII.GetItemPtr(i); if (paii->pti == ptiRemove) { _dsaAII.DeleteItem(i); break; } } } //----------------------------------------------------------------------------- // DESCRIPTION: Adjusts the width of the animating item by the animation step. // // PARAMETERS: 1. paii - PANIMATIONITEMINFO for the animation item // 2. cxStep - animation step used to adjust the item's width // // RETURN: the new width //----------------------------------------------------------------------------- #define ANIM_SLOWSTEPS 3 #define ANIM_SLOWZONE 15 void CTaskBand::_SetAnimationItemWidth(PANIMATIONITEMINFO paii, int cxStep) { int iWidth = _GetItemWidth(paii->iIndex); switch (paii->fState) { case ANIMATE_INSERT: iWidth += cxStep; break; case ANIMATE_DELETE: //slow animation towards end if (((iWidth / cxStep) <= ANIM_SLOWSTEPS) && ((iWidth - cxStep) < ANIM_SLOWZONE - _GetVisibleItemCount())) { // The last step takes 3 times as long cxStep = cxStep / 3; } iWidth -= cxStep; iWidth = max(iWidth, 0); break; case ANIMATE_GLOM: iWidth -= cxStep; iWidth = max(iWidth, 1); //toolbar sizes 0 width to full size break; } _SetItemWidth(paii->iIndex, iWidth); } //----------------------------------------------------------------------------- // DESCRIPTION: Returns the distance the items must travel to end the // animation // // PARAMETERS: 1. paii - pointer to the ANIMATIONITEMINFO for the item // 2. iNormalWidth - width of a non-animation item // // RETURN: the distance the items must travel to end the animation //----------------------------------------------------------------------------- int CTaskBand::_GetAnimationDistLeft(PANIMATIONITEMINFO paii, int iNormalWidth) { int cxDistLeft = 0; int iWidth = _GetItemWidth(paii->iIndex); switch (paii->fState) { case ANIMATE_INSERT: cxDistLeft = max(0, iNormalWidth - iWidth); break; case ANIMATE_DELETE: if ((paii->iIndex == _GetLastVisibleItem()) && (iNormalWidth == g_cxMinimized)) { cxDistLeft = 0; } else { cxDistLeft = max(0, iWidth); } break; case ANIMATE_GLOM: { int iGroupIndex = _GetGroupIndex(paii->iIndex); if (!ToolBar_IsVisible(_tb, iGroupIndex)) { int cGroupSize = _GetGroupSize(iGroupIndex); if (cGroupSize) { int iGroupWidth = _GetGroupWidth(iGroupIndex); cxDistLeft = max(0, iGroupWidth - iNormalWidth); if (iGroupWidth == cGroupSize) { cxDistLeft = 0; } cxDistLeft = cxDistLeft/cGroupSize; } } } break; } return cxDistLeft; } //----------------------------------------------------------------------------- // DESCRIPTION: Completes tasks to finish an animation // // PARAMETERS: 1. paii - pointer to the ANIMATIONITEMINFO for the item // // RETURN: the distance the items must travel to end the animation //----------------------------------------------------------------------------- void CTaskBand::_FinishAnimation(PANIMATIONITEMINFO paii) { switch (paii->fState) { case ANIMATE_DELETE: _DeleteItem(NULL, paii->iIndex); break; case ANIMATE_GLOM: { int iGroupIndex = _GetGroupIndex(paii->iIndex); if (!ToolBar_IsVisible(_tb, iGroupIndex)) { // Turn off glomming flag _GetItem(iGroupIndex)->dwFlags &= ~TIF_ISGLOMMING; _HideGroup(iGroupIndex, TRUE); } // NOTE: HWND_TOPMOST is used to indicate that the deleted button // is being animated. This allows the button to stay around after // its real hwnd becomes invalid if (paii->pti->hwnd == HWND_TOPMOST) { // The button was deleting before it was glommed // Now that the glomming is done, delete it. _DeleteItem(NULL, paii->iIndex); } } break; } } //----------------------------------------------------------------------------- // DESCRIPTION: Returns the width of all the animating buttons // // RETURN: The total animation width //----------------------------------------------------------------------------- int CTaskBand::_GetAnimationWidth() { int iTotAnimationWidth = 0; _UpdateAnimationIndices(); for (int i = _dsaAII.GetItemCount() - 1; i >= 0; i--) { PANIMATIONITEMINFO paii = _dsaAII.GetItemPtr(i); iTotAnimationWidth += _GetItemWidth(paii->iIndex); } return iTotAnimationWidth; } //----------------------------------------------------------------------------- // DESCRIPTION: Synchronizes the indexes held by the animating items to the // true toolbar indexes. // Note: This function may cause the number of animating items to // change. //----------------------------------------------------------------------------- void CTaskBand::_UpdateAnimationIndices() { int cAnimatingItems = _dsaAII.GetItemCount(); if (cAnimatingItems) { // NOTE: items in the animation list are in the same order as the // toolbar int iCurrAnimationItem = cAnimatingItems - 1; PANIMATIONITEMINFO paii = _dsaAII.GetItemPtr(iCurrAnimationItem); for (int i = _tb.GetButtonCount() - 1; i >=0 ; i--) { if (_GetItem(i) == paii->pti) { paii->iIndex = i; iCurrAnimationItem--; if (iCurrAnimationItem < 0) { break; } paii = _dsaAII.GetItemPtr(iCurrAnimationItem); } } // If animation items are not in the same order as the items in the // toolbar then iCurrAnimationItem not be -1 //ASSERT(iCurrAnimationItem == -1); if (iCurrAnimationItem != -1) { _UpdateAnimationIndicesSlow(); } } } void CTaskBand::_UpdateAnimationIndicesSlow() { #ifdef DEBUG int cAnimatingItems = _dsaAII.GetItemCount(); TraceMsg(TF_WARNING, "CTaskBand::_UpdateAnimationIndicesSlow: enter"); #endif for (int i = _dsaAII.GetItemCount() - 1; i >= 0; i--) { PANIMATIONITEMINFO paii = _dsaAII.GetItemPtr(i); int iIndex = _FindItem(paii->pti); if (iIndex == -1) { _dsaAII.DeleteItem(i); } else { paii->iIndex = i; } } #ifdef DEBUG // Being in this function means that either an animating item is no longer in the // toolbar, or that the animating items are in a different order than the toolbar. // If the animating items are only in a different order (bad), the number of animating // items will remain the same. if (cAnimatingItems == _dsaAII.GetItemCount()) { TraceMsg(TF_WARNING, "CTaskBand::_UpdateAnimationIndicesSlow: Animating items are in diff order than toolbar"); } #endif } int CTaskBand::_FindItem(PTASKITEM pti) { int iIndex = -1; if (pti) { for (int i = _tb.GetButtonCount() - 1; i >= 0; i--) { if (pti == _GetItem(i)) { iIndex = i; break; } } } return iIndex; } //----------------------------------------------------------------------------- // DESCRIPTION: Animation Step Constants //----------------------------------------------------------------------------- #define ANIM_STEPFACTOR 9 #define ANIM_STEPMAX 40 // max size of an animation step #define ANIM_STEPMIN 11 // min size of an animation step //----------------------------------------------------------------------------- // DESCRIPTION: Determines an animation step based on the number of items // visible in the toolbar. // // PARAMETERS: 1. iTotalItems - number of visible items in toolbar // // RETURN: The animation step //----------------------------------------------------------------------------- int CTaskBand::_GetAnimationStep() { DWORD dwStep; int iVisibleItems = _GetVisibleItemCount(); int iRows; _GetNumberOfRowsCols(&iRows, NULL, TRUE); // _GetNumberOfRows will never return < 1 int iTotalItems = iVisibleItems - _dsaAII.GetItemCount(); // The step must be large when there are many items, but can be very small // when there are few items. This is achieved by cubing the total items. dwStep = (DWORD)(iTotalItems * iTotalItems * iTotalItems) / ANIM_STEPFACTOR; dwStep = min(dwStep, ANIM_STEPMAX); dwStep = max(dwStep, ANIM_STEPMIN); return dwStep; } //----------------------------------------------------------------------------- // DESCRIPTION: Animation Sleep Constants //----------------------------------------------------------------------------- #define ANIM_PAUSE 1000 #define ANIM_MAXPAUSE 30 //----------------------------------------------------------------------------- // DESCRIPTION: Returns the amount of time to sleep // // PARAMETERS: 1. iStep - current animation step // 2. cSteps - total animation steps // 3. iStepSize - step size for the animation // // RETURN: time to sleep //----------------------------------------------------------------------------- DWORD CTaskBand::_GetStepTime(int cx) { // NOTE: The cx is decrementing to ZERO. // As the cx gets smaller we want to // increment the sleep time. // don't let cx be zero cx = max(1, cx); cx = min(32767, cx); // x^2 curve gives a larger pause at the end. int iDenominator = cx * cx; return min(ANIM_MAXPAUSE, ANIM_PAUSE / iDenominator); } //***************************************************************************** // END OF ANIMATION FUNCTIONS //***************************************************************************** void CTaskBand::_SetItemWidth(int iItem, int iWidth) { TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_SIZE | TBIF_BYINDEX; tbbi.cx = (WORD)iWidth; _tb.SetButtonInfo(iItem, &tbbi); } int CTaskBand::_GetItemWidth(int iItem) { TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_SIZE | TBIF_BYINDEX; _tb.GetButtonInfo(iItem, &tbbi); return tbbi.cx; } //----------------------------------------------------------------------------- // DESCRIPTION: Retrives the index of the last visible button on the toolbar // // RETURN: Index of the last visible item on the toolbar. //----------------------------------------------------------------------------- int CTaskBand::_GetLastVisibleItem() { int iLastIndex = -1; for (int i = _tb.GetButtonCount() - 1; i >=0 ; i--) { if (ToolBar_IsVisible(_tb, i)) { iLastIndex = i; break; } } return iLastIndex; } //----------------------------------------------------------------------------- // DESCRIPTION: Retrives the total width of all buttons in the group // // PARAMETERS: 1. iIndexGroup - the index of the group // // RETURN: the total width of all buttons in the group //----------------------------------------------------------------------------- int CTaskBand::_GetGroupWidth(int iIndexGroup) { int iGroupWidth = 0; int cButtons = _tb.GetButtonCount(); for (int i = iIndexGroup + 1; i < cButtons; i++) { PTASKITEM pti = _GetItem(i); if (!pti->hwnd) { break; } iGroupWidth += _GetItemWidth(i); } return iGroupWidth; } //----------------------------------------------------------------------------- // DESCRIPTION: Retrives the number of visible buttons on the toolbar // RETURN: the number of visible buttons on the toolbar //----------------------------------------------------------------------------- int CTaskBand::_GetVisibleItemCount() { int cItems = 0; // Count the number of visible buttons before the animated item(s) for (int i = _tb.GetButtonCount() - 1; i >=0 ; i--) { if (ToolBar_IsVisible(_tb, i)) { cItems++; } } return cItems; } //----------------------------------------------------------------------------- // DESCRIPTION: Retrives the ideal width of a non-animating button // // PARAMETERS: 1. iRemainder[OUT] - width needed for the total item width // to equal the window width. (set to zero unless the ideal // width is less than the maximum button width. // // RETURN: the total width of all buttons in the group //----------------------------------------------------------------------------- int CTaskBand::_GetIdealWidth(int *iRemainder) { int iIdeal = 0; *iRemainder = 0; RECT rcWin; GetWindowRect(_hwnd, &rcWin); int iWinWidth = RECTWIDTH(rcWin); int iRows; _GetNumberOfRowsCols(&iRows, NULL, TRUE); int cItems = _GetVisibleItemCount(); // button spacing TBMETRICS tbm; _GetToolbarMetrics(&tbm); if (iRows == 1) { // window width that can be used for non-animating items iWinWidth -= (_GetAnimationWidth() + (_dsaAII.GetItemCount() * tbm.cxButtonSpacing)); iWinWidth = max(0, iWinWidth); // find number of non-animating items cItems -= _dsaAII.GetItemCount(); cItems = max(1, cItems); } // We need to round up so that iCols is the smallest number such that // iCols*iRows >= cItems int iCols = (cItems + iRows - 1) / iRows; iCols = max(1, iCols); // calculate the ideal width iIdeal = (iWinWidth / iCols); if (iCols > 1) { iIdeal -= tbm.cxButtonSpacing; } // adjust ideal width int iMax = _IsHorizontal() ? g_cxMinimized : iWinWidth; int iMin = g_cySize + 2*g_cxEdge; if (_IsHorizontal()) { iMin *= 1.8; } iMin += _GetTextSpace(); iIdeal = min(iMax, iIdeal); // calculate the remainder if (_IsHorizontal() && (iIdeal != iMax) && (iRows == 1) && (iIdeal >= iMin)) { *iRemainder = iWinWidth - (iCols * (iIdeal + tbm.cxButtonSpacing)); *iRemainder = max(0, *iRemainder); } return iIdeal; } void CTaskBand::_GetNumberOfRowsCols(int* piRows, int* piCols, BOOL fCurrentSize) { RECT rcWin; RECT rcItem; RECT rcTB; int iIndexVisible = _GetLastVisibleItem(); GetWindowRect(_hwnd, &rcWin); int cxTB = RECTWIDTH(rcWin); int cyTB = RECTHEIGHT(rcWin); if (fCurrentSize) { GetWindowRect(_tb, &rcTB); DWORD dwStyle = GetWindowLong(_hwnd, GWL_STYLE); if (dwStyle & WS_HSCROLL) { cyTB = RECTHEIGHT(rcTB); } else if (dwStyle & WS_VSCROLL) { cxTB = RECTWIDTH(rcTB); } } _tb.GetItemRect(iIndexVisible, &rcItem); TBMETRICS tbm; _GetToolbarMetrics(&tbm); if (piRows) { int cyRow = RECTHEIGHT(rcItem) + tbm.cyButtonSpacing; *piRows = (cyTB + tbm.cyButtonSpacing) / cyRow; *piRows = max(*piRows, 1); } if (piCols && RECTWIDTH(rcItem)) { int cxCol = RECTWIDTH(rcItem) + tbm.cxButtonSpacing; *piCols = (cxTB + tbm.cxButtonSpacing) / cxCol; *piCols = max(*piCols, 1); } } //----------------------------------------------------------------------------- // DESCRIPTION: Retrives the minimum text width for a button. (used only to // determine when task items should be glommed.) // // RETURN: the minimum text width for a button //----------------------------------------------------------------------------- int CTaskBand::_GetTextSpace() { int iTextSpace = 0; if (_fGlom && _IsHorizontal() && (_iGroupSize < GLOM_SIZE)) { if (!_iTextSpace) { HFONT hfont = (HFONT)SendMessage(_tb, WM_GETFONT, 0, 0); if (hfont) { HDC hdc = GetDC(_tb); TEXTMETRIC tm; GetTextMetrics(hdc, &tm); _iTextSpace = tm.tmAveCharWidth * 8; ReleaseDC(_tb, hdc); } } iTextSpace = _iTextSpace; } return iTextSpace; } //----------------------------------------------------------------------------- // DESCRIPTION: Retrieves the toolbar metrics requested by the mask // // RETURN: toolbar metrics //----------------------------------------------------------------------------- void CTaskBand::_GetToolbarMetrics(TBMETRICS *ptbm) { ptbm->cbSize = sizeof(*ptbm); ptbm->dwMask = TBMF_PAD | TBMF_BARPAD | TBMF_BUTTONSPACING; _tb.SendMessage(TB_GETMETRICS, 0, (LPARAM)ptbm); } //----------------------------------------------------------------------------- // DESCRIPTION: Sizes the non-animating buttons to the taskbar. Shrinks // and/or gloms items so that all visible items fit on window. //----------------------------------------------------------------------------- void CTaskBand::_CheckSize() { if (_dsaAII) { int cItems = _GetVisibleItemCount(); // Check for non-animating buttons to size if (cItems > _dsaAII.GetItemCount()) { // Handle grouping by size if (_fGlom && (_iGroupSize >= GLOM_SIZE)) { _AutoGlomGroup(TRUE, 0); } RECT rc; GetWindowRect(_hwnd, &rc); if (!IsRectEmpty(&rc) && (_tb.GetWindowLong(GWL_STYLE) & WS_VISIBLE)) { int iRemainder = 0; int iIdeal = _GetIdealWidth(&iRemainder); BOOL fHoriz = _IsHorizontal(); int iMin = g_cySize + 2*g_cxEdge; if (fHoriz) { iMin *= 1.8; } iMin += _GetTextSpace(); iIdeal = max(iIdeal, iMin); _SizeItems(iIdeal, iRemainder); _tb.SetButtonWidth(iIdeal, iIdeal); int iRows; int iCols; _GetNumberOfRowsCols(&iRows, &iCols, FALSE); BOOL fAllowUnGlom = TRUE; if (_fGlom && fHoriz && (iIdeal == iMin)) { _AutoGlomGroup(TRUE, 0); iMin = (g_cySize + 2*g_cxEdge) * 1.8; iIdeal = _GetIdealWidth(&iRemainder); iIdeal = max(iIdeal, iMin); _SizeItems(iIdeal, iRemainder); _tb.SetButtonWidth(iIdeal, iIdeal); fAllowUnGlom = FALSE; } // if we're forced to the minimum size, then we may need some scrollbars if ((fHoriz && (iIdeal == iMin)) || (!fHoriz && (cItems > (iRows * iCols)))) { if (!(_fGlom && _AutoGlomGroup(TRUE, 0))) { TBMETRICS tbm; _GetToolbarMetrics(&tbm); RECT rcItem; _tb.GetItemRect(_GetLastVisibleItem(), &rcItem); int cyRow = RECTHEIGHT(rcItem) + tbm.cyButtonSpacing; int iColsInner = (cItems + iRows - 1) / iRows; _CheckNeedScrollbars(cyRow, cItems, iColsInner, iRows, iIdeal + tbm.cxButtonSpacing, &rc); } } else { if (_fGlom && fHoriz) { // iMin is reset above to handle the two-stage minimum. The first minimum size forces glomming, and the second minimum size // enforces hard limit for minimum button size. // In the case, of unglomming we need unglom base on the first minimum size iMin = (g_cySize + 2*g_cxEdge) * 1.8 + _GetTextSpace(); } int cOpenSlots = fHoriz ? ((RECTWIDTH(rc) - _GetAnimationWidth()) - (iMin * (cItems - _dsaAII.GetItemCount()))) / iMin : iRows - cItems; if (!(_fGlom && (cOpenSlots >= 2) && fAllowUnGlom && _AutoGlomGroup(FALSE, cOpenSlots))) { _NukeScrollbar(SB_HORZ); _NukeScrollbar(SB_VERT); _tb.SetWindowPos(0, 0, 0, RECTWIDTH(rc), RECTHEIGHT(rc), SWP_NOACTIVATE | SWP_NOZORDER); } } // force wrap recalc _tb.AutoSize(); } else { _SizeItems(g_cxMinimized); _tb.SetButtonWidth(g_cxMinimized, g_cxMinimized); } } } } //----------------------------------------------------------------------------- // DESCRIPTION: Set the sizes of non-animating buttons // // PARAMETERS: 1. iButtonWidth - width to assign each non-animating item // 2. IRemainder - extra width to keep total width constant. // //----------------------------------------------------------------------------- void CTaskBand::_SizeItems(int iButtonWidth, int iRemainder) { TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_SIZE | TBIF_BYINDEX; int iAnimCount = _dsaAII.GetItemCount(); for (int i = _tb.GetButtonCount() - 1; i >=0 ; i--) { if (ToolBar_IsVisible(_tb, i)) { BOOL fResize = TRUE; if (iAnimCount) { for (int j = 0; (j < iAnimCount) && fResize; j++) { PANIMATIONITEMINFO paii = _dsaAII.GetItemPtr(j); if (paii->iIndex == i) { fResize = FALSE; } } } if (fResize) { tbbi.cx = (WORD) iButtonWidth; if (iRemainder) { tbbi.cx++; iRemainder--; } _tb.SetButtonInfo(i, &tbbi); } } } } //--------------------------------------------------------------------------- // // Track which shortcut launched a particular task. // Every so often, we tickle the file's entry in the UEM database // to indicate that the program has been running for a long time. // // These structures are used only by the taskbar thread, hence do // not need to be thread-safe. // class TaskShortcut { public: TaskShortcut(LPCTSTR pszExeName, DWORD pid); void AddRef() { _cRef++; } void Release() { if (--_cRef == 0) delete this; } void Tickle(); void Promote(); static BOOL _PromotePidl(LPCITEMIDLIST pidl, BOOL fForce); inline BOOL MatchesCachedPid(PTASKITEM pti) { return _pid == s_pidCache; } static BOOL MatchesCachedExe(PTASKITEM pti) { return pti->pszExeName && lstrcmpiW(pti->pszExeName, s_szTargetNameCache) == 0; } inline BOOL MatchesPid(DWORD pid) const { return pid == _pid; } void SetInfoFromCache(); static BOOL _HandleShortcutInvoke(LPSHShortcutInvokeAsIDList psidl); // // Note that the session time is now hard-coded to 4 hours and is not // affected by the browseui session time. // enum { s_msSession = 4 * 3600 * 1000 // 4 hours - per DCR }; private: static DWORD s_pidCache; static int s_csidlCache; static WCHAR s_szShortcutNameCache[MAX_PATH]; static WCHAR s_szTargetNameCache[MAX_PATH]; private: ~TaskShortcut() { SHFree(_pszShortcutName); } ULONG _cRef; // reference count DWORD _pid; // process id DWORD _tmTickle; // time of last tickle int _csidl; // csidl we are a child of LPWSTR _pszShortcutName; // Which shortcut launched us? (NULL = don't know) }; //--------------------------------------------------------------------------- // DWORD TaskShortcut::s_pidCache; int TaskShortcut::s_csidlCache; WCHAR TaskShortcut::s_szShortcutNameCache[MAX_PATH]; WCHAR TaskShortcut::s_szTargetNameCache[MAX_PATH]; TaskShortcut::TaskShortcut(LPCTSTR pszExeName, DWORD pid) : _cRef(1), _pid(pid), _tmTickle(GetTickCount()), _pszShortcutName(NULL) { // If this app was recently launched from a shortcut, // save the shortcut name. if (s_pidCache == pid && pszExeName && pszExeName[0] && lstrcmpi(pszExeName, s_szTargetNameCache) == 0) { SetInfoFromCache(); } } void TaskShortcut::SetInfoFromCache() { _csidl = s_csidlCache; SHStrDup(s_szShortcutNameCache, &_pszShortcutName); } //--------------------------------------------------------------------------- void CTaskBand::_AttachTaskShortcut(PTASKITEM pti, LPCTSTR pszExeName) { DWORD pid = 0; GetWindowThreadProcessId(pti->hwnd, &pid); int i; for (i = _tb.GetButtonCount() - 1; i >= 0; i--) { PTASKITEM ptiT = _GetItem(i); if (ptiT->ptsh && ptiT->ptsh->MatchesPid(pid)) { pti->ptsh = ptiT->ptsh; pti->ptsh->AddRef(); return; } } // Wow, the first window associated with this pid. Need to create // a new entry. // Make sure nobody tries to do this in a multithreaded way // since we're not protecting the cache with a critical section ASSERT(GetCurrentThreadId() == GetWindowThreadProcessId(_hwnd, NULL)); pti->ptsh = new TaskShortcut(pszExeName, pid); } // // There is a race condition between app startup and our receiving the // change notification. If the app starts up first, the // _AttachTaskShortcut will fail because we haven't received the change // notification yet. // // _ReattachTaskShortcut looks back through the taskbar and checks if // the program for which we received the change notification is already // on the taskbar, in which case we update his information retroactively. // void CTaskBand::_ReattachTaskShortcut() { // Make sure nobody tries to do this in a multithreaded way // since we're not protecting the cache with a critical section ASSERT(GetCurrentThreadId() == GetWindowThreadProcessId(_hwnd, NULL)); int i; for (i = _tb.GetButtonCount() - 1; i >= 0; i--) { PTASKITEM ptiT = _GetItem(i); if (ptiT->ptsh && ptiT->ptsh->MatchesCachedPid(ptiT)) { int iIndexGroup = _GetGroupIndex(i); PTASKITEM ptiGroup = _GetItem(iIndexGroup); if (ptiT->ptsh->MatchesCachedExe(ptiGroup)) { ptiT->ptsh->SetInfoFromCache(); // Stop after finding the first match, since all apps // with the same pid share the same TaskShortcut, so // updating one entry fixes them all. return; } } } } //--------------------------------------------------------------------------- void TaskShortcut::Tickle() { if (_pszShortcutName) { DWORD tmNow = GetTickCount(); if (tmNow - _tmTickle > s_msSession) { _tmTickle = tmNow; // Note that we promote only once, even if multiple tickle intervals // have elapsed. That way, if you leave Outlook running while you // go on a two-week vacation, then click on Outlook when you get // back, we treat this as one usage, not dozens. // Promote(); } } } //--------------------------------------------------------------------------- // Returns whether or not we actually promoted anybody BOOL TaskShortcut::_PromotePidl(LPCITEMIDLIST pidl, BOOL fForce) { BOOL fPromoted = FALSE; IShellFolder *psf; LPCITEMIDLIST pidlChild; if (SUCCEEDED(SHBindToFolderIDListParent(NULL, pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlChild))) { if (!fForce) { // Don't fire the event if somebody else ran the // shortcut within the last session. We want to bump // the usage count only once per session even if there // are multiple apps running that use the shortcut. FILETIME ftSession; // start of current session GetSystemTimeAsFileTime(&ftSession); DecrementFILETIME(&ftSession, (__int64)10000 * s_msSession); UEMINFO uei; uei.cbSize = sizeof(uei); uei.dwMask = UEIM_FILETIME; SetFILETIMEfromInt64(&uei.ftExecute, 0); // If this query fails, then uei.ftExecute stays 0 UEMQueryEvent(&UEMIID_SHELL, UEME_RUNPIDL, (WPARAM)psf, (LPARAM)pidlChild, &uei); fForce = CompareFileTime(&uei.ftExecute, &ftSession) < 0; } if (fForce) { UEMFireEvent(&UEMIID_SHELL, UEME_RUNPIDL, UEMF_XEVENT, (WPARAM)psf, (LPARAM)pidlChild); fPromoted = TRUE; } psf->Release(); } return fPromoted; } //--------------------------------------------------------------------------- void TaskShortcut::Promote() { // Use SHSimpleIDListFromPath so we don't spin up drives or // hang Explorer if the drive is unavailable LPITEMIDLIST pidl = SHSimpleIDListFromPath(_pszShortcutName); if (pidl) { if (_PromotePidl(pidl, FALSE)) { // Now we have to walk back up the tree to the root of our // csidl, because that's what the Start Menu does. // (Promoting a child entails promoting all his parents. // Otherwise you can get into a weird state where a child // has been promoted but his ancestors haven't.) LPITEMIDLIST pidlParent; if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, _csidl, &pidlParent))) { for (ILRemoveLastID(pidl); ILIsParent(pidlParent, pidl, FALSE); ILRemoveLastID(pidl)) { _PromotePidl(pidl, TRUE); } } } ILFree(pidl); } } //--------------------------------------------------------------------------- BOOL _IsChildOfCsidl(int csidl, LPCWSTR pwszPath) { WCHAR wszCsidl[MAX_PATH]; // Explicitly check S_OK. S_FALSE means directory doesn't exist, // so no point in checking for prefix. if (S_OK == SHGetFolderPathW(NULL, csidl, NULL, SHGFP_TYPE_CURRENT, wszCsidl)) { return PathIsPrefixW(wszCsidl, pwszPath); } return FALSE; } const int c_rgCsidlShortcutInvoke[] = { CSIDL_DESKTOPDIRECTORY, CSIDL_PROGRAMS, CSIDL_COMMON_DESKTOPDIRECTORY, CSIDL_COMMON_PROGRAMS, }; BOOL TaskShortcut::_HandleShortcutInvoke(LPSHShortcutInvokeAsIDList psidl) { // The shortcut must reside in one of the directories that the Start Page // cares about int i; for (i = 0; i < ARRAYSIZE(c_rgCsidlShortcutInvoke); i++) { if (_IsChildOfCsidl(c_rgCsidlShortcutInvoke[i], psidl->szShortcutName)) { // Yes it is -- cache it s_pidCache = psidl->dwPid; s_csidlCache = c_rgCsidlShortcutInvoke[i]; StringCchCopy(s_szShortcutNameCache, ARRAYSIZE(s_szShortcutNameCache), psidl->szShortcutName); StringCchCopy(s_szTargetNameCache, ARRAYSIZE(s_szTargetNameCache), psidl->szTargetName); return TRUE; } } return FALSE; } TASKITEM::TASKITEM(TASKITEM* pti) { hwnd = pti->hwnd; dwFlags = pti->dwFlags; ptsh = NULL; dwTimeLastClicked = pti->dwTimeLastClicked; dwTimeFirstOpened = pti->dwTimeFirstOpened; if (pti->pszExeName) { int cchExeName = lstrlen(pti->pszExeName) + 1; pszExeName = new WCHAR[cchExeName]; if (pszExeName) { StringCchCopy(pszExeName, cchExeName, pti->pszExeName); } } } TASKITEM::~TASKITEM() { if (ptsh) ptsh->Release(); if (pszExeName) { delete [] pszExeName; } } BOOL IsSmallerThanScreen(HWND hwnd) { HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); MONITORINFO mi; mi.cbSize = sizeof(mi); GetMonitorInfo(hMonitor, &mi); WINDOWINFO wi; wi.cbSize = sizeof(wi); GetWindowInfo(hwnd, &wi); int dxMax = mi.rcWork.right - mi.rcWork.left; int dyMax = mi.rcWork.bottom - mi.rcWork.top; return ((wi.rcWindow.right - wi.rcWindow.left < dxMax) || (wi.rcWindow.bottom - wi.rcWindow.top < dyMax)); } HMENU _GetSystemMenu(HWND hwnd) { // We have to make a copy of the menu because the documentation for // GetSystemMenu blatantly lies, it does not give you a copy of the hmenu // and you are not at liberty to alter said menu HMENU hmenu = CreatePopupMenu(); Shell_MergeMenus(hmenu, GetSystemMenu(hwnd, FALSE), 0, 0, 0xffff, 0); if (hmenu) { /* Stolen from Core\ntuser\kernel\mnsys.c xxxSetSysMenu */ UINT wSize; UINT wMinimize; UINT wMaximize; UINT wMove; UINT wRestore; UINT wDefault; LONG lStyle = GetWindowLong(hwnd, GWL_STYLE); /* * System modal window: no size, icon, zoom, or move. */ wSize = wMaximize = wMinimize = wMove = 0; wRestore = MFS_GRAYED; // // Default menu command is close. // wDefault = SC_CLOSE; /* * Minimized exceptions: no minimize, restore. */ // we need to reverse these because VB has a "special" window // that is both minimized but without a minbox. if (IsIconic(hwnd)) { wRestore = 0; wMinimize = MFS_GRAYED; wSize = MFS_GRAYED; wDefault = SC_RESTORE; } else if (!(lStyle & WS_MINIMIZEBOX)) wMinimize = MFS_GRAYED; /* * Maximized exceptions: no maximize, restore. */ if (!(lStyle & WS_MAXIMIZEBOX)) wMaximize = MFS_GRAYED; else if (IsZoomed(hwnd)) { wRestore = 0; /* * If the window is maximized but it isn't larger than the * screen, we allow the user to move the window around the * desktop (but we don't allow resizing). */ wMove = MFS_GRAYED; if (!(lStyle & WS_CHILD)) { if (IsSmallerThanScreen(hwnd)) { wMove = 0; } } wSize = MFS_GRAYED; wMaximize = MFS_GRAYED; } if (!(lStyle & WS_SIZEBOX)) wSize = MFS_GRAYED; /* * Are we dealing with a framed dialog box with a sys menu? * Dialogs with min/max/size boxes get a regular system menu * (as opposed to the dialog menu) */ if (!(lStyle & WS_DLGFRAME) || (lStyle & (WS_SIZEBOX | WS_MINIMIZEBOX | WS_MAXIMIZEBOX))) { EnableMenuItem(hmenu, (UINT)SC_SIZE, wSize); EnableMenuItem(hmenu, (UINT)SC_MINIMIZE, wMinimize); EnableMenuItem(hmenu, (UINT)SC_MAXIMIZE, wMaximize); EnableMenuItem(hmenu, (UINT)SC_RESTORE, wRestore); } EnableMenuItem(hmenu, (UINT)SC_MOVE, wMove); SetMenuDefaultItem(hmenu, wDefault, MF_BYCOMMAND); } return hmenu; } void CTaskBand::_ExecuteMenuOption(HWND hwnd, int iCmd) { if (iCmd == SC_SIZE || iCmd == SC_MOVE) { _FreePopupMenu(); SwitchToThisWindow(hwnd, TRUE); } PostMessage(hwnd, WM_SYSCOMMAND, iCmd, 0); } BOOL _IsWindowNormal(HWND hwnd) { return (hwnd != v_hwndTray) && (hwnd != v_hwndDesktop) && IsWindow(hwnd); } void _RestoreWindow(HWND hwnd, DWORD dwFlags) { HWND hwndTask = hwnd; HWND hwndProxy = hwndTask; if (g_fDesktopRaised) { _RaiseDesktop(FALSE); } // set foreground first so that we'll switch to it. if (IsIconic(hwndTask) && (dwFlags & TIF_EVERACTIVEALT)) { HWND hwndProxyT = (HWND) GetWindowLongPtr(hwndTask, 0); if (hwndProxyT != NULL && IsWindow(hwndProxyT)) hwndProxy = hwndProxyT; } SetForegroundWindow(GetLastActivePopup(hwndProxy)); if (hwndProxy != hwndTask) SendMessage(hwndTask, WM_SYSCOMMAND, SC_RESTORE, -2); } PTASKITEM CTaskBand::_GetItem(int i, TBBUTTONINFO* ptbb /*= NULL*/, BOOL fByIndex /*= TRUE*/) { if (i >= 0 && i < _tb.GetButtonCount()) { TBBUTTONINFO tbb; if (ptbb == NULL) { ptbb = &tbb; ptbb->dwMask = TBIF_LPARAM; } else { ptbb->dwMask = TBIF_COMMAND | TBIF_IMAGE | TBIF_LPARAM | TBIF_SIZE | TBIF_STATE | TBIF_STYLE; } if (fByIndex) { ptbb->dwMask |= TBIF_BYINDEX; } ptbb->cbSize = sizeof(*ptbb); _tb.GetButtonInfo(i, ptbb); ASSERT(ptbb->lParam); // we check for NULL before insertion, so shouldn't be NULL here return (PTASKITEM)ptbb->lParam; } return NULL; } int CTaskBand::_FindIndexByHwnd(HWND hwnd) { if (hwnd) { for (int i = _tb.GetButtonCount() - 1; i >= 0; i--) { PTASKITEM pti = _GetItem(i); if (pti && pti->hwnd == hwnd) { return i; } } } return -1; } void CTaskBand::_CheckNeedScrollbars(int cyRow, int cItems, int iCols, int iRows, int iItemWidth, LPRECT prcView) { int cxRow = iItemWidth; int iVisibleColumns = RECTWIDTH(*prcView) / cxRow; int iVisibleRows = RECTHEIGHT(*prcView) / cyRow; int x,y, cx,cy; RECT rcTabs; rcTabs = *prcView; iVisibleColumns = max(iVisibleColumns, 1); iVisibleRows = max(iVisibleRows, 1); SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_PAGE | SIF_RANGE; si.nMin = 0; si.nPage = 0; si.nPos = 0; if (_IsHorizontal()) { // do vertical scrollbar // -1 because it's 0 based. si.nMax = (cItems + iVisibleColumns - 1) / iVisibleColumns -1 ; si.nPage = iVisibleRows; // we're actually going to need the scrollbars if (si.nPage <= (UINT)si.nMax) { // this effects the vis columns and therefore nMax and nPage rcTabs.right -= g_cxVScroll; iVisibleColumns = RECTWIDTH(rcTabs) / cxRow; if (!iVisibleColumns) iVisibleColumns = 1; si.nMax = (cItems + iVisibleColumns - 1) / iVisibleColumns -1 ; } SetScrollInfo(_hwnd, SB_VERT, &si, TRUE); si.fMask = SIF_POS | SIF_PAGE | SIF_RANGE; GetScrollInfo(_hwnd, SB_VERT, &si); x = 0; y = -si.nPos * cyRow; if (iRows == 1) { cx = RECTWIDTH(rcTabs); } else { cx = cxRow * iVisibleColumns; } // +1 because si.nMax is zero based cy = cyRow * (si.nMax +1); // nuke the other scroll bar _NukeScrollbar(SB_HORZ); } else { // do horz scrollbar si.nMax = iCols -1; si.nPage = iVisibleColumns; // we're actually going to need the scrollbars if (si.nPage <= (UINT)si.nMax) { // this effects the vis columns and therefore nMax and nPage rcTabs.bottom -= g_cyHScroll; iVisibleRows = RECTHEIGHT(rcTabs) / cyRow; if (!iVisibleRows) iVisibleRows = 1; si.nMax = (cItems + iVisibleRows - 1) / iVisibleRows -1 ; } SetScrollInfo(_hwnd, SB_HORZ, &si, TRUE); si.fMask = SIF_POS | SIF_PAGE | SIF_RANGE; GetScrollInfo(_hwnd, SB_HORZ, &si); y = 0; x = -si.nPos * cxRow; cx = cxRow * (si.nMax + 1); cy = cyRow * iVisibleRows; // nuke the other scroll bar _NukeScrollbar(SB_VERT); } _tb.SetWindowPos(0, x,y, cx, cy, SWP_NOACTIVATE| SWP_NOZORDER); } void CTaskBand::_NukeScrollbar(int fnBar) { SCROLLINFO si; si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS; si.cbSize = sizeof(si); si.nMin = 0; si.nMax = 0; si.nPage = 0; si.nPos = 0; SetScrollInfo(_hwnd, fnBar, &si, TRUE); } BOOL CTaskBand::_IsHidden(int i) { TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_STATE | TBIF_BYINDEX; _tb.GetButtonInfo(i, &tbbi); if (tbbi.fsState & TBSTATE_HIDDEN) { return TRUE; } return FALSE; } int CTaskBand::_GetGroupIndexFromExeName(WCHAR* pszExeName) { for (int i = _tb.GetButtonCount() - 1; i >=0; i--) { PTASKITEM pti = _GetItem(i); if ((!pti->hwnd) && (lstrcmpi(pti->pszExeName, pszExeName) == 0)) { return i; } } return -1; } DWORD CTaskBand::_GetGroupAge(int iIndexGroup) { int iGroupSize = _GetGroupSize(iIndexGroup); DWORD dwTimeLastClicked = _GetItem(iIndexGroup + 1)->dwTimeLastClicked; for (int i = iIndexGroup + 2; i <= iIndexGroup + iGroupSize; i++) { PTASKITEM pti = _GetItem(i); if (pti->dwTimeLastClicked > dwTimeLastClicked) { dwTimeLastClicked = pti->dwTimeLastClicked; } } return dwTimeLastClicked; } // // _GetGroupSize: returns size of group *not including* the group button // int CTaskBand::_GetGroupSize(int iIndexGroup) { int iGroupSize = 0; PTASKITEM ptiGroup = _GetItem(iIndexGroup); if (ptiGroup) { ASSERT(!ptiGroup->hwnd); int cButtons = _tb.GetButtonCount(); for (int i = iIndexGroup + 1; i < cButtons; i++) { PTASKITEM pti = _GetItem(i); if (!pti->hwnd) { break; } iGroupSize++; } } return iGroupSize; } int CTaskBand::_GetGroupIndex(int iIndexApp) { int i = iIndexApp; while ((i > 0) && (_GetItem(i)->hwnd)) { i--; } return i; } void CTaskBand::_UpdateFlashingFlag() { // Loop through the tab items, see if any have TIF_FLASHING // set, and update the flashing flag. _fFlashing = FALSE; int iCount = _tb.GetButtonCount(); for (int i = 0; i < iCount; i++) { PTASKITEM pti = _GetItem(i); if (!pti->hwnd) { pti->dwFlags &= ~(TIF_FLASHING | TIF_RENDERFLASHED); } else { int iGroupIndex = _GetGroupIndex(i); PTASKITEM ptiGroup = _GetItem(iGroupIndex); if (pti->dwFlags & TIF_FLASHING) { ptiGroup->dwFlags |= TIF_FLASHING; _fFlashing = TRUE; } if (pti->dwFlags & TIF_RENDERFLASHED) { ptiGroup->dwFlags |= TIF_RENDERFLASHED; } } } } void CTaskBand::_RealityCheck() { // // Delete any buttons corresponding to non-existent windows. // for (int i = 0; i < _tb.GetButtonCount(); i++) { PTASKITEM pti = _GetItem(i); // NOTE: HWND_TOPMOST is used to indicate that the deleted button // is being animated. This allows the button to stay around after // its real hwnd becomes invalid if (pti->hwnd && !IsWindow(pti->hwnd) && ((pti->hwnd != HWND_TOPMOST) || !_dsaAII.GetItemCount())) { #ifdef DEBUG PTASKITEM ptiGroup = _GetItem(_GetGroupIndex(i)); TraceMsg(TF_WARNING, "CTaskBand::_RealityCheck: window %x (%s) no longer valid", pti->hwnd, ptiGroup->pszExeName); #endif _DeleteItem(pti->hwnd, i); } } } class ICONDATA { public: ICONDATA(int i, CTaskBand* p) : iPref(i), ptb(p) { ptb->AddRef(); } virtual ~ICONDATA() { ptb->Release(); } int iPref; CTaskBand* ptb; }; typedef ICONDATA* PICONDATA; void CALLBACK CTaskBand::IconAsyncProc(HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult) { PICONDATA pid = (PICONDATA)dwData; if (pid) { pid->ptb->_SetWindowIcon(hwnd, (HICON)lResult, pid->iPref); delete pid; } } int CTaskBand::GetIconCB(CTaskBand* ptb, PICONCBPARAM pip, LPARAM lParam, int iPref) { int iRet = I_IMAGENONE; if (IsWindow(pip->hwnd)) { PICONDATA pid = new ICONDATA(iPref, ptb); if (pid) { if (!SendMessageCallback(pip->hwnd, WM_GETICON, lParam, 0, CTaskBand::IconAsyncProc, (ULONG_PTR)pid)) { delete pid; } } } return iRet; } int CTaskBand::GetSHILIconCB(CTaskBand* ptb, PICONCBPARAM pip, LPARAM lParam, int) { int iRet = I_IMAGENONE; TCHAR szIcon[MAX_PATH]; DWORD cb = sizeof(szIcon); HKEY hkeyApp; if (SUCCEEDED(AssocQueryKey(ASSOCF_OPEN_BYEXENAME | ASSOCF_VERIFY, ASSOCKEY_APP, pip->pszExeName, NULL, &hkeyApp))) { if (ERROR_SUCCESS == SHGetValue(hkeyApp, NULL, TEXT("TaskbarGroupIcon"), NULL, szIcon, &cb)) { int iIcon = PathParseIconLocation(szIcon); int iIndex = Shell_GetCachedImageIndex(szIcon, iIcon, 0); if (iIndex >= 0) { iRet = MAKELONG(iIndex, IL_SHIL); } } RegCloseKey(hkeyApp); } if (iRet == I_IMAGENONE) { int iIndex = Shell_GetCachedImageIndex(pip->pszExeName, 0, 0); if (iIndex >= 0) { iRet = MAKELONG(iIndex, IL_SHIL); } } return iRet; } int CTaskBand::GetDefaultIconCB(CTaskBand* ptb, PICONCBPARAM pip, LPARAM, int) { HICON hicon = LoadIcon(NULL, IDI_WINLOGO); return ptb->_AddIconToNormalImageList(hicon, pip->iImage); } int CTaskBand::GetClassIconCB(CTaskBand* ptb, PICONCBPARAM pip, LPARAM lParam, int) { if (IsWindow(pip->hwnd)) { HICON hicon = (HICON)GetClassLongPtr(pip->hwnd, (int)lParam); return ptb->_AddIconToNormalImageList(hicon, pip->iImage); } return I_IMAGENONE; } void CTaskBand::_UpdateItemIcon(int iIndex) { static const struct { PICONCALLBACK pfnCB; LPARAM lParam; } c_IconCallbacks[] = { { CTaskBand::GetIconCB, ICON_SMALL2 }, { CTaskBand::GetIconCB, ICON_SMALL }, { CTaskBand::GetIconCB, ICON_BIG }, { CTaskBand::GetClassIconCB, GCLP_HICONSM }, { CTaskBand::GetClassIconCB, GCLP_HICON }, { CTaskBand::GetSHILIconCB, 0, }, { CTaskBand::GetDefaultIconCB, 0, }, }; TBBUTTONINFO tbbi; PTASKITEM pti = _GetItem(iIndex, &tbbi); if (pti) { int iIndexGroup = _GetGroupIndex(iIndex); PTASKITEM ptiGroup = _GetItem(iIndexGroup); if (ptiGroup) { ICONCBPARAM ip; ip.hwnd = pti->hwnd; ip.pszExeName = ptiGroup->pszExeName; ip.iImage = tbbi.iImage; for (int i = 0; i < ARRAYSIZE(c_IconCallbacks); i++) { int iPref = (ARRAYSIZE(c_IconCallbacks) - i) + 1; if (iPref >= pti->iIconPref) { PTASKITEM ptiTemp = _GetItem(iIndex); if (ptiTemp == pti) { int iImage = c_IconCallbacks[i].pfnCB(this, &ip, c_IconCallbacks[i].lParam, iPref); if (iImage != I_IMAGENONE) { _SetItemImage(iIndex, iImage, iPref); break; } } } } } } } BOOL IsValidHICON(HICON hicon) { BOOL fIsValid = FALSE; if (hicon) { // Check validity of icon returned ICONINFO ii = {0}; fIsValid = GetIconInfo(hicon, &ii); if (ii.hbmMask) { DeleteObject(ii.hbmMask); } if (ii.hbmColor) { DeleteObject(ii.hbmColor); } } return fIsValid; } void CTaskBand::_MoveGroup(HWND hwnd, WCHAR* szNewExeName) { BOOL fRedraw = (BOOL)_tb.SendMessage(WM_SETREDRAW, FALSE, 0); int iIndexNewGroup = _GetGroupIndexFromExeName(szNewExeName); int iIndexOld = _FindIndexByHwnd(hwnd); int iIndexOldGroup = _GetGroupIndex(iIndexOld); if (iIndexNewGroup != iIndexOldGroup) { if (iIndexOld >= 0) { PTASKITEM pti = _GetItem(iIndexOld); if (iIndexNewGroup < 0) { PTASKITEM ptiGroup = new TASKITEM; if (ptiGroup) { ptiGroup->hwnd = NULL; ptiGroup->dwTimeLastClicked = 0; int cchExeName = lstrlen(szNewExeName) + 1; ptiGroup->pszExeName = new WCHAR[cchExeName]; if (ptiGroup->pszExeName) { StringCchCopy(ptiGroup->pszExeName, cchExeName, szNewExeName); iIndexNewGroup = _AddToTaskbar(ptiGroup, -1, FALSE, FALSE); if (iIndexNewGroup < 0) { delete[] ptiGroup->pszExeName; delete ptiGroup; } else if (iIndexNewGroup <= iIndexOldGroup) { iIndexOld++; iIndexOldGroup++; } } else { delete ptiGroup; } } } if (iIndexNewGroup >= 0) { int iIndexNew = _AddToTaskbar(pti, iIndexNewGroup + _GetGroupSize(iIndexNewGroup) + 1, _IsHidden(iIndexNewGroup), FALSE); if (iIndexNew >= 0) { _CheckButton(iIndexNew, pti->dwFlags & TIF_CHECKED); if (iIndexNew <= iIndexOldGroup) { iIndexOld++; iIndexOldGroup++; } // Copy the old icon to prevent re-getting the icon TBBUTTONINFO tbbiOld; _GetItem(iIndexOld, &tbbiOld); TBBUTTONINFO tbbiNew; _GetItem(iIndexNew, &tbbiNew); tbbiNew.iImage = tbbiOld.iImage; tbbiNew.dwMask = TBIF_BYINDEX | TBIF_IMAGE; _tb.SetButtonInfo(iIndexNew, &tbbiNew); tbbiOld.iImage = I_IMAGENONE; tbbiOld.dwMask = TBIF_BYINDEX | TBIF_IMAGE; _tb.SetButtonInfo(iIndexOld, &tbbiOld); _DeleteTaskItem(iIndexOld, FALSE); int iSize = _GetGroupSize(iIndexOldGroup); if (iSize == 0) { _DeleteTaskItem(iIndexOldGroup, TRUE); } else if (iSize == 1) { _Glom(iIndexOldGroup, FALSE); } } } } } _tb.SetRedraw(fRedraw); _CheckSize(); } void CTaskBand::_SetWindowIcon(HWND hwnd, HICON hicon, int iPref) { int iIndex = _FindIndexByHwnd(hwnd); if (iIndex >= 0) { TBBUTTONINFO tbbi; PTASKITEM pti = _GetItem(iIndex, &tbbi); if (iPref >= pti->iIconPref && IsValidHICON(hicon)) { int iImage = _AddIconToNormalImageList(hicon, tbbi.iImage); if (iImage >= 0) { _SetItemImage(iIndex, iImage, iPref); if (pti->hwnd) { int iIndexGroup = _GetGroupIndex(iIndex); PTASKITEM ptiGroup = _GetItem(iIndexGroup); HKEY hkeyApp; if (SUCCEEDED(AssocQueryKey(ASSOCF_OPEN_BYEXENAME | ASSOCF_VERIFY, ASSOCKEY_APP, ptiGroup->pszExeName, NULL, &hkeyApp))) { HKEY hkeyIcons; if (ERROR_SUCCESS == RegOpenKeyEx(hkeyApp, TEXT("TaskbarExceptionsIcons"), 0, KEY_READ, &hkeyIcons)) { int iKey = 0; WCHAR szKeyName[MAX_PATH]; DWORD cchKeyName = ARRAYSIZE(szKeyName); FILETIME ftBogus; while (ERROR_SUCCESS == RegEnumKeyEx(hkeyIcons, iKey, szKeyName, &cchKeyName, NULL, NULL, NULL, &ftBogus)) { HKEY hkeyNewGroup; if (ERROR_SUCCESS == RegOpenKeyEx(hkeyIcons, szKeyName, 0, KEY_READ, &hkeyNewGroup)) { WCHAR szIconName[MAX_PATH]; DWORD cbIconName = sizeof(szIconName); if (ERROR_SUCCESS == RegQueryValueEx(hkeyNewGroup, L"IconPath", NULL, NULL, (LPBYTE)szIconName, &cbIconName)) { HICON hiconDll = NULL; int iIconIndex = PathParseIconLocation(szIconName); ExtractIconEx(szIconName, iIconIndex, NULL, &hiconDll, 1); if (hiconDll) { if (SHAreIconsEqual(hiconDll, hicon)) { WCHAR szNewGroup[MAX_PATH]; DWORD cbNewGroup = sizeof(szNewGroup); if (ERROR_SUCCESS == RegQueryValueEx(hkeyNewGroup, L"NewExeName", NULL, NULL, (LPBYTE)szNewGroup, &cbNewGroup)) { for (int i = _tb.GetButtonCount() - 1; i >=0; i--) { PTASKITEM pti = _GetItem(i); if (!pti->hwnd) { WCHAR* pszGroupExe = PathFindFileName(pti->pszExeName); if (pszGroupExe && (lstrcmpi(pszGroupExe, szNewGroup) == 0)) { DWORD dwType; // Make it is an exe and that it exists if (GetBinaryType(pti->pszExeName, &dwType)) { _MoveGroup(hwnd, pti->pszExeName); } break; } } } } } DestroyIcon(hiconDll); } } RegCloseKey(hkeyNewGroup); } cchKeyName = ARRAYSIZE(szKeyName); iKey++; } RegCloseKey(hkeyIcons); } RegCloseKey(hkeyApp); } } } } } } void CTaskBand::_Glom(int iIndexGroup, BOOL fGlom) { BOOL fRedraw = (BOOL)_tb.SendMessage(WM_SETREDRAW, FALSE, 0); if ((!fGlom) && (iIndexGroup == _iIndexPopup)) { _FreePopupMenu(); } if (fGlom == _IsHidden(iIndexGroup)) { if (_fAnimate && _IsHorizontal()) { int iGroupSize = _GetGroupSize(iIndexGroup); if (!fGlom) { _HideGroup(iIndexGroup, FALSE); if (iGroupSize) { int iWidth = _GetItemWidth(iIndexGroup) / iGroupSize; iWidth = max(iWidth, 1); for(int i = iIndexGroup + iGroupSize; i > iIndexGroup; i--) { _SetItemWidth(i, iWidth); } } } if (!(fGlom && (_GetItem(iIndexGroup)->dwFlags & TIF_ISGLOMMING))) { _AnimateItems(iIndexGroup, !fGlom, TRUE); } } else { _HideGroup(iIndexGroup, fGlom); _CheckSize(); } } _tb.SetRedraw(fRedraw); } void CTaskBand::_HideGroup(int iIndexGroup, BOOL fHide) { int iGroupSize = _GetGroupSize(iIndexGroup); TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_STATE | TBIF_BYINDEX; // Glom button _tb.GetButtonInfo(iIndexGroup, &tbbi); tbbi.fsState = fHide ? (tbbi.fsState & ~TBSTATE_HIDDEN) : (tbbi.fsState | TBSTATE_HIDDEN); _tb.SetButtonInfo(iIndexGroup, &tbbi); // Group buttons for (int i = iIndexGroup + iGroupSize; i > iIndexGroup; i--) { _tb.GetButtonInfo(i, &tbbi); tbbi.fsState = fHide ? (tbbi.fsState | TBSTATE_HIDDEN) : (tbbi.fsState & ~TBSTATE_HIDDEN); _tb.SetButtonInfo(i, &tbbi); } } BOOL CTaskBand::_AutoGlomGroup(BOOL fGlom, int iOpenSlots) { int iIndex = -1; DWORD dwTimeLastClicked = 0; int iSize = 0; int i = 0; while (i < _tb.GetButtonCount()) { PTASKITEM pti = _GetItem(i); int iGroupSize = _GetGroupSize(i); // Don't mess with the blank group if ((pti->pszExeName && (pti->pszExeName[0] != 0)) && (fGlom || (!fGlom && ((_iGroupSize >= GLOM_SIZE) || (iGroupSize < iOpenSlots)))) && ((iGroupSize > 1) && (fGlom == _IsHidden(i)))) { BOOL fMatch; DWORD dwGroupTime = 0; switch (_iGroupSize) { case GLOM_OLDEST: dwGroupTime = _GetGroupAge(i); fMatch = (dwTimeLastClicked == 0) || (fGlom && (dwGroupTime < dwTimeLastClicked)) || (!fGlom && (dwGroupTime > dwTimeLastClicked)); break; case GLOM_BIGGEST: fMatch = (fGlom && (iGroupSize > iSize)) || (!fGlom && ((iGroupSize < iSize) || (iSize == 0))); break; default: fMatch = (fGlom && (iGroupSize >= _iGroupSize)) || (!fGlom && (iGroupSize < _iGroupSize)); break; } if (fMatch) { dwTimeLastClicked = dwGroupTime; iSize = iGroupSize; iIndex = i; } } i += iGroupSize + 1; } if ((iIndex != -1) && (fGlom || (!fGlom && (iSize < iOpenSlots)))) { _Glom(iIndex, fGlom); return TRUE; } return FALSE; } void CTaskBand::_GetItemTitle(int iIndex, WCHAR* pszTitle, int cchTitle, BOOL fCustom) { PTASKITEM pti = _GetItem(iIndex); if (pti->hwnd) { if (InternalGetWindowText(pti->hwnd, pszTitle, cchTitle)) { if (fCustom) { WCHAR szGrpText[MAX_PATH] = L" - "; int iIndexGroup = _GetGroupIndex(iIndex); _GetItemTitle(iIndexGroup, &szGrpText[3], MAX_PATH - 3, TRUE); int iLenGrp = lstrlen(szGrpText); int iLenWnd = lstrlen(pszTitle); if (iLenWnd > iLenGrp) { if (StrCmp(&pszTitle[iLenWnd - iLenGrp], szGrpText) == 0) { pszTitle[iLenWnd - iLenGrp] = 0; } } } } } else { if ((pti->pszExeName) && (pti->pszExeName[0] != 0)) { DWORD cchOut = cchTitle; AssocQueryString(ASSOCF_INIT_BYEXENAME | ASSOCF_VERIFY, ASSOCSTR_FRIENDLYAPPNAME, pti->pszExeName, NULL, pszTitle, &cchOut); } else { pszTitle[0] = 0; } } } int CTaskBand::_AddToTaskbar(PTASKITEM pti, int iIndexTaskbar, BOOL fVisible, BOOL fForceGetIcon) { ASSERT(IS_VALID_WRITE_PTR(pti, TASKITEM)); int iIndex = -1; TBBUTTON tbb = {0}; BOOL fRedraw = (BOOL)_tb.SendMessage(WM_SETREDRAW, FALSE, 0); if (fForceGetIcon) { tbb.iBitmap = I_IMAGENONE; } else { tbb.iBitmap = I_IMAGECALLBACK; } tbb.fsState = TBSTATE_ENABLED; if (!fVisible) tbb.fsState |= TBSTATE_HIDDEN; tbb.fsStyle = BTNS_CHECK | BTNS_NOPREFIX; if (!pti->hwnd) tbb.fsStyle |= BTNS_DROPDOWN | BTNS_WHOLEDROPDOWN; tbb.dwData = (DWORD_PTR)pti; tbb.idCommand = Toolbar_GetUniqueID(_tb); if (_tb.InsertButton(iIndexTaskbar, &tbb)) { iIndex = iIndexTaskbar; if (iIndex == -1) { iIndex = _tb.GetButtonCount() - 1; } if (fForceGetIcon) { _UpdateItemIcon(iIndex); } _UpdateItemText(iIndex); } _tb.SetRedraw(fRedraw); return (iIndex); } void CTaskBand::_DeleteTaskItem(int iIndex, BOOL fDeletePTI) { if (iIndex >= 0 && iIndex < _tb.GetButtonCount()) { TBBUTTONINFO tbbi; PTASKITEM pti = _GetItem(iIndex, &tbbi); _tb.DeleteButton(iIndex); _RemoveItemFromAnimationList(pti); if (fDeletePTI) { delete pti; } _RemoveImage(tbbi.iImage); } } void CTaskBand::_SetThreadPriority(int iPriority, DWORD dwWakeupTime) { if (_iOldPriority == INVALID_PRIORITY) { HANDLE hThread = GetCurrentThread(); int iCurPriority = GetThreadPriority(hThread); // Make sure we are actually changed the thread priority if (iCurPriority != iPriority) { _iOldPriority = iCurPriority; _iNewPriority = iPriority; if (dwWakeupTime) { // Make sure that we are guaranteed to wakeup, by having the desktop thread up our thread priority SendMessage(GetShellWindow(), CWM_TASKBARWAKEUP, GetCurrentThreadId(), MAKELONG(dwWakeupTime, _iOldPriority)); } SetThreadPriority(hThread, _iNewPriority); TraceMsg(TF_WARNING, "CTaskBand:: Thread Priority was changed from %d to %d", _iOldPriority, _iNewPriority); } } } void CTaskBand::_RestoreThreadPriority() { if (_iOldPriority != INVALID_PRIORITY) { HANDLE hThread = GetCurrentThread(); int iCurPriority = GetThreadPriority(hThread); // Make sure no one has changed our priority since that last time we did if (iCurPriority == _iNewPriority) { SetThreadPriority(hThread, _iOldPriority); SendMessage(GetShellWindow(), CWM_TASKBARWAKEUP, 0, 0); TraceMsg(TF_WARNING, "CTaskBand:: Thread Priority was restored from %d to %d", _iNewPriority, _iOldPriority); } _iOldPriority = INVALID_PRIORITY; _iNewPriority = INVALID_PRIORITY; } } void CTaskBand::_UpdateProgramCount() { DWORD dwDisposition; HKEY hKey; if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_CURRENT_USER, TEXT("SessionInformation"), 0, NULL, REG_OPTION_VOLATILE, KEY_SET_VALUE, NULL, &hKey, &dwDisposition)) { DWORD dwProgramCount = _ptray->CountOfRunningPrograms(); RegSetValueEx(hKey, TEXT("ProgramCount"), 0, REG_DWORD, reinterpret_cast(&dwProgramCount), sizeof(dwProgramCount)); RegCloseKey(hKey); } } int CTaskBand::_InsertItem(HWND hwndTask, PTASKITEM pti, BOOL fForceGetIcon) { _SetThreadPriority(THREAD_PRIORITY_BELOW_NORMAL, 5000); BOOL fRestoreThreadPriority = TRUE; PTASKITEM ptiGroup = NULL; WCHAR szExeName[MAX_PATH]; int iRet = _FindIndexByHwnd(hwndTask); int iIndexGroup = -1; if (iRet != -1) return iRet; SHExeNameFromHWND(hwndTask, szExeName, ARRAYSIZE(szExeName)); WCHAR* pszNoPath = PathFindFileName(szExeName); if (pszNoPath) { for (int i = 0; i < ARRAYSIZE(g_rgNoGlom); i++) { if (lstrcmpi(pszNoPath, g_rgNoGlom[i].szExeName) == 0) { StringCchPrintf(szExeName, ARRAYSIZE(szExeName), L"HWND%x", hwndTask); } } } // Initialize Taskbar entry, this entry will go into a group on the taskbar or onto the taskbar if (!pti) { pti = new TASKITEM; if (!pti) goto Failure; pti->hwnd = hwndTask; pti->dwTimeFirstOpened = pti->dwTimeLastClicked = GetTickCount(); } _AttachTaskShortcut(pti, szExeName); // Find the last taskbar entry with a given Exe Name if (_fGlom) { iIndexGroup = _GetGroupIndexFromExeName(szExeName); } if (iIndexGroup == -1) { ptiGroup = new TASKITEM; if (!ptiGroup) goto Failure; ptiGroup->hwnd = NULL; ptiGroup->dwTimeLastClicked = 0; int cchExeName = lstrlen(szExeName) + 1; ptiGroup->pszExeName = new WCHAR[cchExeName]; if (!ptiGroup->pszExeName) goto Failure; StringCchCopy(ptiGroup->pszExeName, cchExeName, szExeName); iRet = _AddToTaskbar(ptiGroup, -1, FALSE, fForceGetIcon); if (iRet == -1) goto Failure; int iRetLast = iRet; iRet = _AddToTaskbar(pti, -1, TRUE, fForceGetIcon); if (iRet == -1) { _DeleteTaskItem(iRetLast, TRUE); ptiGroup = NULL; } } else { iRet = _AddToTaskbar(pti, iIndexGroup + _GetGroupSize(iIndexGroup) + 1, _IsHidden(iIndexGroup), fForceGetIcon); } // If _AddToTaskbar fails (iRet == -1) don't try to add this item anywhere else if ((iIndexGroup == _iIndexPopup) && (iRet != -1)) { _AddItemToDropDown(iRet); } Failure: if (iRet == -1) { if (ptiGroup) { delete ptiGroup; } if (pti) { delete pti; } } else { if (_fAnimate && _IsHorizontal() && ToolBar_IsVisible(_tb, iRet) && !c_tray.IsTaskbarFading()) { _SetItemWidth(iRet, 1); // cannot be zero or toolbar will resize it. // If this operation is successful then _AsyncAnimateItems will raise thread priority // after the animation is complete fRestoreThreadPriority = !_AnimateItems(iRet, TRUE, FALSE); } } _UpdateProgramCount(); _CheckSize(); if (fRestoreThreadPriority) { _RestoreThreadPriority(); } return iRet; } //--------------------------------------------------------------------------- // Delete an item from the listbox but resize the buttons if needed. void CTaskBand::_DeleteItem(HWND hwnd, int iIndex) { if (iIndex == -1) iIndex = _FindIndexByHwnd(hwnd); if (iIndex != -1) { int iIndexGroup = _GetGroupIndex(iIndex); int iGroupSize = _GetGroupSize(iIndexGroup) - 1; if (iGroupSize == 0) { _FreePopupMenu(); _DeleteTaskItem(iIndex, TRUE); _DeleteTaskItem(iIndexGroup, TRUE); } else if ((iGroupSize == 1) || (_fGlom && (_iGroupSize >= GLOM_SIZE) && (iGroupSize < _iGroupSize))) { _FreePopupMenu(); _DeleteTaskItem(iIndex, TRUE); _Glom(iIndexGroup, FALSE); } else { if (iIndexGroup == _iIndexPopup) _RemoveItemFromDropDown(iIndex); _DeleteTaskItem(iIndex, TRUE); } _CheckSize(); // Update the flag that says, "There is an item flashing." _UpdateFlashingFlag(); _UpdateProgramCount(); } } //--------------------------------------------------------------------------- // Adds the given window to the task list. // Returns TRUE/FALSE depending on whether the window was actually added. // NB No check is made to see if it's already in the list. BOOL CTaskBand::_AddWindow(HWND hwnd) { if (_IsWindowNormal(hwnd)) { return _InsertItem(hwnd); } return FALSE; } BOOL CTaskBand::_CheckButton(int iIndex, BOOL fCheck) { TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_STATE | TBIF_BYINDEX; _tb.GetButtonInfo(iIndex, &tbbi); if (fCheck) tbbi.fsState |= TBSTATE_CHECKED; else tbbi.fsState &= ~TBSTATE_CHECKED; return _tb.SetButtonInfo(iIndex, &tbbi); } BOOL CTaskBand::_IsButtonChecked(int iIndex) { TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_STATE | TBIF_BYINDEX; _tb.GetButtonInfo(iIndex, &tbbi); return BOOLIFY(tbbi.fsState & TBSTATE_CHECKED); } int CTaskBand::_GetCurSel() { for (int i = _tb.GetButtonCount() - 1; i >= 0; i--) { if (_IsButtonChecked(i)) { return i; } } return -1; } void CTaskBand::_SetCurSel(int iIndex, BOOL fIgnoreCtrlKey) { // Under certain very rare circumstances someone will call us with an invalid index // Case #1: CallbackSM is called with a no longer valid uID with maps to a bogus index // Case #2: _SelectWindow creates a new button, but before calling this function another button is removed causing // the index of the new button to be invalid if (iIndex == -1 || (iIndex >= 0 && iIndex < _tb.GetButtonCount())) { int iIndexGroup = (iIndex == -1) ? -1 : _GetGroupIndex(iIndex); BOOL fControlKey = (GetKeyState(VK_CONTROL) < 0) && (!fIgnoreCtrlKey); if (fControlKey) { if (GetForegroundWindow() != (HWND)_tb) { _fIgnoreTaskbarActivate = TRUE; _tb.SetFocus(); } } for (int i = _tb.GetButtonCount() - 1; i >= 0; i--) { PTASKITEM pti = _GetItem(i); if (fControlKey) { if ((i == iIndex) || (i == iIndexGroup)) { pti->dwFlags = (pti->dwFlags & TIF_CHECKED) ? (pti->dwFlags & (~TIF_CHECKED)) : pti->dwFlags | TIF_CHECKED; } } else { pti->dwFlags = ((i == iIndex) || (i == iIndexGroup)) ? pti->dwFlags | TIF_CHECKED : (pti->dwFlags & (~TIF_CHECKED)); } _CheckButton(i, pti->dwFlags & TIF_CHECKED); } } } //--------------------------------------------------------------------------- // If the given window is in the task list then it is selected. // If it's not in the list then it is added. int CTaskBand::_SelectWindow(HWND hwnd) { int i; // Initialize to zero for the empty case int iCurSel; // Are there any items? // Some item has the focus, is it selected? iCurSel = _GetCurSel(); i = -1; // We aren't highlighting the correct task. Find it. if (IsWindow(hwnd)) { i = _FindIndexByHwnd(hwnd); if ( i == -1 ) { // Didn't find it - better add it now. i = _InsertItem(hwnd); } else if (i == iCurSel) { return i; // the current one is already selected } } // passing -1 is ok _SetCurSel(i, TRUE); if (i != -1) { _ScrollIntoView(i); } return i; } //--------------------------------------------------------------------------- // Set the focus to the given window // If fAutomin is set the old task will be re-minimising if it was restored // during the last switch_to. void CTaskBand::_SwitchToWindow(HWND hwnd) { // use GetLastActivePopup (if it's a visible window) so we don't change // what child had focus all the time HWND hwndLastActive = GetLastActivePopup(hwnd); if ((hwndLastActive) && (IsWindowVisible(hwndLastActive))) hwnd = hwndLastActive; int iIndex = _FindIndexByHwnd(hwnd); if (iIndex != -1) { PTASKITEM pti = _GetItem(iIndex); if (pti) { pti->dwTimeLastClicked = GetTickCount(); } } SwitchToThisWindow(hwnd, TRUE); } int CTaskBand::_GetSelectedItems(CDSA* pdsa) { int cSelected = 0; for (int i = _tb.GetButtonCount() - 1; i >= 0; i--) { TBBUTTONINFO tbbi; PTASKITEM pti = _GetItem(i, &tbbi); if ((tbbi.fsState & TBSTATE_CHECKED) && !(tbbi.fsState & TBSTATE_HIDDEN)) { if (pti->hwnd) { cSelected++; if (pdsa) pdsa->AppendItem(&pti); } else { cSelected += _GetGroupItems(i, pdsa); } } } return cSelected; } void CTaskBand::_OnGroupCommand(int iRet, CDSA* pdsa) { // turn off animations during this ANIMATIONINFO ami; ami.cbSize = sizeof(ami); SystemParametersInfo(SPI_GETANIMATION, sizeof(ami), &ami, FALSE); LONG iAnimate = ami.iMinAnimate; ami.iMinAnimate = FALSE; SystemParametersInfo(SPI_SETANIMATION, sizeof(ami), &ami, FALSE); switch (iRet) { case IDM_CASCADE: case IDM_VERTTILE: case IDM_HORIZTILE: { int cbHWND = pdsa->GetItemCount(); HWND* prgHWND = new HWND[cbHWND]; if (prgHWND) { for (int i = 0; i < cbHWND; i++) { PTASKITEM pti; pdsa->GetItem(i, &pti); prgHWND[i] = pti->hwnd; if (IsIconic(pti->hwnd)) { // this needs to by synchronous with the arrange ShowWindow(prgHWND[i], SW_RESTORE); } BringWindowToTop(pti->hwnd); } if (iRet == IDM_CASCADE) { CascadeWindows(GetDesktopWindow(), MDITILE_ZORDER, NULL, cbHWND, prgHWND); } else { UINT wHow = (iRet == IDM_VERTTILE ? MDITILE_VERTICAL : MDITILE_HORIZONTAL); TileWindows(GetDesktopWindow(), wHow, NULL, cbHWND, prgHWND); } SetForegroundWindow(prgHWND[cbHWND - 1]); delete[] prgHWND; } } break; case IDM_CLOSE: case IDM_MINIMIZE: { int idCmd; switch (iRet) { case IDM_MINIMIZE: idCmd = SC_MINIMIZE; break; case IDM_CLOSE: idCmd = SC_CLOSE; break; } for (int i = pdsa->GetItemCount() - 1; i >= 0; i--) { PTASKITEM pti; pdsa->GetItem(i, &pti); PostMessage(pti->hwnd, WM_SYSCOMMAND, idCmd, 0L); } _SetCurSel(-1, TRUE); } break; } // restore animations state ami.iMinAnimate = iAnimate; SystemParametersInfo(SPI_SETANIMATION, sizeof(ami), &ami, FALSE); } int CTaskBand::_GetGroupItems(int iIndexGroup, CDSA* pdsa) { int iGroupSize = _GetGroupSize(iIndexGroup); if (pdsa) { for (int i = iIndexGroup + 1; i < iIndexGroup + iGroupSize + 1; i++) { PTASKITEM ptiTemp = _GetItem(i); pdsa->AppendItem(&ptiTemp); } } return iGroupSize; } void CTaskBand::_SysMenuForItem(int i, int x, int y) { _iSysMenuCount++; CDSA dsa; dsa.Create(4); PTASKITEM pti = _GetItem(i); int cSelectedItems = _GetSelectedItems(&dsa); if (((cSelectedItems > 1) && _IsButtonChecked(i)) || !pti->hwnd) { HMENU hmenu = LoadMenuPopup(MAKEINTRESOURCE(MENU_GROUPCONTEXT)); if (cSelectedItems <= 1) { dsa.Destroy(); dsa.Create(4); _GetGroupItems(i, &dsa); } // OFFICESDI: Is this an office app doing its taskbar fakery BOOL fMinimize = FALSE; BOOL fOfficeApp = FALSE; for (int iIndex = (int)(dsa.GetItemCount()) - 1; iIndex >= 0; iIndex--) { PTASKITEM pti; dsa.GetItem(iIndex, &pti); if (pti->dwFlags & TIF_EVERACTIVEALT) { fOfficeApp = TRUE; } if (_ShouldMinimize(pti->hwnd)) fMinimize = TRUE; } // OFFICESDI: If it is an office app disable pretty much everything if (fOfficeApp) { EnableMenuItem(hmenu, IDM_CLOSE, MF_DISABLED | MF_GRAYED | MF_BYCOMMAND); EnableMenuItem(hmenu, IDM_CASCADE, MF_DISABLED | MF_GRAYED | MF_BYCOMMAND); EnableMenuItem(hmenu, IDM_HORIZTILE, MF_DISABLED | MF_GRAYED | MF_BYCOMMAND); EnableMenuItem(hmenu, IDM_VERTTILE, MF_DISABLED | MF_GRAYED | MF_BYCOMMAND); EnableMenuItem(hmenu, IDM_MINIMIZE, MF_DISABLED | MF_GRAYED | MF_BYCOMMAND); } else if (!fMinimize) { EnableMenuItem(hmenu, IDM_MINIMIZE, MF_DISABLED | MF_GRAYED | MF_BYCOMMAND); } CToolTipCtrl ttc = _tb.GetToolTips(); ttc.Activate(FALSE); int iRet = TrackPopupMenuEx(hmenu, TPM_RETURNCMD | TPM_RIGHTBUTTON, x, y, _tb, NULL); ttc.Activate(TRUE); _OnGroupCommand(iRet, &dsa); } else { LPARAM lParam = MAKELPARAM(x, y); _RestoreWindow(pti->hwnd, pti->dwFlags); _SelectWindow(pti->hwnd); PostMessage(_hwnd, TBC_POSTEDRCLICK, (WPARAM)pti->hwnd, (LPARAM)lParam); } dsa.Destroy(); _iSysMenuCount--; } void CALLBACK CTaskBand::FakeSystemMenuCB(HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lres) { CTaskBand* ptasks = (CTaskBand*)dwData; KillTimer(ptasks->_hwnd, IDT_SYSMENU); if (uMsg == WM_GETICON) { SendMessageCallback(hwnd, WM_SYSMENU, 0, ptasks->_dwPos, (SENDASYNCPROC)CTaskBand::FakeSystemMenuCB, (ULONG_PTR)ptasks); } else { // // Since we fake system menu's sometimes, we can come through here // 1 or 2 times per system menu request (once for the real one and // once for the fake one). Only decrement it down to 0. Don't go neg. // if (ptasks->_iSysMenuCount) // Decrement it if any outstanding... ptasks->_iSysMenuCount--; ptasks->_dwPos = 0; // Indicates that we aren't doing a menu now if (ptasks->_iSysMenuCount <= 0) { CToolTipCtrl ttc = ptasks->_tb.GetToolTips(); ttc.Activate(TRUE); } } } HWND CTaskBand::_CreateFakeWindow(HWND hwndOwner) { WNDCLASSEX wc; if (!GetClassInfoEx(hinstCabinet, TEXT("_ExplorerFakeWindow"), &wc)) { ZeroMemory(&wc, sizeof(wc)); wc.cbSize = sizeof(wc); wc.lpfnWndProc = DefWindowProc; wc.hInstance = hinstCabinet; wc.lpszClassName = TEXT("_ExplorerFakeWindow"); RegisterClassEx(&wc); } return CreateWindow(TEXT("_ExplorerFakeWindow"), NULL, WS_POPUP | WS_SYSMENU, 0, 0, 0, 0, hwndOwner, NULL, hinstCabinet, NULL); } void CTaskBand::_HandleSysMenuTimeout() { HWND hwndTask = _hwndSysMenu; DWORD dwPos = _dwPos; HWND hwndFake = NULL; KillTimer(_hwnd, IDT_SYSMENU); HMENU hPopup = GetSystemMenu(hwndTask, FALSE); // This window doesn't have the system menu. Since this window // is hung, let's fake one so the user can still close it. if (hPopup == NULL) { if ((hwndFake = _CreateFakeWindow(_hwnd)) != NULL) { hPopup = GetSystemMenu(hwndFake, FALSE); } } if (hPopup) { // Disable everything on the popup menu _except_ close int cItems = GetMenuItemCount(hPopup); BOOL fMinimize = _ShouldMinimize(hwndTask); for (int iItem = 0; iItem < cItems; iItem++) { UINT ID = GetMenuItemID(hPopup, iItem); // Leave the minimize item as is. NT allows // hung-window minimization. if (ID == SC_MINIMIZE && fMinimize) { continue; } if (ID != SC_CLOSE) { EnableMenuItem(hPopup, iItem, MF_BYPOSITION | MF_GRAYED); } } // workaround for user bug, we must be the foreground window SetForegroundWindow(_hwnd); ::SetFocus(_hwnd); if (SC_CLOSE == TrackPopupMenu(hPopup, TPM_RIGHTBUTTON | TPM_RETURNCMD, GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos), 0, _hwnd, NULL)) { EndTask(hwndTask, NULL, NULL); } } // Destroy the fake window if (hwndFake != NULL) { DestroyWindow(hwndFake); } // Turn back on tooltips FakeSystemMenuCB(hwndTask, WM_SYSMENU, (ULONG_PTR)this, 0); } void CTaskBand::_HandleSysMenu(HWND hwnd) { // // At this point, USER32 just told us that the app is now about to bring // up its own system menu. We can therefore put away our fake system // menu. // DefWindowProc(_hwnd, WM_CANCELMODE, 0, 0); // Close menu KillTimer(_hwnd, IDT_SYSMENU); } void CTaskBand::_FakeSystemMenu(HWND hwndTask, DWORD dwPos) { if (_iSysMenuCount <= 0) { CToolTipCtrl ttc = _tb.GetToolTips(); ttc.Activate(FALSE); } // HACKHACK: sleep to give time to switch to them. (user needs this... ) Sleep(20); DWORD dwTimeout = TIMEOUT_SYSMENU; // // ** Advanced System Menu functionality ** // // If the app doesn't put up its system menu within a reasonable timeout, // then we popup a fake menu for it anyway. Suppport for this is required // in USER32 (basically it needs to tell us when to turn off our timeout // timer). // // If the user-double right-clicks on the task bar, they get a really // short timeout. If the app is already hung, then they get a really // short timeout. Otherwise, they get the relatively long timeout. // if (_dwPos != 0) // 2nd right-click (on a double-right click) dwTimeout = TIMEOUT_SYSMENU_HUNG; // // We check to see if the app in question is hung, and if so, simulate // speed up the timeout process. It will happen soon enough. // _hwndSysMenu = hwndTask; _dwPos = dwPos; _iSysMenuCount++; PTASKITEM pti = NULL; int iIndex = _FindIndexByHwnd(hwndTask); if (iIndex != -1) { pti = _GetItem(iIndex); } if (IsHungAppWindow(hwndTask) || (pti && pti->fHungApp)) { _HandleSysMenuTimeout(); } else { SetTimer(_hwnd, IDT_SYSMENU, dwTimeout, NULL); if (!SendMessageCallback(hwndTask, WM_GETICON, 0, ICON_SMALL2, (SENDASYNCPROC)FakeSystemMenuCB, (ULONG_PTR)this)) { _HandleSysMenuTimeout(); } } } BOOL CTaskBand::_ContextMenu(DWORD dwPos) { int i, x, y; if (dwPos != (DWORD)-1) { x = GET_X_LPARAM(dwPos); y = GET_Y_LPARAM(dwPos); POINT pt = {x, y}; _tb.ScreenToClient(&pt); i = _tb.HitTest(&pt); } else { RECT rc; i = _tb.GetHotItem(); _tb.GetItemRect(i, &rc); _tb.ClientToScreen((POINT*)&rc); x = rc.left; y = rc.top; } if ((i >= 0) && (i < _tb.GetButtonCount())) { if (!_IsButtonChecked(i)) { _SetCurSel(i, FALSE); } _SysMenuForItem(i, x, y); } return (i >= 0); } void CTaskBand::_HandleCommand(WORD wCmd, WORD wID, HWND hwnd) { if (hwnd != _tb) { switch (wCmd) { case SC_CLOSE: { BOOL fForce = (GetKeyState(VK_CONTROL) < 0) ? TRUE : FALSE; EndTask(_hwndSysMenu, FALSE , fForce); } break; case SC_MINIMIZE: ShowWindow(_hwndSysMenu, SW_FORCEMINIMIZE); break; } } else if (wCmd == BN_CLICKED) { int iIndex = _tb.CommandToIndex(wID); if (GetKeyState(VK_CONTROL) < 0) { _SetCurSel(iIndex, FALSE); } else { PTASKITEM pti = _GetItem(iIndex); if (pti->hwnd) { _OnButtonPressed(iIndex, pti, FALSE); } else { if (_iIndexPopup == -1) { _SetCurSel(iIndex, FALSE); _HandleDropDown(iIndex); } } } } } BOOL _IsChineseLanguage() { WORD wLang = GetUserDefaultLangID(); return (PRIMARYLANGID(wLang) == LANG_CHINESE && ((SUBLANGID(wLang) == SUBLANG_CHINESE_TRADITIONAL) || (SUBLANGID(wLang) == SUBLANG_CHINESE_SIMPLIFIED))); } void CTaskBand::_DrawNumber(HDC hdc, int iValue, BOOL fCalcRect, LPRECT prc) { DWORD uiStyle = DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX | DT_CENTER; WCHAR szCount[14]; _itow(iValue, szCount, 10); if (fCalcRect) { StringCchCat(szCount, ARRAYSIZE(szCount), L"0"); } uiStyle |= fCalcRect ? DT_CALCRECT : 0; if (_hTheme) { if (fCalcRect) { GetThemeTextExtent(_hTheme, hdc, TDP_GROUPCOUNT, 0, szCount, -1, uiStyle, NULL, prc); } else { DrawThemeText(_hTheme, hdc, TDP_GROUPCOUNT, 0, szCount, -1, uiStyle, 0, prc); } } else { HFONT hfont = SelectFont(hdc, _hfontCapBold); SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT)); SetBkMode(hdc, TRANSPARENT); DrawText(hdc, (LPTSTR)szCount, -1, prc, uiStyle); SelectFont(hdc, hfont); } } LRESULT CTaskBand::_HandleCustomDraw(LPNMTBCUSTOMDRAW ptbcd, PTASKITEM pti) { if (!pti) { pti = (PTASKITEM)ptbcd->nmcd.lItemlParam; } LRESULT lres = CDRF_DODEFAULT; switch (ptbcd->nmcd.dwDrawStage) { case CDDS_PREPAINT: lres = CDRF_NOTIFYITEMDRAW; break; case CDDS_ITEMPREPAINT: { if (ptbcd->nmcd.uItemState & CDIS_CHECKED) { // set bold text, unless on chinese language system (where bold text is illegible) if (!_IsChineseLanguage()) { _hfontSave = SelectFont(ptbcd->nmcd.hdc, _hfontCapBold); lres |= CDRF_NOTIFYPOSTPAINT | CDRF_NEWFONT; } } if (pti->dwFlags & TIF_RENDERFLASHED) { if (_hTheme) { DrawThemeBackground(_hTheme, ptbcd->nmcd.hdc, (ptbcd->nmcd.hdr.hwndFrom == _tb) ? TDP_FLASHBUTTON : TDP_FLASHBUTTONGROUPMENU, 0, &(ptbcd->nmcd.rc), 0); lres |= TBCDRF_NOBACKGROUND; } else { // set blue background ptbcd->clrHighlightHotTrack = GetSysColor(COLOR_HIGHLIGHT); ptbcd->clrBtnFace = GetSysColor(COLOR_HIGHLIGHT); ptbcd->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT); if (!(ptbcd->nmcd.uItemState & CDIS_HOT)) { ptbcd->nmcd.uItemState |= CDIS_HOT; lres |= TBCDRF_NOEDGES; } lres |= TBCDRF_HILITEHOTTRACK; } } if (pti->dwFlags & TIF_TRANSPARENT) { lres = CDRF_SKIPDEFAULT; } if (!pti->hwnd) { lres |= CDRF_NOTIFYPOSTPAINT; RECT rc; int iIndex = _tb.CommandToIndex((int)ptbcd->nmcd.dwItemSpec); _DrawNumber(ptbcd->nmcd.hdc, _GetGroupSize(iIndex), TRUE, &rc); ptbcd->iListGap = RECTWIDTH(rc); } } break; case CDDS_ITEMPOSTPAINT: { if (!pti->hwnd) { int iIndex = _tb.CommandToIndex((int)ptbcd->nmcd.dwItemSpec); if (ptbcd->nmcd.rc.right >= ptbcd->rcText.left) { RECT rc = ptbcd->rcText; rc.right = rc.left; rc.left -= ptbcd->iListGap; _DrawNumber(ptbcd->nmcd.hdc, _GetGroupSize(iIndex), FALSE, &rc); } } if (ptbcd->nmcd.uItemState & CDIS_CHECKED) { // restore font ASSERT(!_IsChineseLanguage()); SelectFont(ptbcd->nmcd.hdc, _hfontSave); } } break; } return lres; } void CTaskBand::_RemoveImage(int iImage) { if (iImage >= 0 && HIWORD(iImage) == IL_NORMAL) { CImageList il = CImageList(_tb.GetImageList()); if (il) { BOOL fRedraw = (BOOL)_tb.SendMessage(WM_SETREDRAW, FALSE, 0); il.Remove(iImage); // Removing image bumps all subsequent indices down by 1. Iterate // through the buttons and patch up their indices as necessary. TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_BYINDEX | TBIF_IMAGE; for (int i = _tb.GetButtonCount() - 1; i >= 0; i--) { _tb.GetButtonInfo(i, &tbbi); if (tbbi.iImage > iImage && HIWORD(tbbi.iImage) == IL_NORMAL) { --tbbi.iImage; _tb.SetButtonInfo(i, &tbbi); } } _tb.SetRedraw(fRedraw); } } } void CTaskBand::_OnButtonPressed(int iIndex, PTASKITEM pti, BOOL fForceRestore) { ASSERT(pti); if (iIndex == _iIndexActiveAtLDown) { if (pti->dwFlags & TIF_EVERACTIVEALT) { PostMessage(pti->hwnd, WM_SYSCOMMAND, SC_RESTORE, -1); _SetCurSel(-1, FALSE); } else if (IsIconic(pti->hwnd) || fForceRestore) { if (pti->hwnd == GetForegroundWindow()) { ShowWindowAsync(pti->hwnd, SW_RESTORE); } else { _SwitchToItem(iIndex, pti->hwnd, TRUE); } } else if (_ShouldMinimize(pti->hwnd)) { SHAllowSetForegroundWindow(pti->hwnd); PostMessage(pti->hwnd, WM_SYSCOMMAND, SC_MINIMIZE, 0); _SetCurSel(-1, FALSE); } } else { _SwitchToItem(iIndex, pti->hwnd, TRUE); } } void CTaskBand::_GetDispInfo(LPNMTBDISPINFO lptbdi) { if (lptbdi->dwMask & TBNF_IMAGE) { int iIndex = _tb.CommandToIndex(lptbdi->idCommand); _UpdateItemIcon(iIndex); TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_BYINDEX | TBIF_IMAGE; _tb.GetButtonInfo(iIndex, &tbbi); lptbdi->iImage = tbbi.iImage; lptbdi->dwMask |= TBNF_DI_SETITEM; } } LRESULT CTaskBand::_HandleNotify(LPNMHDR lpnm) { switch (lpnm->code) { case NM_LDOWN: { int iIndex = _tb.CommandToIndex(((LPNMTOOLBAR)lpnm)->iItem); PTASKITEM pti = _GetItem(iIndex); if (pti && pti->hwnd) { _iIndexActiveAtLDown = _GetCurSel(); } } break; case NM_KEYDOWN: { LPNMKEY pnmk = (LPNMKEY)lpnm; switch (pnmk->nVKey) { case VK_SPACE: case VK_RETURN: // need to toggle checked state, toolbar doesn't do it for us { int iItem = _tb.GetHotItem(); if (iItem >= 0) { TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_BYINDEX | TBIF_STATE; _tb.GetButtonInfo(iItem, &tbbi); tbbi.fsState ^= TBSTATE_CHECKED; _tb.SetButtonInfo(iItem, &tbbi); PTASKITEM pti = _GetItem(iItem); _OnButtonPressed(iItem, pti, FALSE); } } return TRUE; } } break; case TBN_DELETINGBUTTON: break; case TBN_HOTITEMCHANGE: if (_fDenyHotItemChange) { return 1; } else { LPNMTBHOTITEM pnmhot = (LPNMTBHOTITEM)lpnm; if (pnmhot->dwFlags & HICF_ARROWKEYS) { // If this change came from a mouse then the hot item is already in view _ScrollIntoView(_tb.CommandToIndex(pnmhot->idNew)); } } break; case TBN_DROPDOWN: { int iIndex = _tb.CommandToIndex(((LPNMTOOLBAR)lpnm)->iItem); int iCurIndex = _GetCurSel(); _iIndexActiveAtLDown = iCurIndex; if ((iCurIndex == -1) || (_GetGroupIndex(iCurIndex) != iIndex) || (GetKeyState(VK_CONTROL) < 0)) { _SetCurSel(iIndex, FALSE); } if (!(GetKeyState(VK_CONTROL) < 0)) { _SetCurSel(iIndex, FALSE); _HandleDropDown(iIndex); } } break; case TBN_GETDISPINFO: { LPNMTBDISPINFO lptbdi = (LPNMTBDISPINFO)lpnm; _GetDispInfo(lptbdi); } break; case NM_CUSTOMDRAW: return _HandleCustomDraw((LPNMTBCUSTOMDRAW)lpnm); case TTN_NEEDTEXT: { int iIndex = _tb.CommandToIndex((int)lpnm->idFrom); LPTOOLTIPTEXT pttt = (LPTOOLTIPTEXT)lpnm; int cchLen = 0; PTASKITEM pti = _GetItem(iIndex); if (pti && !pti->hwnd) { StringCchPrintf(pttt->szText, ARRAYSIZE(pttt->szText), L"(%d) ", _GetGroupSize(iIndex)); cchLen = lstrlen(pttt->szText); } _GetItemTitle(iIndex, &(pttt->szText[cchLen]), ARRAYSIZE(pttt->szText) - cchLen, FALSE); } break; case NM_THEMECHANGED: { _VerifyButtonHeight(); } break; } return 0; } void CTaskBand::_SwitchToItem(int iItem, HWND hwnd, BOOL fIgnoreCtrlKey) { if (_IsWindowNormal(hwnd)) { _RaiseDesktop(FALSE); if (_pmpPopup) _pmpPopup->OnSelect(MPOS_FULLCANCEL); _SetCurSel(iItem, fIgnoreCtrlKey); if (!(GetKeyState(VK_CONTROL) < 0) || fIgnoreCtrlKey) { _SwitchToWindow(hwnd); } } else if (!hwnd) { // I know what you are thinking, why would we ever get a NM_CLICK message for a dropdown button. // Ok, sit back and enjoy // 1) Click on a group button // 2) All window messages are funnelled through the menuband currently being used for the group menu // 3) User clicks on another group button // 4) The WM_LBUTTONDOWN message is captured and eaten by menuband, then menuband dismisses itself causing a TBC_FREEPOPUPMENU // 5) Then the toolbar button for the other group button gets an WM_LBUTTONUP message // 6) Guess what, dropdown button notifications are sent during WM_LBUTTONDOWN not UP // 7) Thus we don't get an TBN_DROPDOWN we get an NM_CLICK // 8) We need to make sure the user didn't click on the same group button as before // 9) However, the previous group menu has been dismissed, so I create _iIndexLastPopup which persists after a group menu is dismissed if (iItem != _iIndexLastPopup) { _SetCurSel(iItem, fIgnoreCtrlKey); if (!(GetKeyState(VK_CONTROL) < 0) || fIgnoreCtrlKey) { _HandleDropDown(iItem); } } } // NOTE: HWND_TOPMOST is used to indicate that the deleted button // is being animated. This allows the button to stay around after // its real hwnd becomes invalid else if (hwnd != HWND_TOPMOST) { // Window went away? _DeleteItem(hwnd); _SetCurSel(-1, fIgnoreCtrlKey); } } BOOL WINAPI CTaskBand::BuildEnumProc(HWND hwnd, LPARAM lParam) { CTaskBand* ptasks = (CTaskBand*)lParam; if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !::GetWindow(hwnd, GW_OWNER) && (!(GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW))) { ptasks->_AddWindow(hwnd); } return TRUE; } //--------------------------------------------------------------------------- // Work around a toolbar bug where it goes wacko if you press both mouse // buttons. The reason is that the second mouse button doing down tries // to reassert capture. This causes the toolbar to receive WM_CAPTURECHANGED // with its own hwnd as lParam. Toolbar doesn't realize that it's being told // that it is stealing capture from itself and thinks somebody else is // trying to steal capture, so it posts a message to itself to clean up. // The posted message arrives, and toolbar cleans up the capture, thinking // it's cleaning up the old capture that it lost, but in fact it's cleaning // up the NEW capture it just finished setting! // // So filter out WM_CAPTURECHANGED messages that are effectively NOPs. // LRESULT CALLBACK s_FilterCaptureSubclassProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { switch (uMsg) { case WM_CAPTURECHANGED: if (hwnd == (HWND)lParam) { // Don't let toolbar be fooled into cleaning up capture // when it shouldn't. return 0; } break; case WM_NCDESTROY: RemoveWindowSubclass(hwnd, s_FilterCaptureSubclassProc, uIdSubclass); break; } return DefSubclassProc(hwnd, uMsg, wParam, lParam); } //--------------------------------------------------------------------------- LRESULT CTaskBand::_HandleCreate() { ASSERT(_hwnd); _uCDHardError = RegisterWindowMessage( TEXT(COPYDATA_HARDERROR) ); RegisterDragDrop(_hwnd, this); _tb.Create(_hwnd, NULL, NULL, WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | CCS_NODIVIDER | TBSTYLE_LIST | TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | CCS_NORESIZE | TBSTYLE_TRANSPARENT); if (_tb) { SendMessage(_tb, TB_ADDSTRING, (WPARAM)hinstCabinet, (LPARAM)IDS_BOGUSLABELS); _OpenTheme(); SendMessage(_tb, TB_SETWINDOWTHEME, 0, (LPARAM)(_IsHorizontal() ? c_wzTaskBandTheme : c_wzTaskBandThemeVert)); SetWindowSubclass(_tb, s_FilterCaptureSubclassProc, 0, 0); _tb.SetButtonStructSize(); // initial size SIZE size = {0, 0}; _tb.SetButtonSize(size); _tb.SetExtendedStyle( TBSTYLE_EX_TRANSPARENTDEADAREA | TBSTYLE_EX_FIXEDDROPDOWN | TBSTYLE_EX_DOUBLEBUFFER | TBSTYLE_EX_TOOLTIPSEXCLUDETOOLBAR); // version info _tb.SendMessage(CCM_SETVERSION, COMCTL32_VERSION, 0); _CreateTBImageLists(); HWND hwndTT = _tb.GetToolTips(); if (hwndTT) { SHSetWindowBits(hwndTT, GWL_STYLE, TTS_ALWAYSTIP | TTS_NOPREFIX, TTS_ALWAYSTIP | TTS_NOPREFIX); } // set shell hook WM_ShellHook = RegisterWindowMessage(TEXT("SHELLHOOK")); RegisterShellHook(_hwnd, 3); // 3 = magic flag // force getting of font, calc of metrics _HandleWinIniChange(0, 0, TRUE); // populate the toolbar EnumWindows(BuildEnumProc, (LPARAM)this); SHChangeNotifyEntry fsne; fsne.fRecursive = FALSE; fsne.pidl = NULL; _uShortcutInvokeNotify = SHChangeNotifyRegister(_hwnd, SHCNRF_NewDelivery | SHCNRF_ShellLevel, SHCNE_ASSOCCHANGED | SHCNE_EXTENDED_EVENT | SHCNE_UPDATEIMAGE, TBC_CHANGENOTIFY, 1, &fsne); // set window text to give accessibility apps something to read TCHAR szTitle[80]; LoadString(hinstCabinet, IDS_TASKBANDTITLE, szTitle, ARRAYSIZE(szTitle)); SetWindowText(_hwnd, szTitle); SetWindowText(_tb, szTitle); return 0; // success } // Failure. return -1; } void CTaskBand::_FreePopupMenu() { _iIndexPopup = -1; ATOMICRELEASE(_psmPopup); if (_pmpPopup) { IUnknown_SetSite(_pmpPopup, NULL); _pmpPopup->OnSelect(MPOS_FULLCANCEL); } ATOMICRELEASE(_pmpPopup); ATOMICRELEASE(_pmbPopup); SendMessage(v_hwndTray, TM_SETPUMPHOOK, NULL, NULL); _menuPopup.Detach(); } HRESULT CTaskBand::_CreatePopupMenu(POINTL* ppt, RECTL* prcl) { HRESULT hr = E_FAIL; CToolTipCtrl ttc = _tb.GetToolTips(); ttc.Activate(FALSE); SetActiveWindow(v_hwndTray); CTaskBandSMC* ptbc = new CTaskBandSMC(this); if (ptbc) { if (SUCCEEDED(CoCreateInstance(CLSID_MenuBand, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellMenu2, &_psmPopup))) && SUCCEEDED(_psmPopup->Initialize(ptbc, 0, 0, SMINIT_CUSTOMDRAW | SMINIT_VERTICAL | SMINIT_TOPLEVEL | SMINIT_USEMESSAGEFILTER)) && SUCCEEDED(CoCreateInstance(CLSID_MenuDeskBar, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IMenuPopup, &_pmpPopup))) && SUCCEEDED(_psmPopup->SetMenu(_menuPopup, _hwnd, SMSET_USEPAGER | SMSET_NOPREFIX)) && SUCCEEDED(_psmPopup->QueryInterface(IID_PPV_ARG(IMenuBand, &_pmbPopup)))) { _psmPopup->SetMinWidth(RECTWIDTH(*prcl)); IBandSite* pbs; if (SUCCEEDED(CoCreateInstance(CLSID_MenuBandSite, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IBandSite, &pbs)))) { if (SUCCEEDED(_pmpPopup->SetClient(pbs))) { IDeskBand* pdb; if (SUCCEEDED(_psmPopup->QueryInterface(IID_PPV_ARG(IDeskBand, &pdb)))) { pbs->AddBand(pdb); pdb->Release(); SendMessage(v_hwndTray, TM_SETPUMPHOOK, (WPARAM)_pmbPopup, (LPARAM)_pmpPopup); if (_hTheme) { HWND hwndTB; IUnknown_GetWindow(_psmPopup, &hwndTB); if (hwndTB) { SendMessage(hwndTB, TB_SETWINDOWTHEME, 0, (LPARAM)c_wzTaskBandGroupMenuTheme); } _psmPopup->SetNoBorder(TRUE); } hr = _pmpPopup->Popup(ppt, prcl, MPPF_BOTTOM); } } pbs->Release(); } } ptbc->Release(); } if (FAILED(hr)) { ttc.Activate(TRUE); _FreePopupMenu(); } return hr; } void CTaskBand::_AddItemToDropDown(int iIndex) { PTASKITEM pti = _GetItem(iIndex); WCHAR szWndText[MAX_WNDTEXT]; _GetItemTitle(iIndex, szWndText, ARRAYSIZE(szWndText), TRUE); if ((HMENU)_menuPopup) { _menuPopup.InsertMenu(0, MF_BYCOMMAND, iIndex, szWndText); } if (_psmPopup) { _psmPopup->InvalidateItem(NULL, SMINV_REFRESH); } } void CTaskBand::_RemoveItemFromDropDown(int iIndex) { _menuPopup.DeleteMenu(iIndex, MF_BYCOMMAND); int iGroupSize = _GetGroupSize(_iIndexPopup); for (int i = iIndex + 1; i <= _iIndexPopup + iGroupSize + 1; i++) { _RefreshItemFromDropDown(i, i - 1, FALSE); } if (_psmPopup) { _psmPopup->InvalidateItem(NULL, SMINV_REFRESH); } } void CTaskBand::_RefreshItemFromDropDown(int iIndex, int iNewIndex, BOOL fRefresh) { PTASKITEM pti = _GetItem(iNewIndex); WCHAR szWndText[MAX_WNDTEXT]; _GetItemTitle(iNewIndex, szWndText, ARRAYSIZE(szWndText), TRUE); _menuPopup.ModifyMenu(iIndex, MF_BYCOMMAND, iNewIndex, szWndText); if (fRefresh && _psmPopup) { if (iIndex == iNewIndex) { SMDATA smd; smd.uId = iIndex; _psmPopup->InvalidateItem(&smd, SMINV_REFRESH | SMINV_POSITION); } else _psmPopup->InvalidateItem(NULL, SMINV_REFRESH); } } void CTaskBand::_ClosePopupMenus() { SendMessage(v_hwndTray, SBM_CANCELMENU, 0, 0); _FreePopupMenu(); } void CTaskBand::_HandleDropDown(int iIndex) { _ClosePopupMenus(); PTASKITEM pti = _GetItem(iIndex); if (pti) { _iIndexLastPopup = _iIndexPopup = iIndex; _menuPopup.CreatePopupMenu(); for (int i = _GetGroupSize(iIndex) + iIndex; i > iIndex; i--) { _AddItemToDropDown(i); } RECT rc; _tb.GetItemRect(iIndex, &rc); MapWindowPoints(_tb, HWND_DESKTOP, (LPPOINT)&rc, 2); POINTL pt = {rc.left, rc.top}; RECTL rcl; RECTtoRECTL(&rc, &rcl); CToolTipCtrl ttc = _tb.GetToolTips(); ttc.Activate(FALSE); _CreatePopupMenu(&pt, &rcl); } } LRESULT CTaskBand::_HandleDestroy() { _UnregisterNotify(_uShortcutInvokeNotify); RevokeDragDrop(_hwnd); RegisterShellHook(_hwnd, FALSE); _hwnd = NULL; if (_hTheme) { CloseThemeData(_hTheme); _hTheme = NULL; } if (_tb) { ASSERT(_tb.IsWindow()); for (int i = _tb.GetButtonCount() - 1; i >= 0; i--) { PTASKITEM pti = _GetItem(i); if (pti) { delete pti; } } CImageList il = CImageList(_tb.GetImageList()); if (il) { il.Destroy(); } } return 1; } LRESULT CTaskBand::_HandleScroll(BOOL fHoriz, UINT code, int nPos) { TBMETRICS tbm; _GetToolbarMetrics(&tbm); SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS; GetScrollInfo(_hwnd, fHoriz ? SB_HORZ : SB_VERT, &si); si.nMax -= (si.nPage -1); switch (code) { case SB_BOTTOM: nPos = si.nMax; break; case SB_TOP: nPos = 0; break; case SB_ENDSCROLL: nPos = si.nPos; break; case SB_LINEDOWN: nPos = si.nPos + 1; break; case SB_LINEUP: nPos = si.nPos - 1; break; case SB_PAGEDOWN: nPos = si.nPos + si.nPage; break; case SB_PAGEUP: nPos = si.nPos - si.nPage; break; case SB_THUMBPOSITION: case SB_THUMBTRACK: break; } if (nPos > (int)(si.nMax)) nPos = si.nMax; if (nPos < 0 ) nPos = 0; SetScrollPos(_hwnd, fHoriz ? SB_HORZ : SB_VERT, nPos, TRUE); DWORD dwSize = _tb.GetButtonSize(); if (fHoriz) { int cxRow = LOWORD(dwSize) + tbm.cxButtonSpacing; _tb.SetWindowPos(0, -nPos * cxRow, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE |SWP_NOZORDER); } else { int cyRow = HIWORD(dwSize) + tbm.cyButtonSpacing; _tb.SetWindowPos(0, 0, -nPos * cyRow , 0, 0, SWP_NOACTIVATE | SWP_NOSIZE |SWP_NOZORDER); } return 0; } // after a selection is made, scroll it into view void CTaskBand::_ScrollIntoView(int iItem) { DWORD dwStyle = GetWindowLong(_hwnd, GWL_STYLE); if (dwStyle & (WS_HSCROLL | WS_VSCROLL)) { int cVisible = 0; for (int i = 0; i < iItem; i++) { if (!_IsHidden(i)) cVisible++; } if (_IsHidden(i)) { PTASKITEM pti = _GetItem(iItem); if (pti->hwnd) { cVisible--; } } int iRows, iCols; _GetNumberOfRowsCols(&iRows, &iCols, TRUE); _HandleScroll((dwStyle & WS_HSCROLL), SB_THUMBPOSITION, (dwStyle & WS_HSCROLL) ? cVisible / iRows : cVisible / iCols); } } //--------------------------------------------------------------------------- LRESULT CTaskBand::_HandleSize(WPARAM fwSizeType) { // Make the listbox fill the parent; if (fwSizeType != SIZE_MINIMIZED) { _CheckSize(); } return 0; } //--------------------------------------------------------------------------- // Have the task list show the given window. // NB Ignore taskman itself. LRESULT CTaskBand::_HandleActivate(HWND hwndActive) { // // App-window activation change is a good time to do a reality // check (make sure there are no ghost buttons, etc). // _RealityCheck(); if (hwndActive && _IsWindowNormal(hwndActive)) { _RaiseDesktop(FALSE); int i = _SelectWindow(hwndActive); if (i != -1) { PTASKITEM pti = _GetItem(i); if (pti) { // Strip off TIF_FLASHING pti->dwFlags &= ~TIF_FLASHING; // Update the flag that says, "There is an item flashing." _UpdateFlashingFlag(); // if it's flashed blue, turn it off. if (pti->dwFlags & TIF_RENDERFLASHED) _RedrawItem(hwndActive, HSHELL_REDRAW); // Switching to an application counts as "usage" // similar to launching it. This solves the "long-running // app treated as if it is rarely run" problem if (pti->ptsh) { pti->ptsh->Tickle(); } } } } else { // Activate taskbar if (!(_fIgnoreTaskbarActivate && GetForegroundWindow() == v_hwndTray) && (_iIndexPopup == -1)) { _SetCurSel(-1, TRUE); } else { _fIgnoreTaskbarActivate = FALSE; } } if (hwndActive) _ptray->_hwndLastActive = hwndActive; return TRUE; } //--------------------------------------------------------------------------- void CTaskBand::_HandleOtherWindowDestroyed(HWND hwndDestroyed) { int i; // Look for the destoyed window. int iItemIndex = _FindIndexByHwnd(hwndDestroyed); if (iItemIndex >= 0) { if (_fAnimate && _IsHorizontal() && ToolBar_IsVisible(_tb, iItemIndex)) { _AnimateItems(iItemIndex, FALSE, FALSE); } else { _DeleteItem(hwndDestroyed, iItemIndex); } } else { // If the item doesn't exist in the task list, make sure it isn't part // of somebody's fake SDI implementation. Otherwise Minimize All will // break. for (i = _tb.GetButtonCount() - 1; i >= 0; i--) { PTASKITEM pti = _GetItem(i); if ((pti->dwFlags & TIF_EVERACTIVEALT) && (HWND) GetWindowLongPtr(pti->hwnd, 0) == hwndDestroyed) { goto NoDestroy; } } } _ptray->HandleWindowDestroyed(hwndDestroyed); NoDestroy: // This might have been a rude app. Figure out if we've // got one now and have the tray sync up. HWND hwndRudeApp = _FindRudeApp(NULL); _ptray->HandleFullScreenApp(hwndRudeApp); if (hwndRudeApp) { DWORD dwStyleEx = GetWindowLongPtr(hwndRudeApp, GWL_EXSTYLE); if (!(dwStyleEx & WS_EX_TOPMOST) && !_IsRudeWindowActive(hwndRudeApp)) { SwitchToThisWindow(hwndRudeApp, TRUE); } } if (_ptray->_hwndLastActive == hwndDestroyed) { if (_ptray->_hwndLastActive == hwndDestroyed) _ptray->_hwndLastActive = NULL; } } void CTaskBand::_HandleGetMinRect(HWND hwndShell, POINTS * prc) { RECT rc; RECT rcTask; int i = _FindIndexByHwnd(hwndShell); if (i == -1) return; // Is this button grouped if (_IsHidden(i)) { // Yes, get the index for the group button and use its size i = _GetGroupIndex(i); } // Found it in our list. _tb.GetItemRect(i, &rc); // // If the Tab is mirrored then let's retreive the screen coordinates // by calculating from the left edge of the screen since screen coordinates // are not mirrored so that minRect will prserve its location. [samera] // if (IS_WINDOW_RTL_MIRRORED(GetDesktopWindow())) { RECT rcTab; _tb.GetWindowRect(&rcTab); rc.left += rcTab.left; rc.right += rcTab.left; rc.top += rcTab.top; rc.bottom += rcTab.top; } else { _tb.MapWindowPoints(HWND_DESKTOP, (LPPOINT)&rc, 2); } prc[0].x = (short)rc.left; prc[0].y = (short)rc.top; prc[1].x = (short)rc.right; prc[1].y = (short)rc.bottom; // make sure the rect is within out client area GetClientRect(_hwnd, &rcTask); MapWindowPoints(_hwnd, HWND_DESKTOP, (LPPOINT)&rcTask, 2); if (prc[0].x < rcTask.left) { prc[1].x = prc[0].x = (short)rcTask.left; prc[1].x++; } if (prc[0].x > rcTask.right) { prc[1].x = prc[0].x = (short)rcTask.right; prc[1].x++; } if (prc[0].y < rcTask.top) { prc[1].y = prc[0].y = (short)rcTask.top; prc[1].y++; } if (prc[0].y > rcTask.bottom) { prc[1].y = prc[0].y = (short)rcTask.bottom; prc[1].y++; } } BOOL CTaskBand::_IsItemActive(HWND hwndItem) { HWND hwnd = GetForegroundWindow(); return (hwnd && hwnd == hwndItem); } void CTaskBand::_CreateTBImageLists() { CImageList il = CImageList(_tb.GetImageList()); ATOMICRELEASE(_pimlSHIL); SHGetImageList(SHIL_SYSSMALL, IID_PPV_ARG(IImageList, &_pimlSHIL)); il.Destroy(); int cx = GetSystemMetrics(SM_CXSMICON); int cy = GetSystemMetrics(SM_CYSMICON); il.Create(cx, cy, SHGetImageListFlags(_tb), 4, 4); _tb.SendMessage(TB_SETIMAGELIST, IL_NORMAL, (LPARAM)(HIMAGELIST)il); _tb.SendMessage(TB_SETIMAGELIST, IL_SHIL, (LPARAM)IImageListToHIMAGELIST(_pimlSHIL)); } int CTaskBand::_AddIconToNormalImageList(HICON hicon, int iImage) { if (hicon) { CImageList il = CImageList(_tb.GetImageList()); if (il) { int iRet; if (iImage < 0 || HIWORD(iImage) != IL_NORMAL) iRet = il.ReplaceIcon(-1, hicon); else iRet = il.ReplaceIcon(iImage, hicon); if (iRet == -1) { TraceMsg(TF_WARNING, "ReplaceIcon failed for iImage %x hicon %x", iImage, hicon); iRet = iImage; } return MAKELONG(iRet, IL_NORMAL); } } return I_IMAGENONE; } void CTaskBand::_UpdateItemText(int iItem) { TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_BYINDEX | TBIF_TEXT; // get current button text TCHAR szWndText[MAX_WNDTEXT]; *szWndText = 0; _GetItemTitle(iItem, szWndText, ARRAYSIZE(szWndText), FALSE); tbbi.pszText = szWndText; _tb.SetButtonInfo(iItem, &tbbi); } void CTaskBand::_DoRedrawWhereNeeded() { int i; for (i = _tb.GetButtonCount() - 1; i >= 0; i--) { PTASKITEM pti = _GetItem(i); if (pti->dwFlags & TIF_NEEDSREDRAW) { pti->dwFlags &= ~TIF_NEEDSREDRAW; _RedrawItem(pti->hwnd, HSHELL_REDRAW, i); } } } void CTaskBand::_RedrawItem(HWND hwndShell, WPARAM code, int i) { if (i == -1) { i = _FindIndexByHwnd(hwndShell); } if (i != -1) { TOOLINFO ti; ti.cbSize = sizeof(ti); PTASKITEM pti = _GetItem(i); // set the bit saying whether we should flash or not if ((code == HSHELL_FLASH) != BOOLIFY(pti->dwFlags & TIF_RENDERFLASHED)) { // only do the set if this bit changed. if (code == HSHELL_FLASH) { // TIF_RENDERFLASHED means, "Paint the background blue." // TIF_FLASHING means, "This item is flashing." pti->dwFlags |= TIF_RENDERFLASHED; // Only set TIF_FLASHING and unhide the tray if the app is inactive. // Some apps (e.g., freecell) flash themselves while active just for // fun. It's annoying for the autohid tray to pop out in that case. if (!_IsItemActive(pti->hwnd)) { pti->dwFlags |= TIF_FLASHING; // unhide the tray whenever we get a flashing app. _ptray->Unhide(); } } else { // Don't clear TIF_FLASHING. We clear that only when the app // is activated. pti->dwFlags &= ~TIF_RENDERFLASHED; } // Update the flag that says, "There is an item flashing." _UpdateFlashingFlag(); } // Don't change the name of a group button if (pti->hwnd) { // update text and icon _UpdateItemText(i); _UpdateItemIcon(i); } int iGroupIndex = _GetGroupIndex(i); if ((iGroupIndex == _iIndexPopup) && hwndShell) { _RefreshItemFromDropDown(i, i, TRUE); } RECT rc; if (_tb.GetItemRect(i, &rc)) { InvalidateRect(_tb, &rc, TRUE); } if (_tb.GetItemRect(iGroupIndex, &rc)) { InvalidateRect(_tb, &rc, TRUE); } ti.hwnd = _tb; ti.uId = i; ti.lpszText = LPSTR_TEXTCALLBACK; SendMessage(_ptray->GetTrayTips(), TTM_UPDATETIPTEXT, 0, (LPARAM)&ti); } } void CTaskBand::_SetActiveAlt(HWND hwndAlt) { int iMax; int i; iMax = _tb.GetButtonCount(); for ( i = 0; i < iMax; i++) { PTASKITEM pti = _GetItem(i); if (pti->hwnd == hwndAlt) pti->dwFlags |= TIF_ACTIVATEALT | TIF_EVERACTIVEALT; else pti->dwFlags &= ~TIF_ACTIVATEALT; } } BOOL _IsRudeWindowActive(HWND hwnd) { // A rude window is considered "active" if it is: // - in the same thread as the foreground window, or // - in the same window hierarchy as the foreground window // HWND hwndFore = GetForegroundWindow(); DWORD dwID = GetWindowThreadProcessId(hwnd, NULL); DWORD dwIDFore = GetWindowThreadProcessId(hwndFore, NULL); if (dwID == dwIDFore) return TRUE; else if (SHIsParentOwnerOrSelf(hwnd, hwndFore) == S_OK) return TRUE; return FALSE; } // _IsRudeWindow -- is given HWND 'rude' (fullscreen) on given monitor // BOOL _IsRudeWindow(HMONITOR hmon, HWND hwnd, HMONITOR hmonTask, BOOL fSkipActiveCheck) { ASSERT(hmon); ASSERT(hwnd); // // Don't count the desktop as rude // also filter out hidden windows (such as the desktop browser's raised window) // if (IsWindowVisible(hwnd) && hwnd != v_hwndDesktop) { RECT rcMon, rcApp, rcTmp; DWORD dwStyle; // // NB: User32 will sometimes send us spurious HSHELL_RUDEAPPACTIVATED // messages. When this happens, and we happen to have a maximized // app up, the old version of this code would think there was a rude app // up. This mistake would break tray always-on-top and autohide. // // // The old logic was: // // If the app's window rect takes up the whole monitor, then it's rude. // (This check could mistake normal maximized apps for rude apps.) // // // The new logic is: // // If the app window does not have WS_DLGFRAME and WS_THICKFRAME, // then do the old check. Rude apps typically lack one of these bits // (while normal apps usually have them), so do the old check in // this case to avoid potential compat issues with rude apps that // have non-fullscreen client areas. // // Otherwise, get the client rect rather than the window rect // and compare that rect against the monitor rect. // // If (mon U app) == app, then app is filling up entire monitor GetMonitorRect(hmon, &rcMon); dwStyle = GetWindowLong(hwnd, GWL_STYLE); if ((dwStyle & (WS_CAPTION | WS_THICKFRAME)) == (WS_CAPTION | WS_THICKFRAME)) { // Doesn't match rude app profile; use client rect GetClientRect(hwnd, &rcApp); MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)&rcApp, 2); } else { // Matches rude app profile; use window rect GetWindowRect(hwnd, &rcApp); } UnionRect(&rcTmp, &rcApp, &rcMon); if (EqualRect(&rcTmp, &rcApp)) { // Looks like a rude app. Is it active? if ((hmonTask == hmon) && (fSkipActiveCheck || _IsRudeWindowActive(hwnd))) { return TRUE; } } } // No, not rude return FALSE; } struct iradata { HMONITOR hmon; // IN hmon we're checking against HWND hwnd; // INOUT hwnd of 1st rude app found HMONITOR hmonTask; HWND hwndSelected; }; BOOL WINAPI CTaskBand::IsRudeEnumProc(HWND hwnd, LPARAM lParam) { struct iradata *pira = (struct iradata *)lParam; HMONITOR hmon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); if (hmon && (pira->hmon == NULL || pira->hmon == hmon)) { if (_IsRudeWindow(hmon, hwnd, pira->hmonTask, (hwnd == pira->hwndSelected))) { // We're done pira->hwnd = hwnd; return FALSE; } } // Keep going return TRUE; } HWND CTaskBand::_EnumForRudeWindow(HWND hwndSelected) { struct iradata irad = { NULL, 0, MonitorFromWindow(_hwnd, MONITOR_DEFAULTTONEAREST), hwndSelected }; // First try our cache if (IsWindow(_hwndLastRude)) { if (!IsRudeEnumProc(_hwndLastRude, (LPARAM)&irad)) { // Cache hit return irad.hwnd; } } // No luck, gotta do it the hard way EnumWindows(IsRudeEnumProc, (LPARAM)&irad); // Cache it for next time _hwndLastRude = irad.hwnd; return irad.hwnd; } HWND CTaskBand::_FindRudeApp(HWND hwndPossible) { // // Search through: // // (a) the toplevel windows for an "active" one that "looks" fullscreen, and // (b) the task items for one that is "active" and is marked fullscreen // HWND hwndSelected = hwndPossible; if (!hwndSelected) { int iCurSel = _GetCurSel(); if (iCurSel != -1) { PTASKITEM pti = _GetItem(iCurSel); hwndSelected = pti->hwnd; } } HWND hwnd = _EnumForRudeWindow(hwndSelected); for (int i = _tb.GetButtonCount() - 1; hwnd == NULL && i >= 0; i--) { PTASKITEM pti = _GetItem(i); if (pti->fMarkedFullscreen && ((pti->hwnd == hwndSelected) || _IsRudeWindowActive(pti->hwnd))) { hwnd = pti->hwnd; } } return hwnd; } // handle WM_APPCOMMAND, special case off those that we know are global // to the system, these really are not "App" commands ;-) LRESULT CTaskBand::_OnAppCommand(int cmd) { BOOL bHandled = FALSE; switch (cmd) { // skip all of these, they are either handled by the system volume control // or by the media player, don't let these fall through to the registry // based app command handling case APPCOMMAND_MEDIA_NEXTTRACK: case APPCOMMAND_MEDIA_PREVIOUSTRACK: case APPCOMMAND_MEDIA_STOP: case APPCOMMAND_MEDIA_PLAY_PAUSE: break; case APPCOMMAND_VOLUME_MUTE: Mixer_ToggleMute(); return 0; case APPCOMMAND_VOLUME_DOWN: Mixer_SetVolume(-MIXER_DEFAULT_STEP); return 0; case APPCOMMAND_VOLUME_UP: Mixer_SetVolume(MIXER_DEFAULT_STEP); return 0; case APPCOMMAND_BASS_BOOST: Mixer_ToggleBassBoost(); return 0; case APPCOMMAND_BASS_DOWN: Mixer_SetBass(-MIXER_DEFAULT_STEP); return 0; case APPCOMMAND_BASS_UP: Mixer_SetBass(MIXER_DEFAULT_STEP); return 0; case APPCOMMAND_TREBLE_DOWN: Mixer_SetTreble(-MIXER_DEFAULT_STEP); return 0; case APPCOMMAND_TREBLE_UP: Mixer_SetTreble(MIXER_DEFAULT_STEP); return 0; default: bHandled = AppCommandTryRegistry(cmd); if (!bHandled) { switch (cmd) { case APPCOMMAND_BROWSER_SEARCH: SHFindFiles(NULL, NULL); bHandled = TRUE; break; } } } return bHandled; } PTASKITEM CTaskBand::_FindItemByHwnd(HWND hwnd) { int iIndex = _FindIndexByHwnd(hwnd); return _GetItem(iIndex); } void CTaskBand::_OnWindowActivated(HWND hwnd, BOOL fSuspectFullscreen) { // // First see if we consider this window fullscreen // HWND hwndRude; PTASKITEM pti = _FindItemByHwnd(hwnd); if (pti && pti->fMarkedFullscreen) { // // Yes, marked by the app as fullscreen // hwndRude = hwnd; } else if (fSuspectFullscreen) { // // Possibly, but we need to double-check for ourselves // // // We shouldn't need to do this but we're getting rude-app activation // msgs when there aren't any. // // Also, the hwnd that user tells us about is just the foreground window -- // _FindRudeApp will return the window that's actually sized fullscreen. // hwndRude = _FindRudeApp(hwnd); } else { // // No, not fullscreen // hwndRude = NULL; } SetTimer(_hwnd, IDT_RECHECKRUDEAPP1, 1000, NULL); // // Okay, now do that weird hwnd futzing for ACTIVEALT apps // if (pti == NULL) { BOOL fFoundBackup = FALSE; BOOL fDone = FALSE; int iMax = _tb.GetButtonCount(); for (int i = 0; (i < iMax) && (!fDone); i++) { PTASKITEM ptiT = _GetItem(i); if (ptiT->hwnd) { DWORD dwFlags = ptiT->dwFlags; if ((dwFlags & TIF_ACTIVATEALT) || (!fFoundBackup && (dwFlags & TIF_EVERACTIVEALT))) { DWORD dwpid1, dwpid2; GetWindowThreadProcessId(hwnd, &dwpid1); GetWindowThreadProcessId(ptiT->hwnd, &dwpid2); // Only change if they're in the same process if (dwpid1 == dwpid2) { hwnd = ptiT->hwnd; if (dwFlags & TIF_ACTIVATEALT) { fDone = TRUE; break; } else fFoundBackup = TRUE; } } } } } // // Now do the actual check/uncheck the button stuff // _HandleActivate(hwnd); // // Finally, let the tray know about any fullscreen windowage // _ptray->HandleFullScreenApp(hwndRude); } // We get notification about activation etc here. This saves having // a fine-grained timer. LRESULT CTaskBand::_HandleShellHook(int iCode, LPARAM lParam) { HWND hwnd = (HWND)lParam; switch (iCode) { case HSHELL_GETMINRECT: { SHELLHOOKINFO * pshi = (SHELLHOOKINFO *)lParam; _HandleGetMinRect(pshi->hwnd, (POINTS *)&pshi->rc); } return TRUE; case HSHELL_RUDEAPPACTIVATED: case HSHELL_WINDOWACTIVATED: _OnWindowActivated(hwnd, TRUE); break; case HSHELL_WINDOWREPLACING: _hwndReplacing = hwnd; break; case HSHELL_WINDOWREPLACED: if (_hwndReplacing) { // If we already created a button for this dude, remove it now. // We might have one if user sent an HSHELL_WINDOWACTIVATED before // the HSHELL_WINDOWREPLACING/HSHELL_WINDOWREPLACED pair. _DeleteItem(_hwndReplacing, -1); // Swap in _hwndReplacing for hwnd in hwnd's button int iItem = _FindIndexByHwnd(hwnd); if (iItem != -1) { PTASKITEM pti = _GetItem(iItem); pti->hwnd = _hwndReplacing; WCHAR szExeName[MAX_PATH]; SHExeNameFromHWND(_hwndReplacing, szExeName, ARRAYSIZE(szExeName)); int iIndexGroup = _GetGroupIndex(iItem); PTASKITEM ptiGroup = _GetItem(iIndexGroup); pti->fHungApp = (lstrcmpi(ptiGroup->pszExeName, szExeName) != 0); } _hwndReplacing = NULL; } break; case HSHELL_WINDOWCREATED: _AddWindow(hwnd); break; case HSHELL_WINDOWDESTROYED: _HandleOtherWindowDestroyed(hwnd); break; case HSHELL_ACTIVATESHELLWINDOW: SwitchToThisWindow(v_hwndTray, TRUE); SetForegroundWindow(v_hwndTray); break; case HSHELL_TASKMAN: // winlogon/user send a -1 lParam to indicate that the // task list should be displayed (normally the lParam is the hwnd) if (-1 == lParam) { RunSystemMonitor(); } else { // if it wasn't invoked via control escape, then it was the win key if (!_ptray->_fStuckRudeApp && GetAsyncKeyState(VK_CONTROL) >= 0) { HWND hwndForeground = GetForegroundWindow(); BOOL fIsTrayForeground = hwndForeground == v_hwndTray; if (v_hwndStartPane && hwndForeground == v_hwndStartPane) { fIsTrayForeground = TRUE; } if (!_hwndPrevFocus) { if (!fIsTrayForeground) { _hwndPrevFocus = hwndForeground; } } else if (fIsTrayForeground) { // _hwndPrevFocus will be wiped out by the MPOS_FULLCANCEL // so save it before we lose it HWND hwndPrevFocus = _hwndPrevFocus; _ClosePopupMenus(); // otherwise they're just hitting the key again. // set focus away SHAllowSetForegroundWindow(hwndPrevFocus); SetForegroundWindow(hwndPrevFocus); _hwndPrevFocus = NULL; return TRUE; } } PostMessage(v_hwndTray, TM_ACTASTASKSW, 0, 0L); } return TRUE; case HSHELL_REDRAW: { int i = _FindIndexByHwnd(hwnd); if (i != -1) { PTASKITEM pti = _GetItem(i); pti->dwFlags |= TIF_NEEDSREDRAW; SetTimer(_hwnd, IDT_REDRAW, 100, 0); } } break; case HSHELL_FLASH: _RedrawItem(hwnd, iCode); break; case HSHELL_ENDTASK: EndTask(hwnd, FALSE, FALSE); break; case HSHELL_APPCOMMAND: // shell gets last shot at WM_APPCOMMAND messages via our shell hook // RegisterShellHookWindow() is called in shell32/.RegisterShellHook() return _OnAppCommand(GET_APPCOMMAND_LPARAM(lParam)); } return 0; } void CTaskBand::_InitFonts() { HFONT hfont; NONCLIENTMETRICS ncm; ncm.cbSize = sizeof(ncm); if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0)) { // Create the bold font ncm.lfCaptionFont.lfWeight = FW_BOLD; hfont = CreateFontIndirect(&ncm.lfCaptionFont); if (hfont) { if (_hfontCapBold) DeleteFont(_hfontCapBold); _hfontCapBold = hfont; } // Create the normal font ncm.lfCaptionFont.lfWeight = FW_NORMAL; hfont = CreateFontIndirect(&ncm.lfCaptionFont); if (hfont) { if (_hfontCapNormal) DeleteFont(_hfontCapNormal); _hfontCapNormal = hfont; } } } void CTaskBand::_SetItemImage(int iItem, int iImage, int iPref) { TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_BYINDEX | TBIF_IMAGE; tbbi.iImage = iImage; _tb.SetButtonInfo(iItem, &tbbi); PTASKITEM pti = _GetItem(iItem); pti->iIconPref = iPref; } void CTaskBand::_UpdateAllIcons() { BOOL fRedraw = (BOOL)_tb.SendMessage(WM_SETREDRAW, FALSE, 0); // Set all of icon indices in the toolbar to image none for (int i = _tb.GetButtonCount() - 1; i >= 0; i--) { _SetItemImage(i, I_IMAGENONE, 0); } // Create a new image list _CreateTBImageLists(); for (i = _tb.GetButtonCount() - 1; i >= 0; i--) { _UpdateItemIcon(i); } _tb.SetRedraw(fRedraw); } //--------------------------------------------------------------------------- LRESULT CTaskBand::_HandleWinIniChange(WPARAM wParam, LPARAM lParam, BOOL fOnCreate) { _tb.SendMessage(WM_WININICHANGE, wParam, lParam); if (wParam == SPI_SETNONCLIENTMETRICS || ((!wParam) && (!lParam || (lstrcmpi((LPTSTR)lParam, TEXT("WindowMetrics")) == 0)))) { // // On creation, don't bother creating the fonts if someone else // (such as the clock control) has already done it for us. // if (!fOnCreate || !_hfontCapNormal) _InitFonts(); if (_tb) { _tb.SetFont(_hfontCapNormal); } // force _TextSpace to be recalculated _iTextSpace = 0; if (fOnCreate) { // // On creation, we haven't been inserted into bandsite yet, // so we need to defer size validation. // PostMessage(_hwnd, TBC_VERIFYBUTTONHEIGHT, 0, 0); } else { _VerifyButtonHeight(); } } if (lParam == SPI_SETMENUANIMATION || lParam == SPI_SETUIEFFECTS || (!wParam && (!lParam || (lstrcmpi((LPTSTR)lParam, TEXT("Windows")) == 0) || (lstrcmpi((LPTSTR)lParam, TEXT("VisualEffects")) == 0)))) { _fAnimate = ShouldTaskbarAnimate(); } if (!wParam && (!lParam || (0 == lstrcmpi((LPCTSTR)lParam, TEXT("TraySettings"))))) { _RefreshSettings(); } return 0; } void CTaskBand::_VerifyButtonHeight() { // force toolbar to get new sizes SIZE size = {0, 0}; _tb.SetButtonSize(size); _BandInfoChanged(); } int CTaskBand::_GetCurButtonHeight() { TBMETRICS tbm; _GetToolbarMetrics(&tbm); int cyButtonHeight = HIWORD(_tb.GetButtonSize()); if (!cyButtonHeight) cyButtonHeight = tbm.cyPad + g_cySize; return cyButtonHeight; } void CTaskBand::_HandleChangeNotify(WPARAM wParam, LPARAM lParam) { LPITEMIDLIST *ppidl; LONG lEvent; LPSHChangeNotificationLock pshcnl; pshcnl = SHChangeNotification_Lock((HANDLE)wParam, (DWORD)lParam, &ppidl, &lEvent); if (pshcnl) { switch (lEvent) { case SHCNE_EXTENDED_EVENT: { LPSHShortcutInvokeAsIDList psidl = (LPSHShortcutInvokeAsIDList)ppidl[0]; if (psidl && psidl->dwItem1 == SHCNEE_SHORTCUTINVOKE) { // Make sure nobody tries to do this in a multithreaded way // since we're not protecting the cache with a critical section ASSERT(GetCurrentThreadId() == GetWindowThreadProcessId(_hwnd, NULL)); if (TaskShortcut::_HandleShortcutInvoke(psidl)) { _ReattachTaskShortcut(); } } } break; case SHCNE_UPDATEIMAGE: { int iImage = ppidl[0] ? *(int UNALIGNED *)((BYTE *)ppidl[0] + 2) : -1; if (iImage == -1) { _UpdateAllIcons(); } } break; // The tray doesn't have a changenotify registered so we piggyback // off this one. If associations change, icons may have changed, // so we have to go rebuild. (Also if the user changes between // small and large system icons we will get an AssocChanged.) case SHCNE_ASSOCCHANGED: PostMessage(v_hwndTray, SBM_REBUILDMENU, 0, 0); break; } SHChangeNotification_Unlock(pshcnl); } } DWORD WINAPI HardErrorBalloonThread(PVOID pv) { HARDERRORDATA *phed = (HARDERRORDATA *)pv; DWORD dwError; WCHAR *pwszTitle = NULL; WCHAR *pwszText = NULL; ASSERT(NULL != phed); dwError = phed->dwError; if (phed->uOffsetTitleW != 0) { pwszTitle = (WCHAR *)((BYTE *)phed + phed->uOffsetTitleW); } if (phed->uOffsetTextW != 0) { pwszText = (WCHAR *)((BYTE *)phed + phed->uOffsetTextW); } TCHAR szMutexName[32]; HANDLE hMutex; StringCchPrintf(szMutexName, ARRAYSIZE(szMutexName), TEXT("HardError_%08lX"), dwError); hMutex = CreateMutex(NULL, FALSE, szMutexName); if (NULL != hMutex) { DWORD dwWaitResult = WaitForSingleObject(hMutex, 0); // Just test it if (dwWaitResult == WAIT_OBJECT_0) { IUserNotification *pun; HRESULT hr; hr = CoCreateInstance(CLSID_UserNotification, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IUserNotification, &pun)); if (SUCCEEDED(hr)) { pun->SetBalloonRetry(120 * 1000, 0, 0); pun->SetBalloonInfo(pwszTitle, pwszText, NIIF_WARNING); pun->SetIconInfo(NULL, pwszTitle); hr = pun->Show(NULL, 0); pun->Release(); } ReleaseMutex(hMutex); } CloseHandle(hMutex); } LocalFree(pv); return 0; } LRESULT CTaskBand::_HandleHardError(HARDERRORDATA *phed, DWORD cbData) { DWORD dwError; BOOL fHandled; BOOL fBalloon; dwError = phed->dwError; fHandled = FALSE; fBalloon = TRUE; // Check if we're on the right desktop HDESK hdeskInput = OpenInputDesktop(0, FALSE, STANDARD_RIGHTS_REQUIRED | DESKTOP_READOBJECTS); if (NULL == hdeskInput) { // Couldn't open desktop, we must not be getting the hard error while on // the default desktop. Lets not handle that case. Its silly to have // balloons on the wrong desktop, or not where the user can see them. fBalloon = FALSE; } else { CloseDesktop(hdeskInput); } if (fBalloon) { HARDERRORDATA *phedCopy; phedCopy = (HARDERRORDATA *)LocalAlloc(LPTR, cbData); if (NULL != phedCopy) { CopyMemory(phedCopy,phed,cbData); if (SHCreateThread(HardErrorBalloonThread,phedCopy,CTF_COINIT,NULL)) { fHandled = TRUE; } else { LocalFree(phedCopy); } } } return fHandled; } void CTaskBand::_OnSetFocus() { NMHDR nmhdr; _tb.SetFocus(); nmhdr.hwndFrom = _hwnd; nmhdr.code = NM_SETFOCUS; SendMessage(GetParent(_hwnd), WM_NOTIFY, (WPARAM)NULL, (LPARAM)&nmhdr); } void CTaskBand::_OpenTheme() { if (_hTheme) { CloseThemeData(_hTheme); _hTheme = NULL; } _hTheme = OpenThemeData(_hwnd, c_wzTaskBandTheme); TBMETRICS tbm; _GetToolbarMetrics(&tbm); tbm.cxPad = _hTheme ? 20 : 8; tbm.cyBarPad = 0; tbm.cxButtonSpacing = _hTheme ? 0 : 3; tbm.cyButtonSpacing = _hTheme ? 0 : 3; _tb.SendMessage(TB_SETMETRICS, 0, (LPARAM)&tbm); _CheckSize(); } LRESULT CTaskBand::v_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT lres; INSTRUMENT_WNDPROC(SHCNFI_MAIN_WNDPROC, hwnd, uMsg, wParam, lParam); switch (uMsg) { case WM_CREATE: return _HandleCreate(); case WM_DESTROY: return _HandleDestroy(); case WM_WINDOWPOSCHANGED: { LRESULT lres = _HandleSize(wParam); SetTimer(_hwnd, IDT_RECHECKRUDEAPP1, 1000, NULL); return lres; } case WM_PAINT: case WM_PRINTCLIENT: { PAINTSTRUCT ps; LPRECT prc = NULL; HDC hdc = (HDC)wParam; if (uMsg == WM_PAINT) { BeginPaint(hwnd, &ps); prc = &ps.rcPaint; hdc = ps.hdc; } if (_hTheme) { DrawThemeParentBackground(hwnd, hdc, prc); } else { RECT rc; GetClientRect(hwnd, &rc); FillRect(hdc, &rc, (HBRUSH)(COLOR_3DFACE + 1)); } if (uMsg == WM_PAINT) { EndPaint(hwnd, &ps); } } break; case WM_ERASEBKGND: { if (_hTheme) { return 1; } else { RECT rc; GetClientRect(hwnd, &rc); FillRect((HDC)wParam, &rc, (HBRUSH)(COLOR_3DFACE + 1)); } } // this keeps our window from comming to the front on button down // instead, we activate the window on the up click // we only want this for the tree and the view window // (the view window does this itself) case WM_MOUSEACTIVATE: { POINT pt; RECT rc; GetCursorPos(&pt); GetWindowRect(_hwnd, &rc); if ((LOWORD(lParam) == HTCLIENT) && PtInRect(&rc, pt)) return MA_NOACTIVATE; else goto DoDefault; } case WM_SETFOCUS: _OnSetFocus(); break; case WM_VSCROLL: return _HandleScroll(FALSE, LOWORD(wParam), HIWORD(wParam)); case WM_HSCROLL: return _HandleScroll(TRUE, LOWORD(wParam), HIWORD(wParam)); case WM_NOTIFY: return _HandleNotify((LPNMHDR)lParam); case WM_NCHITTEST: lres = DefWindowProc(hwnd, uMsg, wParam, lParam); if (lres == HTVSCROLL || lres == HTHSCROLL) return lres; else return HTTRANSPARENT; case WM_TIMER: switch (wParam) { case IDT_RECHECKRUDEAPP1: case IDT_RECHECKRUDEAPP2: case IDT_RECHECKRUDEAPP3: case IDT_RECHECKRUDEAPP4: case IDT_RECHECKRUDEAPP5: { HWND hwnd = _FindRudeApp(NULL); _ptray->HandleFullScreenApp(hwnd); if (hwnd) { DWORD dwStyleEx = GetWindowLongPtr(hwnd, GWL_EXSTYLE); if (!(dwStyleEx & WS_EX_TOPMOST) && !_IsRudeWindowActive(hwnd)) { SwitchToThisWindow(hwnd, TRUE); } } KillTimer(_hwnd, wParam); if (!hwnd && (wParam < IDT_RECHECKRUDEAPP5)) { SetTimer(_hwnd, wParam + 1, 1000, NULL); } } break; case IDT_ASYNCANIMATION: _AsyncAnimateItems(); break; case IDT_REDRAW: _DoRedrawWhereNeeded(); KillTimer(hwnd, IDT_REDRAW); break; case IDT_SYSMENU: KillTimer(_hwnd, IDT_SYSMENU); _HandleSysMenuTimeout(); break; } break; case WM_COMMAND: _HandleCommand(GET_WM_COMMAND_CMD(wParam, lParam), GET_WM_COMMAND_ID(wParam, lParam), GET_WM_COMMAND_HWND(wParam, lParam)); break; case WM_THEMECHANGED: _OpenTheme(); break; case TBC_POSTEDRCLICK: _FakeSystemMenu((HWND)wParam, (DWORD)lParam); break; case TBC_BUTTONHEIGHT: return _GetCurButtonHeight(); case WM_CONTEXTMENU: if (SHRestricted(REST_NOTRAYCONTEXTMENU)) { break; } // if we didn't find an item to put the sys menu up for, then // pass on the WM_CONTExTMENU message if (!_ContextMenu((DWORD)lParam)) goto DoDefault; break; case TBC_SYSMENUCOUNT: return _iSysMenuCount; case TBC_CHANGENOTIFY: _HandleChangeNotify(wParam, lParam); break; case TBC_VERIFYBUTTONHEIGHT: _VerifyButtonHeight(); break; case TBC_SETACTIVEALT: _SetActiveAlt((HWND) lParam); break; case TBC_CANMINIMIZEALL: return _CanMinimizeAll(); case TBC_MINIMIZEALL: return _MinimizeAll((HWND) wParam, (BOOL) lParam); break; case TBC_WARNNODROP: // // tell the user they can't drop objects on the taskbar // ShellMessageBox(hinstCabinet, _hwnd, MAKEINTRESOURCE(IDS_TASKDROP_ERROR), MAKEINTRESOURCE(IDS_TASKBAR), MB_ICONHAND | MB_OK); break; case TBC_SETPREVFOCUS: _hwndPrevFocus = (HWND)lParam; break; case TBC_FREEPOPUPMENUS: DAD_ShowDragImage(FALSE); _FreePopupMenu(); _SetCurSel(-1, TRUE); DAD_ShowDragImage(TRUE); break; case TBC_MARKFULLSCREEN: { HWND hwndFS = (HWND)lParam; if (IsWindow(hwndFS)) { // // look for the item they're talking about // PTASKITEM pti = _FindItemByHwnd(hwndFS); if (pti == NULL) { // // we didn't find it, so insert it now // pti = _GetItem(_InsertItem(hwndFS)); } if (pti) { // // mark it fullscreen/not fullscreen // pti->fMarkedFullscreen = BOOLIFY(wParam); if (_IsRudeWindowActive(hwndFS)) { // // it's active, so tell the tray to hide/show // HWND hwndRude = pti->fMarkedFullscreen ? hwndFS : NULL; _ptray->HandleFullScreenApp(hwndRude); } } } } break; case TBC_TASKTAB: { _tb.SetFocus(); int iNewIndex = 0; int iCurIndex = max(_tb.GetHotItem(), 0); int iCount = _tb.GetButtonCount(); if (iCount >= 2) { iNewIndex = iCurIndex; do { iNewIndex += (int)wParam; if (iNewIndex >= iCount) { iNewIndex = 0; } if (iNewIndex < 0) { iNewIndex = iCount - 1; } } while (_IsHidden(iNewIndex)); } _tb.SetHotItem(iNewIndex); } break; case WM_COPYDATA: { COPYDATASTRUCT *pcd; pcd = (PCOPYDATASTRUCT)lParam; if (pcd && pcd->dwData == _uCDHardError) { HARDERRORDATA *phed = (HARDERRORDATA *)pcd->lpData;; if (phed) { return _HandleHardError(phed, pcd->cbData); } return 0; // 0 = not handled } } // // If its not our hard error data, then just // fall through to default processing // default: DoDefault: if (uMsg == WM_ShellHook) return _HandleShellHook((int)wParam, lParam); else return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; } BOOL CTaskBand::_RegisterWindowClass() { WNDCLASSEX wc = {0}; wc.cbSize = sizeof(wc); if (GetClassInfoEx(hinstCabinet, c_szTaskSwClass, &wc)) return TRUE; wc.lpszClassName = c_szTaskSwClass; wc.lpfnWndProc = s_WndProc; wc.cbWndExtra = sizeof(LONG_PTR); wc.hInstance = hinstCabinet; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); return RegisterClassEx(&wc); } int TimeSortCB(PTASKITEM p1, PTASKITEM p2, LPARAM lParam) { if (p1->dwTimeFirstOpened > p2->dwTimeFirstOpened) return -1; else return 1; } int DestroyCB(PTASKITEM pti, LPVOID pData) { if (pti) delete pti; return 0; } void CTaskBand::_RefreshSettings() { BOOL fOldGlom = _fGlom; int iOldGroupSize = _iGroupSize; _LoadSettings(); if ((fOldGlom != _fGlom) || (iOldGroupSize != _iGroupSize)) { CDPA dpa; _BuildTaskList(&dpa); if (dpa) { int i; dpa.Sort(TimeSortCB, 0); BOOL fRedraw = (BOOL)_tb.SendMessage(WM_SETREDRAW, FALSE, 0); BOOL fAnimate = _fAnimate; _fAnimate = FALSE; for (i = _tb.GetButtonCount() - 1; i >= 0; i--) { _DeleteTaskItem(i, TRUE); } for (i = dpa.GetPtrCount() - 1; i >= 0 ; i--) { PTASKITEM pti = dpa.FastGetPtr(i); // NOTE: HWND_TOPMOST is used to indicate that the deleted button // is being animated. This allows the button to stay around after // its real hwnd becomes invalid. // Don't re-insert a button that was deleting. if (pti->hwnd != HWND_TOPMOST) { _InsertItem(pti->hwnd, pti, TRUE); } } dpa.Destroy(); _tb.SendMessage(WM_SETREDRAW, fRedraw, 0); _fAnimate = fAnimate; } _BandInfoChanged(); } } void CTaskBand::_LoadSettings() { if (SHRestricted(REST_NOTASKGROUPING) == 0) { _fGlom = SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("TaskbarGlomming"), FALSE, TRUE); if (_fGlom) { DWORD cbSize = sizeof(_fGlom); DWORD dwDefault = GLOM_OLDEST; SHRegGetUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("TaskbarGroupSize"), NULL, &_iGroupSize, &cbSize, FALSE, (LPBYTE)&dwDefault, sizeof(dwDefault)); } } else { _fGlom = FALSE; } } BOOL CTaskBand::_ShouldMinimize(HWND hwnd) { BOOL fRet = FALSE; DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); if (IsWindowVisible(hwnd) && !IsMinimized(hwnd) && IsWindowEnabled(hwnd)) { if (dwStyle & WS_MINIMIZEBOX) { if ((dwStyle & (WS_CAPTION | WS_SYSMENU)) == (WS_CAPTION | WS_SYSMENU)) { HMENU hmenu = GetSystemMenu(hwnd, FALSE); if (hmenu) { // is there a sys menu and is the sc_min/maximize part enabled? fRet = !(GetMenuState(hmenu, SC_MINIMIZE, MF_BYCOMMAND) & MF_DISABLED); } } else { fRet = TRUE; } } } return fRet; } BOOL CTaskBand::_CanMinimizeAll() { int i; for ( i = _tb.GetButtonCount() - 1; i >= 0; i--) { PTASKITEM pti = _GetItem(i); if (_ShouldMinimize(pti->hwnd) || (pti->dwFlags & TIF_EVERACTIVEALT)) return TRUE; } return FALSE; } typedef struct MINALLDATAtag { CDPA dpa; CTray* pTray; HWND hwndDesktop; HWND hwndTray; BOOL fPostRaiseDesktop; } MINALLDATA; DWORD WINAPI CTaskBand::MinimizeAllThreadProc(void* pv) { LONG iAnimate; ANIMATIONINFO ami; MINALLDATA* pminData = (MINALLDATA*)pv; if (pminData) { // turn off animiations during this ami.cbSize = sizeof(ami); SystemParametersInfo(SPI_GETANIMATION, sizeof(ami), &ami, FALSE); iAnimate = ami.iMinAnimate; ami.iMinAnimate = FALSE; SystemParametersInfo(SPI_SETANIMATION, sizeof(ami), &ami, FALSE); // //EnumWindows(MinimizeEnumProc, 0); // go through the tab control and minimize them. // don't do enumwindows because we only want to minimize windows // that are restorable via the tray for (int i = pminData->dpa.GetPtrCount() - 1; i >= 0 ; i--) { PTASKITEM pti = pminData->dpa.FastGetPtr(i); if (pti) { // we do the whole minimize on its own thread, so we don't do the showwindow // async. this allows animation to be off for the full minimize. if (_ShouldMinimize(pti->hwnd)) { ShowWindow(pti->hwnd, SW_SHOWMINNOACTIVE); } else if (pti->dwFlags & TIF_EVERACTIVEALT) { SHAllowSetForegroundWindow(pti->hwnd); SendMessage(pti->hwnd, WM_SYSCOMMAND, SC_MINIMIZE, -1); } } } pminData->pTray->CheckWindowPositions(); pminData->dpa.DestroyCallback(DestroyCB, NULL); if (pminData->fPostRaiseDesktop) { PostMessage(pminData->hwndDesktop, DTM_RAISE, (WPARAM)pminData->hwndTray, DTRF_RAISE); } delete pminData; // restore animations state ami.iMinAnimate = iAnimate; SystemParametersInfo(SPI_SETANIMATION, sizeof(ami), &ami, FALSE); } return 0; } void CTaskBand::_BuildTaskList(CDPA* pdpa ) { if (pdpa && _tb) { if (pdpa->Create(5)) { for (int i = _tb.GetButtonCount() - 1; (i >= 0) && ((HDPA)pdpa); i--) { PTASKITEM pti = _GetItem(i); if (pti->hwnd) { PTASKITEM ptiNew = new TASKITEM(pti); if (ptiNew) { pdpa->AppendPtr(ptiNew); } else { pdpa->DestroyCallback(DestroyCB, NULL); } } } } else { pdpa->Destroy(); } } } BOOL CTaskBand::_MinimizeAll(HWND hwndTray, BOOL fPostRaiseDesktop) { BOOL fFreeMem = TRUE; // might want to move this into MinimizeAllThreadProc (to match // _ptray->CheckWindowPositions). but what if CreateThread fails? _ptray->SaveWindowPositions(IDS_MINIMIZEALL); MINALLDATA* pminData = new MINALLDATA; if (pminData) { _BuildTaskList(&(pminData->dpa)); if (pminData->dpa) { pminData->pTray = _ptray; pminData->fPostRaiseDesktop = fPostRaiseDesktop; pminData->hwndDesktop = v_hwndDesktop; pminData->hwndTray = hwndTray; // MinimizeAllThreadProc is responsible for freeing this data fFreeMem = !SHCreateThread(MinimizeAllThreadProc, (void*)pminData, CTF_INSIST, NULL); } } if (fFreeMem) { if (pminData) { pminData->dpa.DestroyCallback(DestroyCB, NULL); delete pminData; } } return !fFreeMem; } int CTaskBand::_HitTest(POINTL ptl) { POINT pt = {ptl.x,ptl.y}; _tb.ScreenToClient(&pt); int iIndex = _tb.HitTest(&pt); if ((iIndex >= _tb.GetButtonCount()) || (iIndex < 0)) iIndex = -1; return iIndex; } HRESULT CTaskBand_CreateInstance(IUnknown* punkOuter, IUnknown** ppunk) { HRESULT hr = E_OUTOFMEMORY; if (punkOuter) return CLASS_E_NOAGGREGATION; CTaskBand* ptb = new CTaskBand(); if (ptb) { hr = ptb->Init(&c_tray); if (SUCCEEDED(hr)) { *ppunk = static_cast(ptb); hr = S_OK; } } return hr; }