Windows2003-3790/shell/explorer/taskband.cpp
2020-09-30 16:53:55 +02:00

6480 lines
187 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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;
}