You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6479 lines
187 KiB
6479 lines
187 KiB
#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;
|
|
}
|