|
|
#include "cabinet.h"
#include "taskband.h"
#include <shguidp.h>
#include "bandsite.h"
#include "util.h"
#include "tray.h"
#include "rcids.h"
#include "bandsite.h"
#include "startmnu.h"
#include "mixer.h"
#include <regstr.h>
#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<LPBYTE>(&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<PTASKITEM>* 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<PTASKITEM>* 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<PTASKITEM>* 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<PTASKITEM> 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<TASKITEM> 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<TASKITEM> 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<TASKITEM>* 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<IDeskBand*>(ptb); hr = S_OK; } }
return hr; }
|