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.
2700 lines
77 KiB
2700 lines
77 KiB
#include "shellprv.h"
|
|
#include "common.h"
|
|
|
|
#include "sftbar.h"
|
|
#include "resource.h"
|
|
#include "dpastuff.h"
|
|
#include "shlwapi.h"
|
|
#include "cobjsafe.h"
|
|
#include <iimgctx.h>
|
|
#include "uemapp.h"
|
|
#include "util.h"
|
|
#include "brutil.h"
|
|
#include "dobjutil.h"
|
|
#include "idlcomm.h"
|
|
|
|
extern UINT g_idFSNotify;
|
|
|
|
#define TF_SFTBAR TF_MENUBAND
|
|
|
|
#define PGMP_RECALCSIZE 200
|
|
|
|
CSFToolbar::CSFToolbar()
|
|
{
|
|
#ifdef CASCADE_DEBUG
|
|
_fCascadeFolder = TRUE;
|
|
#endif
|
|
_dwStyle = TBSTYLE_TOOLTIPS;
|
|
_fDirty = TRUE; // we havn't enumerated, so our state is dirty
|
|
_fRegisterChangeNotify = TRUE;
|
|
_fAllowReorder = TRUE;
|
|
|
|
_tbim.iButton = -1;
|
|
_iDragSource = -1;
|
|
_lEvents = SHCNE_DRIVEADD|SHCNE_CREATE|SHCNE_MKDIR|SHCNE_DRIVEREMOVED|
|
|
SHCNE_DELETE|SHCNE_RMDIR|SHCNE_RENAMEITEM|SHCNE_RENAMEFOLDER|
|
|
SHCNE_MEDIAINSERTED|SHCNE_MEDIAREMOVED|SHCNE_NETUNSHARE|SHCNE_NETSHARE|
|
|
SHCNE_UPDATEITEM|SHCNE_UPDATEIMAGE|SHCNE_ASSOCCHANGED|
|
|
SHCNE_UPDATEDIR|SHCNE_EXTENDED_EVENT;
|
|
#define SHCNE_PIDL1ISCHILD \
|
|
(SHCNE_DRIVEADD|SHCNE_CREATE|SHCNE_MKDIR|SHCNE_DRIVEREMOVED|\
|
|
SHCNE_DELETE|SHCNE_RMDIR|SHCNE_NETUNSHARE|SHCNE_NETSHARE|\
|
|
SHCNE_UPDATEITEM)
|
|
|
|
}
|
|
|
|
CSFToolbar::~CSFToolbar()
|
|
{
|
|
ATOMICRELEASE(_pcmSF);
|
|
ATOMICRELEASE(_piml);
|
|
|
|
_ReleaseShellFolder();
|
|
|
|
ILFree(_pidl);
|
|
|
|
OrderList_Destroy(&_hdpa);
|
|
|
|
if (_hwndWorkerWindow)
|
|
DestroyWindow(_hwndWorkerWindow);
|
|
|
|
OrderList_Destroy(&_hdpaOrder);
|
|
}
|
|
|
|
HRESULT CSFToolbar::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
static const QITAB qit[] = {
|
|
QITABENT(CSFToolbar, IWinEventHandler),
|
|
QITABENT(CSFToolbar, IShellChangeNotify),
|
|
QITABENT(CSFToolbar, IDropTarget),
|
|
QITABENT(CSFToolbar, IContextMenu),
|
|
QITABENT(CSFToolbar, IShellFolderBand),
|
|
{ 0 },
|
|
};
|
|
|
|
return QISearch(this, qit, riid, ppvObj);
|
|
}
|
|
|
|
HRESULT CSFToolbar::SetShellFolder(IShellFolder* psf, LPCITEMIDLIST pidl)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
// Save the old values
|
|
LPITEMIDLIST pidlSave = _pidl;
|
|
IShellFolder *psfSave = _psf;
|
|
ITranslateShellChangeNotify *ptscnSave = _ptscn;
|
|
|
|
_psf = NULL;
|
|
_pidl = NULL;
|
|
_ptscn = NULL;
|
|
|
|
ASSERT(NULL == psf || IS_VALID_CODE_PTR(psf, IShellFolder));
|
|
ASSERT(NULL == pidl || IS_VALID_PIDL(pidl));
|
|
|
|
if (psf || pidl)
|
|
{
|
|
if (psf)
|
|
{
|
|
_psf = psf;
|
|
_psf->AddRef();
|
|
|
|
_psf->QueryInterface(IID_PPV_ARG(ITranslateShellChangeNotify, &_ptscn));
|
|
}
|
|
|
|
if (pidl)
|
|
_pidl = ILClone(pidl);
|
|
hr = S_OK;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ILFree(pidlSave);
|
|
if (psfSave)
|
|
psfSave->Release();
|
|
if (ptscnSave)
|
|
ptscnSave->Release();
|
|
}
|
|
else
|
|
{
|
|
ASSERT(_psf == NULL);
|
|
ASSERT(_pidl == NULL);
|
|
ASSERT(_ptscn == NULL);
|
|
// we failed -- restore the old values
|
|
_psf = psfSave;
|
|
_pidl = pidlSave;
|
|
_ptscn = ptscnSave;
|
|
}
|
|
|
|
// This code is here for ShellFolderToolbar reuse. When setting a new shell folder
|
|
// into an existing band, we will refresh. Note that this is a noop on a new band.
|
|
|
|
_RememberOrder();
|
|
_SetDirty(TRUE);
|
|
if (_fShow)
|
|
_FillToolbar();
|
|
return hr;
|
|
}
|
|
|
|
HWND CSFToolbar::_CreatePager(HWND hwndParent)
|
|
{
|
|
if (!_fMulticolumn)
|
|
{
|
|
_hwndPager = CreateWindowEx(0, WC_PAGESCROLLER, NULL,
|
|
WS_CHILD | WS_TABSTOP |
|
|
WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
|
|
0, 0, 0, 0, hwndParent, (HMENU) 0, HINST_THISDLL, NULL);
|
|
if (_hwndPager)
|
|
{
|
|
hwndParent = _hwndPager;
|
|
}
|
|
}
|
|
|
|
return hwndParent;
|
|
}
|
|
|
|
HRESULT CSFToolbar::_CreateToolbar(HWND hwndParent)
|
|
{
|
|
if (!_hwndTB)
|
|
{
|
|
|
|
hwndParent = _CreatePager(hwndParent);
|
|
|
|
_hwndTB = CreateWindowEx(WS_EX_TOOLWINDOW, TOOLBARCLASSNAME, NULL,
|
|
WS_VISIBLE | WS_CHILD | TBSTYLE_FLAT |
|
|
WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
|
|
CCS_NODIVIDER | CCS_NOPARENTALIGN |
|
|
CCS_NORESIZE | _dwStyle,
|
|
0, 0, 0, 0, hwndParent, (HMENU) 0, HINST_THISDLL, NULL);
|
|
if (!_hwndTB)
|
|
{
|
|
TraceMsg(TF_ERROR, "_hwndTB failed");
|
|
return HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
if (_hwndPager)
|
|
SendMessage(_hwndPager, PGM_SETCHILD, 0, (LPARAM)_hwndTB);
|
|
|
|
SendMessage(_hwndTB, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
|
|
|
|
// Set the format to ANSI or UNICODE as appropriate.
|
|
ToolBar_SetUnicodeFormat(_hwndTB, DLL_IS_UNICODE);
|
|
if (_hwndPager)
|
|
{
|
|
// Set the format to ANSI or UNICODE as appropriate.
|
|
ToolBar_SetUnicodeFormat(_hwndPager, DLL_IS_UNICODE);
|
|
}
|
|
|
|
// Make sure we're on the same wavelength.
|
|
SendMessage(_hwndTB, CCM_SETVERSION, COMCTL32_VERSION, 0);
|
|
SendMessage(_hwndTB, TB_SETEXTENDEDSTYLE, TBSTYLE_EX_DOUBLEBUFFER, TBSTYLE_EX_DOUBLEBUFFER);
|
|
|
|
RECT rc;
|
|
SIZE size;
|
|
|
|
SystemParametersInfoA(SPI_GETWORKAREA, sizeof(RECT), &rc, FALSE);
|
|
if (!_hwndPager)
|
|
{
|
|
size.cx = RECTWIDTH(rc);
|
|
size.cy = GetSystemMetrics(SM_CYSCREEN) - (2 * GetSystemMetrics(SM_CYEDGE)); // Need to subrtact off the borders
|
|
}
|
|
else
|
|
{
|
|
//HACKHACK: THIS WILL FORCE NO WRAP TO HAPPEN FOR PROPER WIDTH CALC WHEN PAGER IS PRESENT.
|
|
size.cx = RECTWIDTH(rc);
|
|
size.cy = 32000;
|
|
}
|
|
ToolBar_SetBoundingSize(_hwndTB, &size);
|
|
|
|
if (!_SubclassWindow(_hwndTB))
|
|
{
|
|
_fRegisterChangeNotify = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_hwndPager && GetParent(_hwndPager) != hwndParent)
|
|
SetParent(_hwndPager, hwndParent);
|
|
}
|
|
|
|
if (FAILED(_GetTopBrowserWindow(&_hwndDD)))
|
|
_hwndDD = GetParent(_hwndTB);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
#define MAX_COMMANDID 0xFFFF // We're allowed one word of command ids (tested at 5)
|
|
int CSFToolbar::_GetCommandID()
|
|
{
|
|
int id = -1;
|
|
|
|
if (!_fCheckIds)
|
|
{
|
|
id = _nNextCommandID++;
|
|
}
|
|
else
|
|
{
|
|
// We are reusing command ids and must verify that
|
|
// the current one is not in use. This is slow, but
|
|
// I assume the number of buttons on one of these
|
|
// bands is relatively few.
|
|
//
|
|
for (int i = 0 ; i <= MAX_COMMANDID ; i++)
|
|
{
|
|
TBBUTTONINFO tbbiDummy;
|
|
|
|
tbbiDummy.cbSize = sizeof(tbbiDummy);
|
|
tbbiDummy.dwMask = 0; // we don't care about data, just existence
|
|
|
|
if (-1 != ToolBar_GetButtonInfo(_hwndTB, _nNextCommandID, &tbbiDummy))
|
|
{
|
|
// A button by this id wasn't found, so the id must be free
|
|
//
|
|
id = _nNextCommandID++;
|
|
break;
|
|
}
|
|
|
|
_nNextCommandID++;
|
|
_nNextCommandID %= MAX_COMMANDID;
|
|
}
|
|
}
|
|
|
|
if (_nNextCommandID > MAX_COMMANDID)
|
|
{
|
|
_nNextCommandID = 0;
|
|
_fCheckIds = TRUE;
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: This function determines the toolbar button style for the
|
|
given pidl.
|
|
|
|
Returns S_OK if pdwMIFFlags is also set (i.e., the object
|
|
supported IMenuBandItem to provide more info). S_FALSE if only
|
|
*pdwTBStyle is set.
|
|
|
|
*/
|
|
HRESULT CSFToolbar::_TBStyleForPidl(LPCITEMIDLIST pidl, DWORD *pdwTBStyle, DWORD *pdwTBState, DWORD *pdwMIFFlags, int* piIcon)
|
|
{
|
|
DWORD dwStyle = TBSTYLE_BUTTON;
|
|
if (!_fAccelerators)
|
|
dwStyle |= TBSTYLE_NOPREFIX;
|
|
|
|
*pdwMIFFlags = 0;
|
|
*pdwTBStyle = dwStyle;
|
|
*piIcon = -1;
|
|
*pdwTBState = TBSTATE_ENABLED;
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
|
|
PIBDATA CSFToolbar::_CreateItemData(PORDERITEM poi)
|
|
{
|
|
return new IBDATA(poi);
|
|
}
|
|
|
|
|
|
//
|
|
// poiOrIndex can be...
|
|
//
|
|
// A valid pointer (which will be treated as a PORDERITEM)
|
|
// A MAKEINTRESOURCE value (which will be treated as a sentinel value)
|
|
//
|
|
PIBDATA CSFToolbar::_AddOrderItemTB(PORDERITEM poiOrIndex, int index, TBBUTTON* ptbb)
|
|
{
|
|
TCHAR szName[MAX_PATH];
|
|
|
|
LPCITEMIDLIST pidlOI;
|
|
PORDERITEM poi;
|
|
if (IS_INTRESOURCE(poiOrIndex))
|
|
{
|
|
poi = NULL;
|
|
pidlOI = (LPCITEMIDLIST)poiOrIndex;
|
|
}
|
|
else
|
|
{
|
|
poi = poiOrIndex;
|
|
pidlOI = poi->pidl;
|
|
}
|
|
|
|
|
|
// We need to do this even for NULL because _ObtainPIDLName cooks
|
|
// up the word "(Empty)" as necessary.
|
|
_ObtainPIDLName(pidlOI, szName, SIZECHARS(szName));
|
|
|
|
TBBUTTON tbb = {0};
|
|
DWORD dwMIFFlags;
|
|
DWORD dwStyle;
|
|
DWORD dwState;
|
|
int iIcon;
|
|
int iCommandID = _GetCommandID();
|
|
BOOL bNoIcon = FALSE;
|
|
|
|
if (!ptbb)
|
|
ptbb = &tbb;
|
|
|
|
if (S_OK == _TBStyleForPidl(pidlOI, &dwStyle, &dwState, &dwMIFFlags,&iIcon) &&
|
|
!(dwMIFFlags & SMIF_ICON))
|
|
{
|
|
bNoIcon = TRUE;
|
|
}
|
|
|
|
PIBDATA pibdata = _CreateItemData(poi);
|
|
if (pibdata)
|
|
{
|
|
pibdata->SetFlags(dwMIFFlags);
|
|
pibdata->SetNoIcon(bNoIcon);
|
|
|
|
if (!bNoIcon && iIcon != -1)
|
|
ptbb->iBitmap = iIcon;
|
|
else
|
|
ptbb->iBitmap = I_IMAGECALLBACK;
|
|
|
|
ptbb->idCommand = iCommandID;
|
|
ptbb->fsState = (BYTE)dwState;
|
|
ptbb->fsStyle = (BYTE)dwStyle;
|
|
ptbb->dwData = (DWORD_PTR)pibdata;
|
|
ptbb->iString = (INT_PTR)szName;
|
|
|
|
// Disregard variablewidth if we are vertical
|
|
if (_fVariableWidth && !_fVertical)
|
|
ptbb->fsStyle |= TBSTYLE_AUTOSIZE;
|
|
|
|
if (ptbb->idCommand != -1)
|
|
{
|
|
if (SendMessage(_hwndTB, TB_INSERTBUTTON, index, (LPARAM)ptbb))
|
|
{
|
|
TraceMsg(TF_BAND, "SFToolbar::_AddPidl %d 0x%x [%s]", ptbb->idCommand, ptbb->dwData, ptbb->iString);
|
|
}
|
|
else
|
|
{
|
|
delete pibdata;
|
|
pibdata = NULL;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return pibdata;
|
|
}
|
|
|
|
void CSFToolbar::_ObtainPIDLName(LPCITEMIDLIST pidl, LPTSTR psz, int cchMax)
|
|
{
|
|
DisplayNameOf(_psf, pidl, SHGDN_NORMAL, psz, cchMax);
|
|
}
|
|
|
|
int CSFToolbar::_GetBitmap(int iCommandID, PIBDATA pibdata, BOOL fUseCache)
|
|
{
|
|
int iBitmap;
|
|
|
|
if (_fNoIcons || !pibdata || pibdata->GetNoIcon())
|
|
{
|
|
iBitmap = -1;
|
|
}
|
|
else
|
|
{
|
|
iBitmap = OrderItem_GetSystemImageListIndex(pibdata->GetOrderItem(), _psf, fUseCache);
|
|
}
|
|
|
|
return iBitmap;
|
|
}
|
|
|
|
void CSFToolbar::_OnGetDispInfo(LPNMHDR pnm, BOOL fUnicode)
|
|
{
|
|
LPNMTBDISPINFO pdi = (LPNMTBDISPINFO)pnm;
|
|
PIBDATA pibdata = (PIBDATA)pdi->lParam;
|
|
if (pdi->dwMask & TBNF_IMAGE)
|
|
{
|
|
pdi->iImage = _GetBitmap(pdi->idCommand, pibdata, TRUE);
|
|
}
|
|
|
|
if (pdi->dwMask & TBNF_TEXT)
|
|
{
|
|
if (pdi->pszText)
|
|
{
|
|
if (fUnicode)
|
|
{
|
|
pdi->pszText[0] = TEXT('\0');
|
|
}
|
|
else
|
|
{
|
|
pdi->pszText[0] = 0;
|
|
}
|
|
}
|
|
}
|
|
pdi->dwMask |= TBNF_DI_SETITEM;
|
|
}
|
|
|
|
|
|
// Adds pidl as a new button, handles ILFree(pidl) for the caller
|
|
//
|
|
BOOL CSFToolbar::_AddPidl(LPITEMIDLIST pidl, DWORD dwFlags, int index)
|
|
{
|
|
if (_hdpa)
|
|
{
|
|
PORDERITEM poi = OrderItem_Create(pidl, index);
|
|
if (poi)
|
|
{
|
|
int iPos = DPA_InsertPtr(_hdpa, index, poi);
|
|
if (-1 != iPos)
|
|
{
|
|
// If we did not load an order, then new items should
|
|
// show up alphabetically in the list, not at the bottom.
|
|
if (!_fHasOrder && !(dwFlags & FSNA_BULKADD))
|
|
{
|
|
// Sort by name
|
|
_SortDPA(_hdpa);
|
|
|
|
// Find the index of the order item. We use this index as
|
|
// the toolbar insert index.
|
|
index = DPA_GetPtrIndex(_hdpa, poi);
|
|
}
|
|
|
|
if (_AddOrderItemTB(poi, index, NULL))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
DPA_DeletePtr(_hdpa, iPos);
|
|
}
|
|
|
|
OrderItem_Free(poi);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
ILFree(pidl);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CSFToolbar::_FilterPidl(LPCITEMIDLIST pidl)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
void CSFToolbar::s_NewItem(void *pvParam, LPCITEMIDLIST pidl)
|
|
{
|
|
CSFToolbar* psft = (CSFToolbar*)pvParam;
|
|
psft->v_NewItem(pidl);
|
|
}
|
|
|
|
HRESULT CSFToolbar::_GetIEnumIDList(DWORD dwEnumFlags, IEnumIDList **ppenum)
|
|
{
|
|
ASSERT(_psf);
|
|
// Pass in a NULL hwnd so the enumerator does not show any UI while
|
|
// we're filling a band.
|
|
return IShellFolder_EnumObjects(_psf, NULL, dwEnumFlags, ppenum);
|
|
}
|
|
|
|
void CSFToolbar::_FillDPA(HDPA hdpa, HDPA hdpaSort, DWORD dwEnumFlags)
|
|
{
|
|
IEnumIDList* penum;
|
|
int cItems = 0;
|
|
|
|
if (!_psf)
|
|
return;
|
|
|
|
if (S_OK == _GetIEnumIDList(dwEnumFlags, &penum))
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
ULONG ul;
|
|
|
|
while (S_OK == penum->Next(1, &pidl, &ul))
|
|
{
|
|
cItems++;
|
|
if (_FilterPidl(pidl) || !OrderList_Append(hdpa, pidl, -1))
|
|
{
|
|
TraceMsg(TF_MENUBAND, "SFToolbar (0x%x)::_FillDPA : Did not Add Pidl (0x%x).", this, pidl);
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
|
|
penum->Release();
|
|
}
|
|
|
|
ORDERINFO oinfo;
|
|
int iInsertIndex = _tbim.iButton + 1; // This is the button where the cursor sat.
|
|
// So, We want to insert after that
|
|
if (iInsertIndex >= ToolBar_ButtonCount(_hwndTB)) // But, if it's at the end,
|
|
iInsertIndex = -1; // Convert the insert to an append.
|
|
// - Comments in rhyme by lamadio
|
|
|
|
oinfo.psf = _psf;
|
|
(oinfo.psf)->AddRef();
|
|
oinfo.dwSortBy = (_fHasOrder || _fDropping)? ((_fNoNameSort ? OI_SORTBYORDINAL : OI_SORTBYNAME)): OI_MERGEBYNAME;
|
|
OrderList_Merge(hdpa, hdpaSort, _fDropping ? iInsertIndex : _DefaultInsertIndex(), (LPARAM) &oinfo,
|
|
s_NewItem, (void *)this);
|
|
ATOMICRELEASE(oinfo.psf);
|
|
}
|
|
|
|
// This function re-enumerates the IShellFolder, keeping things ordered correctly.
|
|
//
|
|
void CSFToolbar::_FillToolbar()
|
|
{
|
|
HDPA hdpaSort;
|
|
HDPA hdpa;
|
|
|
|
if (!_fDirty || !_psf)
|
|
return;
|
|
|
|
// If we have an order array, use that, otherwise
|
|
// use the currently viewed items
|
|
|
|
// remove the ref for the member variable since we can get reentered
|
|
// this would be better with real refcounting but this'll do
|
|
|
|
BOOL fTakeOrderRef = FALSE;
|
|
if (_hdpaOrder)
|
|
{
|
|
hdpaSort = _hdpaOrder; // already sorted by name
|
|
// we set it to null, so we have complete ownership of it.
|
|
// at the end we're going to nuke _hdpaOrder anyway in _RememberOrder.
|
|
_hdpaOrder = NULL;
|
|
fTakeOrderRef = TRUE;
|
|
}
|
|
else
|
|
{
|
|
hdpaSort = _hdpa;
|
|
_SortDPA(hdpaSort);
|
|
}
|
|
|
|
hdpa = DPA_Create(hdpaSort ? DPA_GetPtrCount(hdpaSort) : 12);
|
|
if (hdpa)
|
|
{
|
|
_FillDPA(hdpa, hdpaSort, SHCONTF_FOLDERS|SHCONTF_NONFOLDERS);
|
|
|
|
HDPA hdpaToRemove = DPA_Create(4);
|
|
if (hdpaToRemove)
|
|
{
|
|
HDPA hdpaToAdd = DPA_Create(4);
|
|
if (hdpaToAdd)
|
|
{
|
|
int i, j;
|
|
BOOL fReleaseAdd = TRUE;
|
|
|
|
if (_hdpa)
|
|
{
|
|
// if there isn't anything in the hdpaSort list (which is all the stuff that's already there),
|
|
// add a null element. this is so it'll generate a "remove this null element" later.
|
|
// otherwise, we might end up with an (Empty) item left over.
|
|
if (DPA_GetPtrCount(hdpaSort) == 0)
|
|
{
|
|
OrderList_Append(hdpaSort, NULL, 0);
|
|
}
|
|
|
|
ORDERINFO oi = {0};
|
|
oi.dwSortBy = OI_SORTBYNAME;
|
|
oi.psf = _psf;
|
|
_psf->AddRef();
|
|
|
|
DPA_Sort(hdpaSort, OrderItem_Compare, (LPARAM) &oi);
|
|
DPA_Sort(hdpa, OrderItem_Compare, (LPARAM) &oi);
|
|
|
|
i = 0;
|
|
j = 0;
|
|
while ((i < DPA_GetPtrCount(hdpaSort)) && (j < DPA_GetPtrCount(hdpa)))
|
|
{
|
|
void *pv1 = DPA_FastGetPtr(hdpaSort, i);
|
|
void *pv2 = DPA_FastGetPtr(hdpa, j);
|
|
int nCmp = OrderItem_Compare(pv1, pv2, (LPARAM) &oi);
|
|
if (nCmp > 0)
|
|
{
|
|
DPA_AppendPtr(hdpaToAdd, pv2);
|
|
j++;
|
|
}
|
|
else if (nCmp < 0)
|
|
{
|
|
DPA_AppendPtr(hdpaToRemove, pv1);
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
j++;
|
|
}
|
|
}
|
|
|
|
while (i < DPA_GetPtrCount(hdpaSort))
|
|
{
|
|
DPA_AppendPtr(hdpaToRemove, DPA_FastGetPtr(hdpaSort, i));
|
|
i++;
|
|
}
|
|
while (j < DPA_GetPtrCount(hdpa))
|
|
{
|
|
DPA_AppendPtr(hdpaToAdd, DPA_FastGetPtr(hdpa, j));
|
|
j++;
|
|
}
|
|
|
|
_psf->Release();
|
|
}
|
|
else
|
|
{
|
|
DPA_Destroy(hdpaToAdd);
|
|
hdpaToAdd = hdpa;
|
|
fReleaseAdd = FALSE;
|
|
_hdpa = DPA_Create(DPA_GetPtrCount(hdpa));
|
|
}
|
|
|
|
SendMessage(_hwndTB, WM_SETREDRAW, FALSE, 0);
|
|
|
|
if (_hdpa)
|
|
{
|
|
_NotifyBulkOperation(TRUE);
|
|
// add buttons back in
|
|
for (i = 0; i < DPA_GetPtrCount(hdpaToAdd); i++)
|
|
{
|
|
PORDERITEM poi = (PORDERITEM)DPA_FastGetPtr(hdpaToAdd, i);
|
|
_OnFSNotifyAdd(poi->pidl, FSNA_BULKADD, poi->nOrder);
|
|
}
|
|
|
|
// remove buttons that die
|
|
for (i = 0; i < DPA_GetPtrCount(hdpaToRemove); i++)
|
|
{
|
|
PORDERITEM poi = (PORDERITEM)DPA_FastGetPtr(hdpaToRemove, i);
|
|
_OnFSNotifyRemove(poi->pidl);
|
|
}
|
|
_NotifyBulkOperation(FALSE);
|
|
}
|
|
|
|
if (fReleaseAdd)
|
|
{
|
|
DPA_Destroy(hdpaToAdd);
|
|
}
|
|
}
|
|
DPA_Destroy(hdpaToRemove);
|
|
}
|
|
|
|
OrderList_Destroy(&hdpa);
|
|
}
|
|
|
|
SendMessage(_hwndTB, WM_SETREDRAW, TRUE, 0);
|
|
|
|
if (fTakeOrderRef)
|
|
{
|
|
OrderList_Destroy(&hdpaSort);
|
|
}
|
|
_RememberOrder();
|
|
|
|
_UpdateButtons();
|
|
_SetDirty(FALSE);
|
|
|
|
if (!SHIsTempDisplayMode())
|
|
{
|
|
_ToolbarChanged();
|
|
}
|
|
|
|
TraceMsg(TF_BAND, "SFToolbar::_FillToolbar found %d items", DPA_GetPtrCount(_hdpa));
|
|
}
|
|
|
|
void CSFToolbar::EmptyToolbar()
|
|
{
|
|
if (_hwndTB)
|
|
{
|
|
TraceMsg(TF_BAND, "SFToolbar::EmptyToolbar %d items", _hdpa ? DPA_GetPtrCount(_hdpa) : 0);
|
|
|
|
while (InlineDeleteButton(0))
|
|
{
|
|
// delete the buttons
|
|
}
|
|
}
|
|
|
|
OrderList_Destroy(&_hdpa);
|
|
|
|
_fDirty = TRUE;
|
|
|
|
_nNextCommandID = 0;
|
|
}
|
|
|
|
void CSFToolbar::_SetDirty(BOOL fDirty)
|
|
{
|
|
_fDirty = fDirty;
|
|
}
|
|
|
|
UINT CSFToolbar::_IndexToID(int iTBIndex)
|
|
{
|
|
TBBUTTON tbb;
|
|
|
|
if (SendMessage(_hwndTB, TB_GETBUTTON, iTBIndex, (LPARAM)&tbb))
|
|
{
|
|
return tbb.idCommand;
|
|
}
|
|
return (UINT)-1;
|
|
}
|
|
|
|
// if ptbbi is specified, dwMask must be filled in
|
|
//
|
|
// if pIndex is specified, it receives the DPA index, not the toolbar index
|
|
|
|
HRESULT CSFToolbar::_GetButtonFromPidl(LPCITEMIDLIST pidl, TBBUTTONINFO * ptbbi, int * pIndex, LPITEMIDLIST *ppidlOut)
|
|
{
|
|
if (ppidlOut)
|
|
*ppidlOut = NULL;
|
|
|
|
if (!_hdpa)
|
|
return E_FAIL;
|
|
|
|
for (int i = DPA_GetPtrCount(_hdpa) - 1 ; i >= 0 ; i--)
|
|
{
|
|
HRESULT hr;
|
|
PORDERITEM poi = (PORDERITEM)DPA_FastGetPtr(_hdpa, i);
|
|
|
|
ASSERT(poi);
|
|
if (pidl == poi->pidl)
|
|
{
|
|
hr = 0;
|
|
}
|
|
else
|
|
{
|
|
hr = (!pidl || !poi->pidl) ? E_FAIL : _psf->CompareIDs(0, pidl, poi->pidl);
|
|
}
|
|
|
|
if (ResultFromShort(0) == hr)
|
|
{
|
|
if (pIndex)
|
|
*pIndex = i;
|
|
|
|
if (ptbbi)
|
|
{
|
|
int id = _IndexToID(v_DPAIndexToTBIndex(i));
|
|
|
|
if (id != -1)
|
|
{
|
|
ptbbi->cbSize = sizeof(*ptbbi);
|
|
if (-1 == ToolBar_GetButtonInfo(_hwndTB, id, ptbbi))
|
|
{
|
|
ZeroMemory(ptbbi, sizeof(*ptbbi));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ZeroMemory(ptbbi, sizeof(*ptbbi));
|
|
}
|
|
}
|
|
|
|
if (ppidlOut)
|
|
*ppidlOut = poi->pidl;
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
// On an add, tack the new button on the end
|
|
void CSFToolbar::_OnFSNotifyAdd(LPCITEMIDLIST pidl, DWORD dwFlags, int nIndex)
|
|
{
|
|
// be paranoid and make sure we don't duplicate an item
|
|
//
|
|
if ((dwFlags & FSNA_BULKADD) || FAILED(_GetButtonFromPidl(pidl, NULL, NULL, NULL)))
|
|
{
|
|
LPITEMIDLIST pidlNew;
|
|
|
|
if (_fFSNotify && !_ptscn && pidl && !(dwFlags & FSNA_BULKADD))
|
|
{
|
|
if (FAILED(SHGetRealIDL(_psf, pidl, &pidlNew)))
|
|
pidlNew = NULL;
|
|
}
|
|
else
|
|
{
|
|
pidlNew = pidl ? ILClone(pidl) : NULL;
|
|
}
|
|
|
|
if ((dwFlags & FSNA_BULKADD) || !_FilterPidl(pidlNew))
|
|
{
|
|
int index = (dwFlags & FSNA_ADDDEFAULT) ? _DefaultInsertIndex() : nIndex;
|
|
|
|
if (_fDropping)
|
|
{
|
|
if (-1 == _tbim.iButton)
|
|
index = 0; // if qlinks has no items, _tbim.iButton is -1, but you can't insert there...
|
|
else if (_tbim.dwFlags & TBIMHT_AFTER)
|
|
index = _tbim.iButton + 1;
|
|
else
|
|
index = _tbim.iButton;
|
|
|
|
// We need to store this as the new order because a drag and drop has occured.
|
|
// We will store this order and use it until the end of time.
|
|
_fHasOrder = TRUE;
|
|
_fChangedOrder = TRUE;
|
|
}
|
|
|
|
_AddPidl(pidlNew, dwFlags, index);
|
|
|
|
if (!(dwFlags & FSNA_BULKADD))
|
|
{
|
|
OrderList_Reorder(_hdpa);
|
|
}
|
|
|
|
if (_fDropping)
|
|
{
|
|
_Dropped(index, FALSE);
|
|
_fDropping = FALSE;
|
|
}
|
|
// NOTE: i'm nuking this call to SetDirty as it doesn't seem
|
|
// necessary and we don't have a matching call to _SetDirty(FALSE);
|
|
// mismatch of those calls causes nt5 bug #173868. [tjgreen 5-15-98]
|
|
//_SetDirty(TRUE);
|
|
}
|
|
else
|
|
{
|
|
ILFree(pidlNew);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// This function syncronously removes the button, and deletes it's contents.
|
|
// iTBIndex is a toolbar index, not a DPA index.
|
|
// This avoids Reentrancy problems, as well as Leaks caused by unhooked toolbars
|
|
BOOL_PTR CSFToolbar::InlineDeleteButton(int iTBIndex)
|
|
{
|
|
BOOL_PTR fRet = FALSE;
|
|
TBBUTTONINFO tbbi = {0};
|
|
tbbi.cbSize = sizeof(tbbi);
|
|
tbbi.dwMask = TBIF_LPARAM | TBIF_BYINDEX;
|
|
if (ToolBar_GetButtonInfo(_hwndTB, iTBIndex, &tbbi) >= 0)
|
|
{
|
|
PIBDATA pibdata = (PIBDATA)tbbi.lParam;
|
|
tbbi.lParam = NULL;
|
|
|
|
ToolBar_SetButtonInfo(_hwndTB, iTBIndex, &tbbi);
|
|
|
|
fRet = SendMessage(_hwndTB, TB_DELETEBUTTON, iTBIndex, 0);
|
|
|
|
if (pibdata)
|
|
delete pibdata;
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
// On a remove, rip out the old button and adjust existing ones
|
|
void CSFToolbar::_OnFSNotifyRemove(LPCITEMIDLIST pidl)
|
|
{
|
|
int i;
|
|
LPITEMIDLIST pidlButton;
|
|
if (SUCCEEDED(_GetButtonFromPidl(pidl, NULL, &i, &pidlButton)))
|
|
{
|
|
// remove it from the DPA before nuking the button. There is a rentrancy issue here.
|
|
DPA_DeletePtr(_hdpa, i);
|
|
InlineDeleteButton(v_DPAIndexToTBIndex(i));
|
|
ILFree(pidlButton);
|
|
_fChangedOrder = TRUE;
|
|
}
|
|
}
|
|
|
|
// On a rename, just change the text of the old button
|
|
//
|
|
void CSFToolbar::_OnFSNotifyRename(LPCITEMIDLIST pidlFrom, LPCITEMIDLIST pidlTo)
|
|
{
|
|
TBBUTTONINFO tbbi;
|
|
LPITEMIDLIST pidlButton;
|
|
int i;
|
|
|
|
tbbi.dwMask = TBIF_COMMAND | TBIF_LPARAM;
|
|
if (SUCCEEDED(_GetButtonFromPidl(pidlFrom, &tbbi, &i, &pidlButton)))
|
|
{
|
|
LPITEMIDLIST pidlNew;
|
|
|
|
if (_fFSNotify && !_ptscn)
|
|
{
|
|
if (FAILED(SHGetRealIDL(_psf, pidlTo, &pidlNew)))
|
|
pidlNew = NULL;
|
|
}
|
|
else
|
|
{
|
|
pidlNew = ILClone(pidlTo);
|
|
}
|
|
|
|
if (pidlNew)
|
|
{
|
|
LPITEMIDLIST pidlFree = pidlNew;
|
|
PORDERITEM poi = (PORDERITEM)DPA_FastGetPtr(_hdpa, i);
|
|
if (EVAL(poi))
|
|
{
|
|
pidlFree = poi->pidl;
|
|
poi->pidl = pidlNew;
|
|
|
|
TCHAR szName[MAX_PATH];
|
|
if (SUCCEEDED(DisplayNameOf(_psf, pidlNew, SHGDN_NORMAL, szName, ARRAYSIZE(szName))))
|
|
{
|
|
// _GetButtonFromPidl filled in tbbi.cbSize and tbbi.idCommand
|
|
//
|
|
PIBDATA pibdata = (PIBDATA)tbbi.lParam;
|
|
if (pibdata)
|
|
pibdata->SetOrderItem(poi);
|
|
|
|
tbbi.dwMask = TBIF_TEXT;
|
|
tbbi.pszText = szName;
|
|
EVAL(ToolBar_SetButtonInfo(_hwndTB, tbbi.idCommand, &tbbi));
|
|
// Just so that it's new location gets persisted
|
|
_fChangedOrder = TRUE;
|
|
|
|
// sync up the orderlist right now so if an updatedir comes in
|
|
// it won't think the renamed pidl is new
|
|
_RememberOrder();
|
|
}
|
|
}
|
|
|
|
ILFree(pidlFree);
|
|
}
|
|
}
|
|
}
|
|
|
|
// On a complete update remove the old button and add it again
|
|
//
|
|
void CSFToolbar::_OnFSNotifyUpdate(LPCITEMIDLIST pidl)
|
|
{
|
|
TBBUTTONINFO tbbi;
|
|
|
|
tbbi.dwMask = TBIF_COMMAND;
|
|
LPITEMIDLIST pidlButton;
|
|
if (SUCCEEDED(_GetButtonFromPidl(pidl, &tbbi, NULL, &pidlButton)))
|
|
{
|
|
TCHAR szName[MAX_PATH];
|
|
|
|
if (SUCCEEDED(DisplayNameOf(_psf, pidlButton, SHGDN_NORMAL, szName, ARRAYSIZE(szName))))
|
|
{
|
|
int iBitmap = _GetBitmap(tbbi.idCommand, _IDToPibData(tbbi.idCommand, NULL), FALSE);
|
|
if (iBitmap >= 0)
|
|
{
|
|
tbbi.dwMask = TBIF_IMAGE | TBIF_TEXT;
|
|
tbbi.iImage = iBitmap;
|
|
tbbi.pszText = szName;
|
|
|
|
ToolBar_SetButtonInfo(_hwndTB, tbbi.idCommand, &tbbi);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSFToolbar::_Refresh()
|
|
{
|
|
if (!_hdpa)
|
|
return;
|
|
|
|
_RememberOrder();
|
|
|
|
EmptyToolbar();
|
|
if (_fShow)
|
|
{
|
|
_FillToolbar();
|
|
}
|
|
}
|
|
|
|
LRESULT CSFToolbar::_OnTimer(WPARAM wParam)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CSFToolbar::_DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_DRAWITEM:
|
|
case WM_MEASUREITEM:
|
|
case WM_INITMENUPOPUP:
|
|
case WM_MENUSELECT:
|
|
if (_pcm2)
|
|
_pcm2->HandleMenuMsg(uMsg, wParam, lParam);
|
|
break;
|
|
|
|
case WM_MENUCHAR:
|
|
{
|
|
LRESULT lres = 0;
|
|
IContextMenu3* pcm3;
|
|
if (_pcm2 && SUCCEEDED(_pcm2->QueryInterface(IID_PPV_ARG(IContextMenu3, &pcm3))))
|
|
{
|
|
pcm3->HandleMenuMsg2(uMsg, wParam, lParam, &lres);
|
|
pcm3->Release();
|
|
}
|
|
return lres;
|
|
}
|
|
break;
|
|
|
|
case WM_TIMER:
|
|
if (_OnTimer(wParam))
|
|
{
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return CNotifySubclassWndProc::_DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose:
|
|
For future use. when renaming a parent of this shell folder
|
|
we should rebind to it and refill us.
|
|
|
|
S_OK Indicates successful handling of this notification
|
|
S_FALSE Indicates the notification is not a handled situation.
|
|
The caller should handle the notification in this case.
|
|
Other Failure code indicates a problem. Caller should abort
|
|
operation or handle the notification itself.
|
|
|
|
*/
|
|
HRESULT CSFToolbar::_OnRenameFolder(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
HRESULT CSFToolbar::OnChange(LONG lEvent, LPCITEMIDLIST pidlOrg1, LPCITEMIDLIST pidlOrg2)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LPITEMIDLIST pidl1 = (LPITEMIDLIST)pidlOrg1;
|
|
LPITEMIDLIST pidl2 = (LPITEMIDLIST)pidlOrg2;
|
|
LPITEMIDLIST pidl1ToFree = NULL; // Used if we allocate a pidl that needs to be freed. (::TranslateIDs())
|
|
LPITEMIDLIST pidl2ToFree = NULL;
|
|
LPITEMIDLIST pidlOut1Event2 = NULL; // Used if we allocate a pidl that needs to be freed. (::TranslateIDs())
|
|
LPITEMIDLIST pidlOut2Event2 = NULL;
|
|
LONG lEvent2 = (LONG)-1;
|
|
|
|
AddRef(); // This object could be released during this call
|
|
|
|
if (_ptscn)
|
|
{
|
|
hr = _ptscn->TranslateIDs(&lEvent, pidlOrg1, pidlOrg2, &pidl1, &pidl2,
|
|
&lEvent2, &pidlOut1Event2, &pidlOut2Event2);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// if pidl1 doesn't equal pidlOrg1, then pidl1 was allocated and needs to be freed.
|
|
pidl1ToFree = ((pidlOrg1 == pidl1) ? NULL : pidl1);
|
|
pidl2ToFree = ((pidlOrg2 == pidl2) ? NULL : pidl2);
|
|
|
|
ASSERT(NULL == pidl1 || IS_VALID_PIDL(pidl1));
|
|
ASSERT(NULL == pidl2 || IS_VALID_PIDL(pidl2));
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = OnTranslatedChange(lEvent, pidl1, pidl2);
|
|
|
|
// Do we have a second event to process?
|
|
if (SUCCEEDED(hr) && lEvent2 != (LONG)-1)
|
|
{
|
|
// Yes, then go do it.
|
|
hr = OnTranslatedChange(lEvent2, pidlOut1Event2, pidlOut2Event2);
|
|
}
|
|
ILFree(pidlOut1Event2);
|
|
ILFree(pidlOut2Event2);
|
|
ILFree(pidl1ToFree);
|
|
ILFree(pidl2ToFree);
|
|
}
|
|
|
|
Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void DBPrPidl(LPCSTR szPre, LPCITEMIDLIST pidl)
|
|
{
|
|
TCHAR szName[MAX_PATH];
|
|
|
|
szName[0] = '\0';
|
|
if (pidl)
|
|
SHGetNameAndFlags(pidl, SHGDN_FORPARSING, szName, SIZECHARS(szName), NULL);
|
|
|
|
TraceMsg(TF_WARNING, "%hs%s", szPre, szName);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
HRESULT CSFToolbar::OnTranslatedChange(LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL fSizeChanged = FALSE;
|
|
|
|
TraceMsg(TF_SFTBAR, "CSFTBar::OnTranslateChange: lEvent=%x", lEvent);
|
|
|
|
// If we weren't given a pidl we won't register for
|
|
// SHChangeNotify calls, but our IShellChange interface
|
|
// can still be QI()d so someone could errantly call us.
|
|
//
|
|
// If we change to using QS() for IShellChange interface
|
|
// then we can put this check there...
|
|
//
|
|
if (NULL == _pidl)
|
|
{
|
|
// HACKHACK (scotth): resource-based menus (CMenuISF) don't set _pidl.
|
|
// Right now allow SHCNE_UPDATEDIR thru...
|
|
if (SHCNE_UPDATEDIR == lEvent)
|
|
goto HandleUpdateDir;
|
|
|
|
TraceMsg(TF_WARNING, "CSFToolbar::OnChange - _pidl is NULL");
|
|
hr = E_FAIL;
|
|
goto CleanUp;
|
|
}
|
|
|
|
if (lEvent & SHCNE_PIDL1ISCHILD)
|
|
{
|
|
// We only handle notifications for immediate kids. (except SHCNE_RENAMEFOLDER)
|
|
//
|
|
|
|
if (!_IsChildID(pidl1, TRUE))
|
|
{
|
|
TraceMsg(TF_SFTBAR, "CSFTBar::OnTranslateChange: Not a child. Bailing");
|
|
hr = E_FAIL;
|
|
goto CleanUp;
|
|
}
|
|
}
|
|
|
|
// Have we been shown yet?
|
|
if (_hdpa == NULL)
|
|
{
|
|
// No. Well, then punt this. We'll catch it on the first enum.
|
|
hr = E_FAIL;
|
|
goto CleanUp;
|
|
}
|
|
|
|
switch (lEvent)
|
|
{
|
|
case SHCNE_EXTENDED_EVENT:
|
|
{
|
|
SHChangeDWORDAsIDList UNALIGNED * pdwidl = (SHChangeDWORDAsIDList UNALIGNED *)pidl1;
|
|
if (pdwidl->dwItem1 == SHCNEE_ORDERCHANGED)
|
|
{
|
|
TraceMsg(TF_SFTBAR, "CSFTBar::OnTranslateChange: Reorder event");
|
|
|
|
// Do this first so that we can say "We can handle it". This prevents the
|
|
// mnfolder code that works around a bug in some installers where they don't
|
|
// send a Create Folder before the create item in that folder. It causes an
|
|
// update dir...
|
|
if (!pidl2 || ILIsEqual(_pidl, pidl2))
|
|
{
|
|
// if this reorder came from us, blow it off
|
|
if (!SHChangeMenuWasSentByMe(this, pidl1))
|
|
{
|
|
// load new order stream
|
|
_LoadOrderStream();
|
|
|
|
// rebuild toolbar
|
|
_SetDirty(TRUE);
|
|
if (_fShow)
|
|
_FillToolbar();
|
|
}
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SHCNE_DRIVEADD:
|
|
case SHCNE_CREATE:
|
|
case SHCNE_MKDIR:
|
|
TraceMsg(TF_SFTBAR, "CSFTBar::OnTranslateChange: Adding item");
|
|
pidl1 = ILFindLastID(pidl1);
|
|
_OnFSNotifyAdd(pidl1, FSNA_ADDDEFAULT, 0);
|
|
_RememberOrder();
|
|
fSizeChanged = TRUE;
|
|
break;
|
|
|
|
case SHCNE_DRIVEREMOVED:
|
|
case SHCNE_DELETE:
|
|
case SHCNE_RMDIR:
|
|
TraceMsg(TF_SFTBAR, "CSFTBar::OnTranslateChange: Removing item");
|
|
pidl1 = ILFindLastID(pidl1);
|
|
_OnFSNotifyRemove(pidl1);
|
|
_RememberOrder();
|
|
fSizeChanged = TRUE;
|
|
break;
|
|
|
|
case SHCNE_RENAMEFOLDER:
|
|
TraceMsg(TF_SFTBAR, "CSFTBar::OnTranslateChange: RenameFolder");
|
|
// Break if notif is handled or if this is not for our kid.
|
|
//
|
|
hr = _OnRenameFolder(pidl1, pidl2);
|
|
if (S_OK == hr)
|
|
{
|
|
fSizeChanged = TRUE;
|
|
break;
|
|
}
|
|
|
|
TraceMsg(TF_SFTBAR, "CSFTBar::OnTranslateChange: RenameFolder Falling through to RenameItem");
|
|
// fall through
|
|
case SHCNE_RENAMEITEM:
|
|
{
|
|
TraceMsg(TF_SFTBAR, "CSFTBar::OnTranslateChange: RenameItem");
|
|
BOOL fOurKid1, fOurKid2;
|
|
LPCITEMIDLIST p1 = pidl1;
|
|
LPCITEMIDLIST p2 = pidl2;
|
|
|
|
pidl1 = ILFindLastID(pidl1);
|
|
pidl2 = ILFindLastID(pidl2);
|
|
|
|
// An item can be renamed out of this folder.
|
|
// Convert that into a remove.
|
|
//
|
|
|
|
fOurKid1 = _IsChildID(p1, TRUE);
|
|
fOurKid2 = _IsChildID(p2, TRUE);
|
|
if (fOurKid1 && fOurKid2)
|
|
{
|
|
TraceMsg(TF_SFTBAR, "CSFTBar::OnTranslateChange: Rename: Both are children");
|
|
_OnFSNotifyRename(pidl1, pidl2);
|
|
fSizeChanged = TRUE;
|
|
hr = S_OK;
|
|
break;
|
|
}
|
|
else if (fOurKid1)
|
|
{
|
|
TraceMsg(TF_SFTBAR, "CSFTBar::OnTranslateChange: Rename: Only one is a child. Removing pidl 1");
|
|
_OnFSNotifyRemove(pidl1);
|
|
fSizeChanged = TRUE;
|
|
break;
|
|
}
|
|
else if (fOurKid2)
|
|
{
|
|
// An item can be renamed into this folder.
|
|
// Convert that into an add.
|
|
//
|
|
TraceMsg(TF_SFTBAR, "CSFTBar::OnTranslateChange: Rename: Only one is a child. Adding pidl2");
|
|
_OnFSNotifyAdd(pidl2, FSNA_ADDDEFAULT, 0);
|
|
fSizeChanged = TRUE;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// (we get here for guys below us who we don't care about,
|
|
// and also for the fallthru from SHCNE_RENAMEFOLDER)
|
|
TraceMsg(TF_SFTBAR, "CSFTBar::OnTranslateChange: Rename: Not our children");
|
|
/*NOTHING*/
|
|
hr = E_FAIL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SHCNE_MEDIAINSERTED:
|
|
case SHCNE_MEDIAREMOVED:
|
|
case SHCNE_NETUNSHARE:
|
|
if (_IsEqualID(pidl1))
|
|
goto HandleUpdateDir;
|
|
|
|
case SHCNE_NETSHARE:
|
|
case SHCNE_UPDATEITEM:
|
|
if (_IsChildID(pidl1, TRUE))
|
|
{
|
|
pidl1 = ILFindLastID(pidl1);
|
|
|
|
_OnFSNotifyUpdate(pidl1);
|
|
fSizeChanged = TRUE;
|
|
}
|
|
break;
|
|
|
|
case SHCNE_UPDATEDIR:
|
|
// in OnChange we picked off update dir notify and we didn't translate ids
|
|
// now we can use ILIsEqual -- translate ids won't translate pidls in case
|
|
// of update dir because it looks for immediate child of its, and fails when
|
|
// it receives its own pidl
|
|
|
|
// NOTE: When sftbar is registered recursivly, we only get the pidl of the
|
|
// top pane. It is forwarded down to the children. Since this is now a "Child"
|
|
// of the top pane, we check to see if this pidl is a child of that pidl, hence the
|
|
// ILIsParent(pidl1, _pidl)
|
|
// HACKHACK, HUGE HACK: normaly w/ update dir pidl2 is NULL but in start menu
|
|
// augmergeisf can change some other notify (e.g. rename folder) to update dir
|
|
// in which case pidl2 is not null and we have to see if it is our child to do the
|
|
// update (11/18/98) reljai
|
|
if (_IsEqualID(pidl1) || // Calling UpdateDir on _THIS_ folder
|
|
_IsChildID(pidl1, FALSE) || // FEATURE (lamadio) Is this needed?
|
|
_IsChildID(pidl2, FALSE) || // A changed to update (see comment)
|
|
_IsParentID(pidl1)) // Some parent in the chain (because it's recursive)
|
|
{
|
|
HandleUpdateDir:
|
|
// NOTE: if a series of UPDATEIMAGE notifies gets
|
|
// translated to UPDATEDIR and we flicker-perf
|
|
// _FillToolbar, we may lose image updates
|
|
// (in which case, _Refresh would fix it)
|
|
//
|
|
_SetDirty(TRUE);
|
|
_FillToolbar();
|
|
}
|
|
break;
|
|
|
|
case SHCNE_ASSOCCHANGED:
|
|
IEInvalidateImageList(); // We may need to use different icons.
|
|
_Refresh(); // full refresh for now.
|
|
break;
|
|
|
|
case SHCNE_UPDATEIMAGE: // global
|
|
if (pidl1)
|
|
{
|
|
int iImage = *(int UNALIGNED *)((BYTE *)pidl1 + 2);
|
|
|
|
IEInvalidateImageList(); // We may need to use different icons.
|
|
if (pidl2)
|
|
{
|
|
iImage = SHHandleUpdateImage(pidl2);
|
|
if (iImage == -1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (iImage == -1 || TBHasImage(_hwndTB, iImage))
|
|
{
|
|
_UpdateIconSize(_uIconSize, TRUE);
|
|
_Refresh();
|
|
}
|
|
fSizeChanged = TRUE;
|
|
}
|
|
else
|
|
{
|
|
_Refresh();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
if (fSizeChanged)
|
|
{
|
|
if (_hwndPager)
|
|
SendMessage(_hwndPager, PGMP_RECALCSIZE, (WPARAM) 0, (LPARAM) 0);
|
|
_ToolbarChanged();
|
|
}
|
|
|
|
CleanUp:
|
|
return hr;
|
|
}
|
|
|
|
BOOL TBHasImage(HWND hwnd, int iImageIndex)
|
|
{
|
|
BOOL fRefresh = FALSE;
|
|
for (int i = ToolBar_ButtonCount(hwnd) - 1 ; i >= 0 ; i--)
|
|
{
|
|
TBBUTTON tbb;
|
|
if (SendMessage(hwnd, TB_GETBUTTON, i, (LPARAM)&tbb))
|
|
{
|
|
if (tbb.iBitmap == iImageIndex)
|
|
{
|
|
fRefresh = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return fRefresh;
|
|
}
|
|
|
|
void CSFToolbar::_SetToolbarState()
|
|
{
|
|
SHSetWindowBits(_hwndTB, GWL_STYLE, TBSTYLE_LIST,
|
|
(_uIconSize != ISFBVIEWMODE_SMALLICONS || _fNoShowText) ? 0 : TBSTYLE_LIST);
|
|
}
|
|
|
|
int CSFToolbar::_DefaultInsertIndex()
|
|
{
|
|
return DA_LAST;
|
|
}
|
|
|
|
BOOL CSFToolbar::_IsParentID(LPCITEMIDLIST pidl)
|
|
{
|
|
// Is the pidl passed in a parent of one of the IDs in the namespace
|
|
// or the only one i've got?
|
|
if (_ptscn)
|
|
return S_OK == _ptscn->IsEqualID(NULL, pidl);
|
|
else
|
|
return ILIsParent(pidl, _pidl, FALSE);
|
|
}
|
|
|
|
BOOL CSFToolbar::_IsEqualID(LPCITEMIDLIST pidl)
|
|
{
|
|
if (_ptscn)
|
|
return S_OK == _ptscn->IsEqualID(pidl, NULL);
|
|
else
|
|
return ILIsEqual(_pidl, pidl);
|
|
}
|
|
|
|
BOOL CSFToolbar::_IsChildID(LPCITEMIDLIST pidlChild, BOOL fImmediate)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
if (pidlChild)
|
|
{
|
|
if (_ptscn)
|
|
fRet = S_OK == _ptscn->IsChildID(pidlChild, fImmediate);
|
|
else
|
|
fRet = ILIsParent(_pidl, pidlChild, fImmediate);
|
|
}
|
|
return fRet;
|
|
}
|
|
|
|
void CSFToolbar::v_CalcWidth(int* pcxMin, int* pcxMax)
|
|
{
|
|
ASSERT(IS_VALID_WRITE_PTR(pcxMin, int));
|
|
ASSERT(IS_VALID_WRITE_PTR(pcxMax, int));
|
|
// Calculate a decent button width given current state
|
|
HIMAGELIST himl;
|
|
int cxMax = 0;
|
|
int cxMin = 0;
|
|
|
|
himl = (HIMAGELIST)SendMessage(_hwndTB, TB_GETIMAGELIST, 0, 0);
|
|
if (himl)
|
|
{
|
|
int cy;
|
|
// Start with the width of the button
|
|
ImageList_GetIconSize(himl, &cxMax, &cy);
|
|
|
|
// We want at least a bit of space around the icon
|
|
if (_uIconSize != ISFBVIEWMODE_SMALLICONS)
|
|
cxMax += 20;
|
|
else
|
|
cxMax += 4 * GetSystemMetrics(SM_CXEDGE);
|
|
|
|
}
|
|
|
|
// Add in any additional space needed
|
|
// Text takes up a bit more space
|
|
if (!_fNoShowText)
|
|
{
|
|
cxMax += 20;
|
|
|
|
// Horizontal text takes up a lot
|
|
// if we're smallicon with text (horizontal button)
|
|
// mode, use the minimized metric to mimic the taskbar
|
|
if (_uIconSize == ISFBVIEWMODE_SMALLICONS)
|
|
cxMax = GetSystemMetrics(SM_CXMINIMIZED);
|
|
}
|
|
|
|
*pcxMin = cxMin;
|
|
*pcxMax = cxMax;
|
|
}
|
|
|
|
// Adjust buttons based on current state.
|
|
//
|
|
void CSFToolbar::_UpdateButtons()
|
|
{
|
|
if (_hwndTB)
|
|
{
|
|
// set "list" (text on right) or not (text underneath)
|
|
// NOTE: list mode always displays some text, don't do it if no text
|
|
_SetToolbarState();
|
|
|
|
v_CalcWidth(&_cxMin, &_cxMax);
|
|
|
|
SendMessage(_hwndTB, TB_SETBUTTONWIDTH, 0, MAKELONG(_cxMin, _cxMax));
|
|
|
|
// We just changed the layout
|
|
//
|
|
SendMessage(_hwndTB, TB_AUTOSIZE, 0, 0);
|
|
if (_hwndPager)
|
|
{
|
|
LRESULT lButtonSize = SendMessage(_hwndTB, TB_GETBUTTONSIZE, 0, 0);
|
|
Pager_SetScrollInfo(_hwndPager, 50, 1, HIWORD(lButtonSize));
|
|
SendMessage(_hwndPager, PGMP_RECALCSIZE, (WPARAM) 0, (LPARAM) 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Helper function that calls IShellFolder::GetUIObjectOf().
|
|
|
|
Returns: pointer to the requested interface
|
|
NULL if failed
|
|
*/
|
|
void *CSFToolbar::_GetUIObjectOfPidl(LPCITEMIDLIST pidl, REFIID riid)
|
|
{
|
|
LPCITEMIDLIST * apidl = &pidl;
|
|
void *pv;
|
|
if (FAILED(_psf->GetUIObjectOf(GetHWNDForUIObject(), 1, apidl, riid, 0, &pv)))
|
|
{
|
|
pv = NULL;
|
|
}
|
|
|
|
return pv;
|
|
}
|
|
|
|
INT_PTR CALLBACK CSFToolbar::_RenameDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
ASSERT(lParam);
|
|
SetWindowLongPtr(hDlg, DWLP_USER, lParam);
|
|
EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
|
|
// cross-lang platform support
|
|
SHSetDefaultDialogFont(hDlg, IDD_NAME);
|
|
HWND hwndEdit = GetDlgItem(hDlg, IDD_NAME);
|
|
SendMessage(hwndEdit, EM_LIMITTEXT, MAX_PATH - 1, 0);
|
|
|
|
TCHAR szText[MAX_PATH + 80];
|
|
TCHAR szTemplate[80];
|
|
HWND hwndLabel = GetDlgItem(hDlg, IDD_PROMPT);
|
|
GetWindowText(hwndLabel, szTemplate, ARRAYSIZE(szTemplate));
|
|
StringCchPrintf(szText, ARRAYSIZE(szText), szTemplate, lParam); // ok to truncate
|
|
SetWindowText(hwndLabel, szText);
|
|
SetWindowText(hwndEdit, (LPTSTR)lParam);
|
|
break;
|
|
}
|
|
|
|
case WM_DESTROY:
|
|
SHRemoveDefaultDialogFont(hDlg);
|
|
return FALSE;
|
|
|
|
case WM_COMMAND:
|
|
switch (GET_WM_COMMAND_ID(wParam, lParam))
|
|
{
|
|
case IDD_NAME:
|
|
{
|
|
if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_UPDATE)
|
|
{
|
|
LPTSTR lpstrName = (LPTSTR) GetWindowLongPtr(hDlg, DWLP_USER);
|
|
EnableOKButtonFromID(hDlg, IDD_NAME);
|
|
GetDlgItemText(hDlg, IDD_NAME, lpstrName, MAX_PATH);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IDOK:
|
|
{
|
|
TCHAR szTmp[MAX_PATH];
|
|
|
|
HRESULT hr = StringCchCopy(szTmp, ARRAYSIZE(szTmp), (LPTSTR)GetWindowLongPtr(hDlg, DWLP_USER));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (PathCleanupSpec(NULL,szTmp))
|
|
{
|
|
HWND hwnd;
|
|
|
|
ShellMessageBox(HINST_THISDLL, hDlg,
|
|
MAKEINTRESOURCE(IDS_FAVS_INVALIDFN),
|
|
MAKEINTRESOURCE(IDS_FAVS_ADDTOFAVORITES), MB_OK | MB_ICONHAND);
|
|
hwnd = GetDlgItem(hDlg, IDD_NAME);
|
|
SetWindowText(hwnd, TEXT("\0"));
|
|
EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
|
|
SetFocus(hwnd);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// fall through
|
|
|
|
case IDCANCEL:
|
|
EndDialog(hDlg, GET_WM_COMMAND_ID(wParam, lParam));
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// This window proc is used for a temporary worker window that is used to position dialogs
|
|
// as well as maintain the correct Z-Order
|
|
// NOTE: This is used in mnfolder as well.
|
|
LRESULT CALLBACK HiddenWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch(uMsg)
|
|
{
|
|
// Make sure activation tracks back to the parent.
|
|
case WM_ACTIVATE:
|
|
{
|
|
if (WA_ACTIVE != LOWORD(wParam))
|
|
goto DefWnd;
|
|
|
|
SetActiveWindow(GetParent(hwnd));
|
|
return FALSE;
|
|
}
|
|
|
|
case WM_WINDOWPOSCHANGING:
|
|
{
|
|
WINDOWPOS* pwp = (WINDOWPOS*)lParam;
|
|
pwp->flags |= SWP_NOOWNERZORDER;
|
|
}
|
|
break;
|
|
}
|
|
|
|
DefWnd:
|
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
|
|
}
|
|
|
|
HWND CSFToolbar::CreateWorkerWindow()
|
|
{
|
|
if (!_hwndWorkerWindow)
|
|
{
|
|
_hwndWorkerWindow = SHCreateWorkerWindow(HiddenWndProc, GetHWNDForUIObject(), WS_EX_TOOLWINDOW /*| WS_EX_TOPMOST */, WS_POPUP, 0, _hwndTB);
|
|
}
|
|
|
|
return _hwndWorkerWindow;
|
|
}
|
|
|
|
HRESULT CSFToolbar::_OnRename(POINT *ppt, int id)
|
|
{
|
|
ASSERT(_psf);
|
|
|
|
TCHAR szName[MAX_PATH];
|
|
LPITEMIDLIST pidl = ILClone(_IDToPidl(id));
|
|
if (!pidl)
|
|
return E_OUTOFMEMORY;
|
|
|
|
_ObtainPIDLName(pidl, szName, ARRAYSIZE(szName));
|
|
|
|
// create a temp window so that placement of the dialog will be close to the point.
|
|
// do this so that we'll use USER's code to get placement correctly w/ respect to multimon and work area
|
|
_hwndWorkerWindow = CreateWorkerWindow();
|
|
|
|
SetWindowPos(_hwndWorkerWindow, NULL, ppt->x, ppt->y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
|
|
// Now the horrible work of disabling our UI parent window so we can go modal.
|
|
// In an ideal world, we would pass our true parent window and USER will do
|
|
// all the work of modality, but we have to use our worker window thingie
|
|
// to get the dialog positioned correctly with respect to multimon,
|
|
// so we have to find the modal parent and disable him the hard way.
|
|
//
|
|
IUnknown *punkSite;
|
|
IUnknown *punkTLB;
|
|
|
|
// Doesn't matter what we SAFECAST "this" to; just pick something to keep the compiler happy
|
|
IUnknown_GetSite(SAFECAST(this, IWinEventHandler*), IID_PPV_ARG(IUnknown, &punkSite));
|
|
IUnknown_QueryService(punkSite, SID_STopLevelBrowser, IID_PPV_ARG(IUnknown, &punkTLB));
|
|
|
|
// Tell OLE to go modal
|
|
HRESULT hrModeless = IUnknown_EnableModless(punkTLB, FALSE);
|
|
|
|
// Tell USER to go modal
|
|
HWND hwndDisable;
|
|
IUnknown_GetWindow(punkTLB, &hwndDisable);
|
|
BOOL bPrevEnabled = FALSE;
|
|
while (hwndDisable && (GetWindowLong(hwndDisable, GWL_STYLE) & WS_CHILD))
|
|
hwndDisable = GetParent(hwndDisable);
|
|
if (hwndDisable)
|
|
bPrevEnabled = !EnableWindow(hwndDisable, FALSE); // return value of EnableWindow needs to be negated.
|
|
|
|
while (1)
|
|
{
|
|
if (DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_ISFBANDRENAME), _hwndWorkerWindow, _RenameDlgProc, (LPARAM)szName) != IDOK)
|
|
break;
|
|
|
|
WCHAR wsz[MAX_PATH];
|
|
SHTCharToUnicode(szName, wsz, ARRAYSIZE(wsz));
|
|
|
|
// Must re-assert TOPMOSTness so SetNameOf UI will be visible.
|
|
// (We lose it when the user dismisses the dialog box above.)
|
|
// Curiously, the worker window is owned by the app's window, not the
|
|
// menu, so the worker window ends up fighting with the menu to see who is on top
|
|
SetWindowPos(_hwndWorkerWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
|
|
if (SUCCEEDED(_psf->SetNameOf(_hwndWorkerWindow, pidl, wsz, 0, NULL)))
|
|
{
|
|
SHChangeNotifyHandleEvents();
|
|
_SaveOrderStream();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// (must undo modality in reverse order)
|
|
|
|
// Tell USER to return to modeless (as appropriate)
|
|
if (hwndDisable)
|
|
EnableWindow(hwndDisable, bPrevEnabled);
|
|
|
|
// Tell OLE to return to modeless (as appropriate)
|
|
if (SUCCEEDED(hrModeless))
|
|
IUnknown_EnableModless(punkTLB, TRUE);
|
|
|
|
ATOMICRELEASE(punkTLB);
|
|
ATOMICRELEASE(punkSite);
|
|
|
|
ILFree(pidl);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
BOOL CSFToolbar::_UpdateIconSize(UINT uIconSize, BOOL fUpdateButtons)
|
|
{
|
|
BOOL fChanged = (_uIconSize != uIconSize);
|
|
|
|
_uIconSize = uIconSize;
|
|
|
|
TraceMsg(TF_BAND, "ISFBand::_UpdateIconSize going %hs", (_uIconSize == ISFBVIEWMODE_LARGEICONS ? "LARGE" : (_uIconSize == ISFBVIEWMODE_SMALLICONS ? "SMALL" : "LOGOS")));
|
|
|
|
if (_hwndTB)
|
|
{
|
|
ATOMICRELEASE(_piml);
|
|
|
|
if (!_fNoIcons)
|
|
{
|
|
int iImageList = (_uIconSize == ISFBVIEWMODE_LARGEICONS) ? SHIL_LARGE : SHIL_SYSSMALL;
|
|
SHGetImageList(iImageList, IID_PPV_ARG(IImageList, &_piml));
|
|
}
|
|
|
|
// sending a null himl is significant.. it means no image list
|
|
SendMessage(_hwndTB, TB_SETIMAGELIST, 0, (LPARAM)_piml);
|
|
|
|
if (fUpdateButtons)
|
|
_UpdateButtons();
|
|
}
|
|
|
|
return fChanged;
|
|
}
|
|
|
|
HMENU CSFToolbar::_GetContextMenu(IContextMenu* pcm, int* pid)
|
|
{
|
|
HMENU hmenu = CreatePopupMenu();
|
|
if (hmenu)
|
|
{
|
|
UINT fFlags = CMF_CANRENAME;
|
|
if (0 > GetKeyState(VK_SHIFT))
|
|
fFlags |= CMF_EXTENDEDVERBS;
|
|
|
|
pcm->QueryContextMenu(hmenu, 0, *pid, CONTEXTMENU_IDCMD_LAST, fFlags);
|
|
}
|
|
return hmenu;
|
|
}
|
|
|
|
void CSFToolbar::_OnDefaultContextCommand(int idCmd)
|
|
{
|
|
}
|
|
|
|
HRESULT CSFToolbar::_GetTopBrowserWindow(HWND* phwnd)
|
|
{
|
|
IUnknown * punkSite;
|
|
|
|
HRESULT hr = IUnknown_GetSite(SAFECAST(this, IWinEventHandler*), IID_PPV_ARG(IUnknown, &punkSite));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = SHGetTopBrowserWindow(punkSite, phwnd);
|
|
punkSite->Release();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSFToolbar::_OnOpen(int id, BOOL fExplore)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
LPCITEMIDLIST pidl = _IDToPidl(id);
|
|
if (pidl)
|
|
{
|
|
IUnknown* punkSite;
|
|
|
|
hr = IUnknown_GetSite(SAFECAST(this, IWinEventHandler*), IID_PPV_ARG(IUnknown, &punkSite));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DWORD dwFlags = SBSP_DEFBROWSER | SBSP_DEFMODE;
|
|
if (fExplore)
|
|
dwFlags |= SBSP_EXPLOREMODE;
|
|
|
|
hr = SHNavigateToFavorite(_psf, pidl, punkSite, dwFlags);
|
|
|
|
punkSite->Release();
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSFToolbar::_HandleSpecialCommand(IContextMenu* pcm, PPOINT ppt, int id, int idCmd)
|
|
{
|
|
TCHAR szCommandString[40];
|
|
|
|
HRESULT hr = ContextMenu_GetCommandStringVerb(pcm, idCmd, szCommandString, ARRAYSIZE(szCommandString));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (lstrcmpi(szCommandString, TEXT("rename")) == 0)
|
|
return _OnRename(ppt, id);
|
|
else if (lstrcmpi(szCommandString, TEXT("open")) == 0)
|
|
return _OnOpen(id, FALSE);
|
|
else if (lstrcmpi(szCommandString, TEXT("explore")) == 0)
|
|
return _OnOpen(id, TRUE);
|
|
}
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
LRESULT CSFToolbar::_DoContextMenu(IContextMenu* pcm, LPPOINT ppt, int id, LPRECT prcExclude)
|
|
{
|
|
LRESULT lres = 0;
|
|
int idCmdFirst = CONTEXTMENU_IDCMD_FIRST;
|
|
HMENU hmContext = _GetContextMenu(pcm, &idCmdFirst);
|
|
if (hmContext)
|
|
{
|
|
int idCmd;
|
|
|
|
if (_hwndToolTips)
|
|
SendMessage(_hwndToolTips, TTM_ACTIVATE, FALSE, 0L);
|
|
|
|
TPMPARAMS tpm;
|
|
TPMPARAMS * ptpm = NULL;
|
|
|
|
if (prcExclude)
|
|
{
|
|
tpm.cbSize = sizeof(tpm);
|
|
tpm.rcExclude = *((LPRECT)prcExclude);
|
|
ptpm = &tpm;
|
|
}
|
|
idCmd = TrackPopupMenuEx(hmContext,
|
|
TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN,
|
|
ppt->x, ppt->y, _hwndTB, ptpm);
|
|
|
|
if (_hwndToolTips)
|
|
SendMessage(_hwndToolTips, TTM_ACTIVATE, TRUE, 0L);
|
|
|
|
if (idCmd)
|
|
{
|
|
// FUSION: When we call out to 3rd party code we want it to use
|
|
// the process default context. This means that the 3rd party code will get
|
|
// v5 in the explorer process. However, if shell32 is hosted in a v6 process,
|
|
// then the 3rd party code will still get v6.
|
|
ULONG_PTR cookie = 0;
|
|
ActivateActCtx(NULL, &cookie);
|
|
|
|
if (idCmd < idCmdFirst)
|
|
{
|
|
_OnDefaultContextCommand(idCmd);
|
|
}
|
|
else
|
|
{
|
|
idCmd -= idCmdFirst;
|
|
|
|
if (_HandleSpecialCommand(pcm, ppt, id, idCmd) != S_OK)
|
|
{
|
|
_hwndWorkerWindow = CreateWorkerWindow();
|
|
|
|
SetWindowPos(_hwndWorkerWindow, NULL, ppt->x, ppt->y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
|
|
CMINVOKECOMMANDINFO ici = {
|
|
sizeof(CMINVOKECOMMANDINFO),
|
|
0,
|
|
_hwndWorkerWindow,
|
|
MAKEINTRESOURCEA(idCmd),
|
|
NULL, NULL,
|
|
SW_NORMAL,
|
|
};
|
|
|
|
pcm->InvokeCommand(&ici);
|
|
}
|
|
}
|
|
if (cookie != 0)
|
|
{
|
|
DeactivateActCtx(0, cookie);
|
|
}
|
|
}
|
|
|
|
// if we get this far
|
|
// we need to return handled so that WM_CONTEXTMENU doesn't come through
|
|
lres = 1;
|
|
|
|
DestroyMenu(hmContext);
|
|
}
|
|
|
|
return lres;
|
|
}
|
|
|
|
|
|
LRESULT CSFToolbar::_OnContextMenu(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LRESULT lres = 0;
|
|
RECT rc;
|
|
LPRECT prcExclude = NULL;
|
|
POINT pt;
|
|
int i;
|
|
|
|
if (lParam != (LPARAM)-1)
|
|
{
|
|
pt.x = GET_X_LPARAM(lParam);
|
|
pt.y = GET_Y_LPARAM(lParam);
|
|
|
|
POINT pt2 = pt;
|
|
MapWindowPoints(HWND_DESKTOP, _hwndTB, &pt2, 1);
|
|
|
|
i = ToolBar_HitTest(_hwndTB, &pt2);
|
|
}
|
|
else
|
|
{
|
|
// keyboard context menu.
|
|
i = (int)SendMessage(_hwndTB, TB_GETHOTITEM, 0, 0);
|
|
if (i >= 0)
|
|
{
|
|
SendMessage(_hwndTB, TB_GETITEMRECT, i, (LPARAM)&rc);
|
|
MapWindowPoints(_hwndTB, HWND_DESKTOP, (LPPOINT)&rc, 2);
|
|
pt.x = rc.left;
|
|
pt.y = rc.bottom;
|
|
prcExclude = &rc;
|
|
}
|
|
}
|
|
|
|
TraceMsg(TF_BAND, "NM_RCLICK %d,%d = %d", pt.x, pt.y, i);
|
|
|
|
if (i >= 0)
|
|
{
|
|
UINT id = _IndexToID(i);
|
|
if (-1 != id)
|
|
{
|
|
LPCITEMIDLIST pidl = _IDToPidl(id, NULL);
|
|
if (pidl)
|
|
{
|
|
LPCONTEXTMENU pcm = (LPCONTEXTMENU)_GetUIObjectOfPidl(pidl, IID_IContextMenu);
|
|
if (pcm)
|
|
{
|
|
// grab pcm2 for owner draw support
|
|
pcm->QueryInterface(IID_PPV_ARG(IContextMenu2, &_pcm2));
|
|
|
|
ToolBar_MarkButton(_hwndTB, id, TRUE);
|
|
|
|
lres = _DoContextMenu(pcm, &pt, id, prcExclude);
|
|
|
|
ToolBar_MarkButton(_hwndTB, id, FALSE);
|
|
|
|
if (lres)
|
|
_FlushNotifyMessages(_hwndTB);
|
|
|
|
ATOMICRELEASE(_pcm2);
|
|
pcm->Release();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return lres;
|
|
}
|
|
|
|
|
|
LRESULT CSFToolbar::_OnCustomDraw(NMCUSTOMDRAW* pnmcd)
|
|
{
|
|
return CDRF_DODEFAULT;
|
|
}
|
|
|
|
void CSFToolbar::_OnDragBegin(int iItem, DWORD dwPreferredEffect)
|
|
{
|
|
LPCITEMIDLIST pidl = _IDToPidl(iItem, &_iDragSource);
|
|
ToolBar_SetHotItem(_hwndTB, _iDragSource);
|
|
|
|
if (_hwndTB && pidl)
|
|
DragDrop(_hwndTB, _psf, pidl, dwPreferredEffect, NULL);
|
|
|
|
_iDragSource = -1;
|
|
}
|
|
|
|
LRESULT CSFToolbar::_OnHotItemChange(NMTBHOTITEM * pnm)
|
|
{
|
|
LPNMTBHOTITEM lpnmhi = (LPNMTBHOTITEM)pnm;
|
|
|
|
if (_hwndPager && (lpnmhi->dwFlags & (HICF_ARROWKEYS | HICF_ACCELERATOR)))
|
|
{
|
|
int iOldPos, iNewPos;
|
|
RECT rc, rcPager;
|
|
int heightPager;
|
|
|
|
int iSelected = lpnmhi->idNew;
|
|
iOldPos = (int)SendMessage(_hwndPager, PGM_GETPOS, (WPARAM)0, (LPARAM)0);
|
|
iNewPos = iOldPos;
|
|
SendMessage(_hwndTB, TB_GETITEMRECT, (WPARAM)iSelected, (LPARAM)&rc);
|
|
|
|
if (rc.top < iOldPos)
|
|
{
|
|
iNewPos =rc.top;
|
|
}
|
|
|
|
GetClientRect(_hwndPager, &rcPager);
|
|
heightPager = RECTHEIGHT(rcPager);
|
|
|
|
if (rc.top >= iOldPos + heightPager)
|
|
{
|
|
iNewPos += (rc.bottom - (iOldPos + heightPager)) ;
|
|
}
|
|
|
|
if (iNewPos != iOldPos)
|
|
SendMessage(_hwndPager, PGM_SETPOS, (WPARAM)0, (LPARAM)iNewPos);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CSFToolbar::_OnToolTipsCreated(NMTOOLTIPSCREATED* pnm)
|
|
{
|
|
_hwndToolTips = pnm->hwndToolTips;
|
|
SHSetWindowBits(_hwndToolTips, GWL_STYLE, TTS_ALWAYSTIP | TTS_TOPMOST | TTS_NOPREFIX, TTS_ALWAYSTIP | TTS_TOPMOST | TTS_NOPREFIX);
|
|
|
|
// set the AutoPopTime (the duration of showing the tooltip) to a large value
|
|
SendMessage(_hwndToolTips, TTM_SETDELAYTIME, TTDT_AUTOPOP, (LPARAM)MAXSHORT);
|
|
}
|
|
|
|
LRESULT CSFToolbar::_OnNotify(LPNMHDR pnm)
|
|
{
|
|
LRESULT lres = 0;
|
|
|
|
//The following statement traps all pager control notification messages.
|
|
if ((pnm->code <= PGN_FIRST) && (pnm->code >= PGN_LAST))
|
|
{
|
|
return SendMessage(_hwndTB, WM_NOTIFY, (WPARAM)0, (LPARAM)pnm);
|
|
}
|
|
|
|
switch (pnm->code)
|
|
{
|
|
case TBN_DRAGOUT:
|
|
{
|
|
TBNOTIFY *ptbn = (TBNOTIFY*)pnm;
|
|
_OnDragBegin(ptbn->iItem, 0);
|
|
lres = 1;
|
|
break;
|
|
}
|
|
|
|
case TBN_HOTITEMCHANGE:
|
|
_OnHotItemChange((LPNMTBHOTITEM)pnm);
|
|
break;
|
|
|
|
|
|
case TBN_GETINFOTIP:
|
|
{
|
|
LPNMTBGETINFOTIP pnmTT = (LPNMTBGETINFOTIP)pnm;
|
|
UINT uiCmd = pnmTT->iItem;
|
|
DWORD dwFlags = _fNoShowText ? QITIPF_USENAME | QITIPF_LINKNOTARGET : QITIPF_LINKNOTARGET;
|
|
|
|
if (!GetInfoTipEx(_psf, dwFlags, _IDToPidl(uiCmd), pnmTT->pszText, pnmTT->cchTextMax))
|
|
{
|
|
TBBUTTONINFO tbbi;
|
|
|
|
tbbi.cbSize = sizeof(tbbi);
|
|
tbbi.dwMask = TBIF_TEXT;
|
|
tbbi.pszText = pnmTT->pszText;
|
|
tbbi.cchText = pnmTT->cchTextMax;
|
|
|
|
lres = (-1 != ToolBar_GetButtonInfo(_hwndTB, uiCmd, &tbbi));
|
|
}
|
|
break;
|
|
}
|
|
|
|
//WARNING: Right now I am calling the same function for both A and W version if this notification supports
|
|
// Strings then it needs to thunk. Right now its only used for image
|
|
case TBN_GETDISPINFOA:
|
|
_OnGetDispInfo(pnm, FALSE);
|
|
break;
|
|
case TBN_GETDISPINFOW:
|
|
_OnGetDispInfo(pnm, TRUE);
|
|
break;
|
|
|
|
case NM_TOOLTIPSCREATED:
|
|
_OnToolTipsCreated((NMTOOLTIPSCREATED*)pnm);
|
|
break;
|
|
|
|
case NM_RCLICK:
|
|
lres = _OnContextMenu(NULL, GetMessagePos());
|
|
break;
|
|
|
|
case NM_CUSTOMDRAW:
|
|
return _OnCustomDraw((NMCUSTOMDRAW*)pnm);
|
|
|
|
}
|
|
|
|
return lres;
|
|
}
|
|
|
|
DWORD CSFToolbar::_GetAttributesOfPidl(LPCITEMIDLIST pidl, DWORD dwAttribs)
|
|
{
|
|
if (FAILED(_psf->GetAttributesOf(1, &pidl, &dwAttribs)))
|
|
dwAttribs = 0;
|
|
|
|
return dwAttribs;
|
|
}
|
|
|
|
PIBDATA CSFToolbar::_IDToPibData(UINT uiCmd, int * piPos)
|
|
{
|
|
PIBDATA pibdata = NULL;
|
|
|
|
// Initialize to NULL in case the GetButtonInfo Fails.
|
|
TBBUTTONINFO tbbi = {0};
|
|
|
|
tbbi.cbSize = sizeof(tbbi);
|
|
tbbi.dwMask = TBIF_LPARAM;
|
|
|
|
int iPos = ToolBar_GetButtonInfo(_hwndTB, uiCmd, &tbbi);
|
|
if (iPos >= 0)
|
|
pibdata = (PIBDATA)tbbi.lParam;
|
|
|
|
if (piPos)
|
|
*piPos = iPos;
|
|
|
|
return pibdata;
|
|
}
|
|
|
|
|
|
LPCITEMIDLIST CSFToolbar::_IDToPidl(UINT uiCmd, int *piPos)
|
|
{
|
|
LPCITEMIDLIST pidl;
|
|
PIBDATA pibdata = _IDToPibData(uiCmd, piPos);
|
|
|
|
if (pibdata)
|
|
pidl = pibdata->GetPidl();
|
|
else
|
|
pidl = NULL;
|
|
|
|
return pidl;
|
|
}
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: IWinEventHandler::OnWinEvent method
|
|
|
|
Processes messages passed on from the bandsite.
|
|
*/
|
|
HRESULT CSFToolbar::OnWinEvent(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plres)
|
|
{
|
|
*plres = 0;
|
|
// We are addref'n here because during the course of the
|
|
// Context menu, the view could be changed which free's the menu.
|
|
// We will release after we're sure the this pointer is no longer needed.
|
|
AddRef();
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_SYSCOLORCHANGE:
|
|
SendMessage(_hwndTB, uMsg, wParam, lParam);
|
|
InvalidateRect(_hwndTB, NULL, TRUE);
|
|
break;
|
|
|
|
case WM_PALETTECHANGED:
|
|
InvalidateRect(_hwndTB, NULL, FALSE);
|
|
SendMessage(_hwndTB, uMsg, wParam, lParam);
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
*plres = _OnCommand(wParam, lParam);
|
|
break;
|
|
|
|
case WM_NOTIFY:
|
|
*plres = _OnNotify((LPNMHDR)lParam);
|
|
break;
|
|
|
|
case WM_CONTEXTMENU:
|
|
*plres = _OnContextMenu(wParam, lParam);
|
|
break;
|
|
}
|
|
|
|
Release();
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
// Map the information loaded (or ctor) into _psf, [_pidl]
|
|
//
|
|
HRESULT CSFToolbar::_AfterLoad()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// if we have a pidl then we need to get ready
|
|
// for notifications...
|
|
//
|
|
if (_pidl)
|
|
{
|
|
// pidls must be rooted off the desktop
|
|
//
|
|
_fFSNotify = TRUE;
|
|
|
|
// shortcut -- just specifying a pidl is good enough
|
|
//
|
|
if (!_psf)
|
|
{
|
|
_fPSFBandDesktop = TRUE;
|
|
hr = IEBindToObject(_pidl, &_psf);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// IDropTarget implementation
|
|
|
|
HRESULT CSFToolbar::GetWindowsDDT(HWND * phwndLock, HWND * phwndScroll)
|
|
{
|
|
*phwndLock = _hwndTB;
|
|
*phwndScroll = _hwndTB;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CSFToolbar::HitTestDDT(UINT nEvent, LPPOINT ppt, DWORD_PTR * pdwId, DWORD *pdwEffect)
|
|
{
|
|
TBINSERTMARK tbim;
|
|
|
|
switch (nEvent)
|
|
{
|
|
case HTDDT_ENTER:
|
|
return S_OK;
|
|
|
|
case HTDDT_OVER:
|
|
{
|
|
int iButton = IBHT_BACKGROUND; // assume we hit the background
|
|
|
|
// if we're the source, this may be a move operation
|
|
//
|
|
*pdwEffect = (_iDragSource >= 0) ? DROPEFFECT_MOVE : DROPEFFECT_NONE;
|
|
if (!ToolBar_InsertMarkHitTest(_hwndTB, ppt, &tbim))
|
|
{
|
|
if (tbim.dwFlags & TBIMHT_BACKGROUND)
|
|
{
|
|
RECT rc;
|
|
GetClientRect(_hwndTB, &rc);
|
|
|
|
// are we outside the toolbar window entirely?
|
|
if (!PtInRect(&rc, *ppt))
|
|
{
|
|
// rebar already did the hittesting so we are on the rebar
|
|
// but not the toolbar => we are in the title part
|
|
if (!_AllowDropOnTitle())
|
|
{
|
|
// yes; don't allow drop here
|
|
iButton = IBHT_OUTSIDEWINDOW;
|
|
*pdwEffect = DROPEFFECT_NONE;
|
|
}
|
|
|
|
// set tbim.iButton to invalid value so we don't draw insert mark
|
|
tbim.iButton = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// nope, we hit a real button
|
|
//
|
|
if (tbim.iButton == _iDragSource)
|
|
{
|
|
iButton = IBHT_SOURCE; // don't drop on the source button
|
|
}
|
|
else
|
|
{
|
|
iButton = tbim.iButton;
|
|
}
|
|
tbim.iButton = IBHT_BACKGROUND;
|
|
|
|
// we never force a move operation if we're on a real button
|
|
*pdwEffect = DROPEFFECT_NONE;
|
|
}
|
|
}
|
|
|
|
*pdwId = iButton;
|
|
}
|
|
break;
|
|
|
|
case HTDDT_LEAVE:
|
|
// Reset
|
|
tbim.iButton = IBHT_BACKGROUND;
|
|
tbim.dwFlags = 0;
|
|
break;
|
|
|
|
default:
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// update ui
|
|
if (tbim.iButton != _tbim.iButton || tbim.dwFlags != _tbim.dwFlags)
|
|
{
|
|
if (ppt)
|
|
_tbim = tbim;
|
|
|
|
// for now I don't want to rely on non-filesystem IShellFolder
|
|
// implementations to call our OnChange method when a drop occurs,
|
|
// so don't even show the insert mark.
|
|
//
|
|
if (_fFSNotify || _iDragSource >= 0)
|
|
{
|
|
DAD_ShowDragImage(FALSE);
|
|
ToolBar_SetInsertMark(_hwndTB, &tbim);
|
|
DAD_ShowDragImage(TRUE);
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CSFToolbar::GetObjectDDT(DWORD_PTR dwId, REFIID riid, void ** ppvObj)
|
|
{
|
|
HRESULT hr = E_NOINTERFACE;
|
|
|
|
*ppvObj = NULL;
|
|
|
|
if ((IBHT_SOURCE == dwId) || (IBHT_OUTSIDEWINDOW == dwId))
|
|
{
|
|
// do nothing
|
|
}
|
|
else if (IBHT_BACKGROUND == dwId)
|
|
{
|
|
// nash:41937: not sure how, but _psf can be NULL...
|
|
if (EVAL(_psf))
|
|
hr = _psf->CreateViewObject(_hwndTB, riid, ppvObj);
|
|
}
|
|
else
|
|
{
|
|
LPCITEMIDLIST pidl = _IDToPidl((UINT)dwId, NULL);
|
|
|
|
if (pidl)
|
|
{
|
|
*ppvObj = _GetUIObjectOfPidl(pidl, riid);
|
|
|
|
if (*ppvObj)
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
|
|
//TraceMsg(TF_BAND, "SFToolbar::GetObject(%d) returns %x", dwId, hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSFToolbar::_SaveOrderStream()
|
|
{
|
|
if (_fChangedOrder)
|
|
{
|
|
// Notify everyone that the order changed
|
|
SHSendChangeMenuNotify(this, SHCNEE_ORDERCHANGED, 0, _pidl);
|
|
_fChangedOrder = FALSE;
|
|
return S_OK;
|
|
}
|
|
else
|
|
return S_FALSE;
|
|
}
|
|
|
|
void CSFToolbar::_Dropped(int nIndex, BOOL fDroppedOnSource)
|
|
{
|
|
_fDropped = TRUE;
|
|
_fChangedOrder = TRUE;
|
|
|
|
// Save new order stream
|
|
_SaveOrderStream();
|
|
|
|
if (fDroppedOnSource)
|
|
_FlushNotifyMessages(_hwndTB);
|
|
}
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: CDelegateDropTarget::OnDropDDT
|
|
|
|
*/
|
|
HRESULT CSFToolbar::OnDropDDT(IDropTarget *pdt, IDataObject *pdtobj, DWORD * pgrfKeyState, POINTL pt, DWORD *pdwEffect)
|
|
{
|
|
// Are we NOT the drag source?
|
|
if (_iDragSource == -1)
|
|
{
|
|
// No, we're not. Well, then the source may be the chevron menu
|
|
// representing the hidden items in this menu. Let's check
|
|
LPITEMIDLIST pidl;
|
|
if (SUCCEEDED(SHPidlFromDataObject2(pdtobj, &pidl)))
|
|
{
|
|
// We've got a pidl, Are we the parent? Do we have a button?
|
|
int iIndex;
|
|
if (ILIsParent(_pidl, pidl, TRUE) &&
|
|
SUCCEEDED(_GetButtonFromPidl(ILFindLastID(pidl), NULL, &iIndex, NULL)))
|
|
{
|
|
// We are the parent! Then let's copy that down and set it
|
|
// as the drag source so that down below we reorder.
|
|
_iDragSource = iIndex;
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
|
|
if (_iDragSource >= 0)
|
|
{
|
|
if (_fAllowReorder)
|
|
{
|
|
TraceMsg(TF_BAND, "SFToolbar::OnDrop reorder %d to %d %s", _iDragSource, _tbim.iButton, _tbim.dwFlags & TBIMHT_AFTER ? "A" : "B");
|
|
|
|
int iNewLocation = _tbim.iButton;
|
|
if (_tbim.dwFlags & TBIMHT_AFTER)
|
|
iNewLocation++;
|
|
|
|
if (iNewLocation > _iDragSource)
|
|
iNewLocation--;
|
|
|
|
if (ToolBar_MoveButton(_hwndTB, _iDragSource, iNewLocation))
|
|
{
|
|
PORDERITEM poi = (PORDERITEM)DPA_DeletePtr(_hdpa, v_TBIndexToDPAIndex(_iDragSource));
|
|
if (poi)
|
|
{
|
|
DPA_InsertPtr(_hdpa, v_TBIndexToDPAIndex(iNewLocation), poi);
|
|
|
|
OrderList_Reorder(_hdpa);
|
|
|
|
// If we're dropping again, then we don't need the _hdpaOrder...
|
|
OrderList_Destroy(&_hdpaOrder);
|
|
|
|
// A reorder has occurred. We need to use the order stream as the order...
|
|
_fHasOrder = TRUE;
|
|
_fDropping = TRUE;
|
|
_Dropped(iNewLocation, TRUE);
|
|
_fDropping = FALSE;
|
|
_RememberOrder();
|
|
_SetDirty(TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Don't forget to reset this!
|
|
_iDragSource = -1;
|
|
|
|
DragLeave();
|
|
}
|
|
else
|
|
{
|
|
// We want to override the default to be LINK (SHIFT+CONTROL)
|
|
if (0 == DataObj_GetDWORD(pdtobj, g_cfPreferredDropEffect, 0))
|
|
{
|
|
if (!(*pgrfKeyState & (MK_CONTROL | MK_SHIFT | MK_ALT)))
|
|
{
|
|
// NOTE: not all data objects will allow us to call SetData()
|
|
DataObj_SetDWORD(pdtobj, g_cfPreferredDropEffect, DROPEFFECT_LINK);
|
|
}
|
|
}
|
|
|
|
_fDropping = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
void CSFToolbar::_SortDPA(HDPA hdpa)
|
|
{
|
|
// If we don't have a _psf, then we certainly can't sort it
|
|
// If we don't have a hdpa, then we certainly can't sort it
|
|
// If the hdpa is empty, then there's no point in sorting it
|
|
if (_psf && hdpa && DPA_GetPtrCount(hdpa))
|
|
{
|
|
ORDERINFO oinfo;
|
|
oinfo.psf = _psf;
|
|
oinfo.psf->AddRef();
|
|
oinfo.dwSortBy = (_fNoNameSort ? OI_SORTBYORDINAL : OI_SORTBYNAME);
|
|
DPA_Sort(hdpa, OrderItem_Compare, (LPARAM)&oinfo);
|
|
oinfo.psf->Release();
|
|
}
|
|
}
|
|
|
|
void CSFToolbar::_RememberOrder()
|
|
{
|
|
OrderList_Destroy(&_hdpaOrder);
|
|
|
|
if (_hdpa)
|
|
{
|
|
_hdpaOrder = OrderList_Clone(_hdpa);
|
|
_SortDPA(_hdpaOrder);
|
|
}
|
|
}
|
|
|
|
HMENU CSFToolbar::_GetBaseContextMenu()
|
|
{
|
|
HMENU hmenu = SHLoadMenuPopup(HINST_THISDLL, MENU_ISFBAND);
|
|
// no logo view, remove the menu item...
|
|
HMENU hView = GetSubMenu(hmenu, 0);
|
|
DeleteMenu(hView, ISFBIDM_LOGOS, MF_BYCOMMAND);
|
|
return hmenu;
|
|
}
|
|
|
|
HMENU CSFToolbar::_GetContextMenu()
|
|
{
|
|
HMENU hmenuSrc = _GetBaseContextMenu();
|
|
if (hmenuSrc)
|
|
{
|
|
MENUITEMINFO mii;
|
|
|
|
mii.cbSize = sizeof(mii);
|
|
mii.fMask = MIIM_STATE;
|
|
mii.fState = MF_CHECKED;
|
|
|
|
UINT uCmdId = ISFBIDM_LOGOS;
|
|
if (_uIconSize != ISFBVIEWMODE_LOGOS)
|
|
uCmdId = (_uIconSize == ISFBVIEWMODE_LARGEICONS ? ISFBIDM_LARGE : ISFBIDM_SMALL);
|
|
|
|
SetMenuItemInfo(hmenuSrc, uCmdId, MF_BYCOMMAND, &mii);
|
|
if (!_fNoShowText)
|
|
SetMenuItemInfo(hmenuSrc, ISFBIDM_SHOWTEXT, MF_BYCOMMAND, &mii);
|
|
|
|
if (!_fFSNotify || !_pidl || ILIsEmpty(_pidl))
|
|
DeleteMenu(hmenuSrc, ISFBIDM_OPEN, MF_BYCOMMAND);
|
|
|
|
HMENU hView = GetSubMenu(hmenuSrc, 0);
|
|
DeleteMenu(hView, ISFBIDM_LOGOS, MF_BYCOMMAND);
|
|
}
|
|
|
|
return hmenuSrc;
|
|
}
|
|
// IContextMenu implementation
|
|
//
|
|
HRESULT CSFToolbar::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
|
|
{
|
|
HMENU hmenuSrc = _GetContextMenu();
|
|
int i = 0;
|
|
if (hmenuSrc)
|
|
{
|
|
i += Shell_MergeMenus(hmenu, hmenuSrc, indexMenu, idCmdFirst, idCmdLast, 0);
|
|
DestroyMenu(hmenuSrc);
|
|
}
|
|
|
|
if (!_pcmSF && _fAllowRename && _psf)
|
|
{
|
|
_psf->CreateViewObject(_hwndTB, IID_PPV_ARG(IContextMenu, &_pcmSF));
|
|
}
|
|
|
|
if (_pcmSF)
|
|
{
|
|
_idCmdSF = i - idCmdFirst;
|
|
HRESULT hrT = _pcmSF->QueryContextMenu(hmenu, indexMenu + i, i, 0x7fff, CMF_BANDCMD);
|
|
if (SUCCEEDED(hrT))
|
|
i += HRESULT_CODE(hrT);
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
BOOL CSFToolbar::_UpdateShowText(BOOL fNoShowText)
|
|
{
|
|
BOOL fChanged = (!_fNoShowText != !fNoShowText);
|
|
|
|
_fNoShowText = (fNoShowText != 0);
|
|
|
|
TraceMsg(TF_BAND, "ISFBand::_UpdateShowText turning text %hs", _fNoShowText ? "OFF" : "ON");
|
|
|
|
if (_hwndTB)
|
|
{
|
|
SendMessage(_hwndTB, TB_SETMAXTEXTROWS, _fNoShowText ? 0 : 1, 0L);
|
|
|
|
_UpdateButtons();
|
|
}
|
|
|
|
return fChanged;
|
|
}
|
|
|
|
HRESULT CSFToolbar::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
|
|
{
|
|
BOOL fChanged = FALSE;
|
|
int idCmd = -1;
|
|
|
|
if (!HIWORD(lpici->lpVerb))
|
|
idCmd = LOWORD(lpici->lpVerb);
|
|
|
|
switch (idCmd)
|
|
{
|
|
case ISFBIDM_REFRESH:
|
|
_Refresh();
|
|
break;
|
|
|
|
case ISFBIDM_OPEN:
|
|
OpenFolderPidl(_pidl);
|
|
break;
|
|
|
|
case ISFBIDM_LARGE:
|
|
fChanged = _UpdateIconSize(ISFBVIEWMODE_LARGEICONS, TRUE);
|
|
break;
|
|
case ISFBIDM_SMALL:
|
|
fChanged = _UpdateIconSize(ISFBVIEWMODE_SMALLICONS, TRUE);
|
|
break;
|
|
|
|
case ISFBIDM_SHOWTEXT:
|
|
fChanged = _UpdateShowText(!_fNoShowText);
|
|
break;
|
|
|
|
default:
|
|
if (_pcmSF && idCmd >= _idCmdSF)
|
|
{
|
|
LPCSTR lpOldVerb = lpici->lpVerb;
|
|
|
|
lpici->lpVerb = MAKEINTRESOURCEA(idCmd -= _idCmdSF);
|
|
|
|
_pcmSF->InvokeCommand(lpici);
|
|
_FlushNotifyMessages(_hwndTB);
|
|
|
|
lpici->lpVerb = lpOldVerb;
|
|
}
|
|
else
|
|
TraceMsg(TF_BAND, "SFToolbar::InvokeCommand %d not handled", idCmd);
|
|
break;
|
|
}
|
|
|
|
// Our minimum sizes have changed, notify the bandsite
|
|
//
|
|
if (fChanged)
|
|
_ToolbarChanged();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CSFToolbar::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwReserved, LPSTR pszName, UINT cchMax)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
void CSFToolbar::_RegisterToolbar()
|
|
{
|
|
// Since _SubclassWindow protects against multiply subclassing,
|
|
// This call is safe, and ensures that the toolbar is subclassed before
|
|
// even trying to register it for change notify.
|
|
if (_hwndTB && _fRegisterChangeNotify)
|
|
_RegisterChangeNotify();
|
|
CDelegateDropTarget::Init();
|
|
}
|
|
|
|
void CSFToolbar::_UnregisterToolbar()
|
|
{
|
|
if (_hwndTB)
|
|
{
|
|
if (_fRegisterChangeNotify)
|
|
_UnregisterChangeNotify();
|
|
_UnsubclassWindow(_hwndTB);
|
|
}
|
|
}
|
|
|
|
void CSFToolbar::_RegisterChangeNotify()
|
|
{
|
|
// Since we want to register for change notify ONLY once,
|
|
// and only if this is a file system toolbar.
|
|
if (!_fFSNRegistered && _fFSNotify)
|
|
{
|
|
if (_ptscn)
|
|
_ptscn->Register(_hwndTB, g_idFSNotify, _lEvents);
|
|
else
|
|
_RegisterWindow(_hwndTB, _pidl, _lEvents);
|
|
|
|
_fFSNRegistered = TRUE;
|
|
}
|
|
}
|
|
|
|
void CSFToolbar::_UnregisterChangeNotify()
|
|
{
|
|
// Only unregister if we have been registered.
|
|
if (_hwndTB && _fFSNRegistered && _fFSNotify)
|
|
{
|
|
_fFSNRegistered = FALSE;
|
|
if (_ptscn)
|
|
_ptscn->Unregister();
|
|
else
|
|
_UnregisterWindow(_hwndTB);
|
|
}
|
|
}
|
|
|
|
void CSFToolbar::_ReleaseShellFolder()
|
|
{
|
|
if (_psf)
|
|
{
|
|
IUnknown_SetOwner(_psf, NULL);
|
|
ATOMICRELEASE(_psf);
|
|
}
|
|
ATOMICRELEASE(_ptscn);
|
|
}
|
|
|
|
// IWinEventHandler::IsWindowOwner
|
|
|
|
HRESULT CSFToolbar::IsWindowOwner(HWND hwnd)
|
|
{
|
|
if (hwnd == _hwndTB ||
|
|
hwnd == _hwndToolTips ||
|
|
hwnd == _hwndPager)
|
|
return S_OK;
|
|
|
|
return S_FALSE;
|
|
}
|