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.
7658 lines
250 KiB
7658 lines
250 KiB
#include "priv.h"
|
|
#include "nsc.h"
|
|
#include "resource.h"
|
|
#include "subsmgr.h"
|
|
#include "favorite.h" //for IsSubscribed()
|
|
#include "chanmgr.h"
|
|
#include "chanmgrp.h"
|
|
#include <mstask.h> // TASK_TRIGGER
|
|
#include "dpastuff.h"
|
|
#include <findhlp.h>
|
|
#include <ntquery.h> // defines some values used for fmtid and pid
|
|
#include "nsctask.h"
|
|
#include <mluisupp.h>
|
|
#include <varutil.h>
|
|
#include <dobjutil.h>
|
|
|
|
#define IDH_ORGFAVS_LIST 50490 // defined in iehelpid.h (can't include due to conflicts)
|
|
|
|
#define TF_NSC 0x00002000
|
|
|
|
#define ID_NSC_SUBCLASS 359
|
|
#define ID_NSCTREE (DWORD)'NSC'
|
|
|
|
#define IDT_SELECTION 135
|
|
|
|
#ifndef UNIX
|
|
#define DEFAULT_PATHSTR "C:\\"
|
|
#else
|
|
#define DEFAULT_PATHSTR "/"
|
|
#endif
|
|
|
|
#define LOGOGAP 2 // all kinds of things
|
|
#define DYITEM 17
|
|
#define DXYFRAMESEL 1
|
|
const DEFAULTORDERPOSITION = 32000;
|
|
|
|
// HTML displays hard scripting errors if methods on automation interfaces
|
|
// return FAILED(). This macro will fix these.
|
|
#define FIX_SCRIPTING_ERRORS(hr) (FAILED(hr) ? S_FALSE : hr)
|
|
|
|
#define DEFINE_SCID(name, fmtid, pid) const SHCOLUMNID name = { fmtid, pid }
|
|
|
|
DEFINE_SCID(SCID_NAME, PSGUID_STORAGE, PID_STG_NAME); // defined in shell32!prop.cpp
|
|
DEFINE_SCID(SCID_ATTRIBUTES, PSGUID_STORAGE, PID_STG_ATTRIBUTES);
|
|
DEFINE_SCID(SCID_TYPE, PSGUID_STORAGE, PID_STG_STORAGETYPE);
|
|
DEFINE_SCID(SCID_SIZE, PSGUID_STORAGE, PID_STG_SIZE);
|
|
DEFINE_SCID(SCID_CREATETIME, PSGUID_STORAGE, PID_STG_CREATETIME);
|
|
|
|
#define IsEqualSCID(a, b) (((a).pid == (b).pid) && IsEqualIID((a).fmtid, (b).fmtid))
|
|
|
|
HRESULT CheckForExpandOnce(HWND hwndTree, HTREEITEM hti);
|
|
|
|
// from util.cpp
|
|
// same guid as in bandisf.cpp
|
|
// {F47162A0-C18F-11d0-A3A5-00C04FD706EC}
|
|
static const GUID TOID_ExtractImage = { 0xf47162a0, 0xc18f, 0x11d0, { 0xa3, 0xa5, 0x0, 0xc0, 0x4f, 0xd7, 0x6, 0xec } };
|
|
//from nsctask.cpp
|
|
EXTERN_C const GUID TASKID_IconExtraction; // = { 0xeb30900c, 0x1ac4, 0x11d2, { 0x83, 0x83, 0x0, 0xc0, 0x4f, 0xd9, 0x18, 0xd0 } };
|
|
|
|
|
|
BOOL IsChannelFolder(LPCWSTR pwzPath, LPWSTR pwzChannelURL);
|
|
|
|
typedef struct
|
|
{
|
|
DWORD iIcon : 12;
|
|
DWORD iOpenIcon : 12;
|
|
DWORD nFlags : 4;
|
|
DWORD nMagic : 4;
|
|
} NSC_ICONCALLBACKINFO;
|
|
|
|
typedef struct
|
|
{
|
|
DWORD iOverlayIndex : 28;
|
|
DWORD nMagic : 4;
|
|
} NSC_OVERLAYCALLBACKINFO;
|
|
|
|
struct NSC_BKGDENUMDONEDATA
|
|
{
|
|
~NSC_BKGDENUMDONEDATA()
|
|
{
|
|
ILFree(pidl);
|
|
ILFree(pidlExpandingTo);
|
|
OrderList_Destroy(&hdpa, TRUE);
|
|
}
|
|
|
|
NSC_BKGDENUMDONEDATA * pNext;
|
|
|
|
LPITEMIDLIST pidl;
|
|
HTREEITEM hitem;
|
|
DWORD dwSig;
|
|
HDPA hdpa;
|
|
LPITEMIDLIST pidlExpandingTo;
|
|
DWORD dwOrderSig;
|
|
UINT uDepth;
|
|
BOOL fUpdate;
|
|
BOOL fUpdatePidls;
|
|
};
|
|
|
|
//if you don't remove the selection, treeview will expand everything below the current selection
|
|
void TreeView_DeleteAllItemsQuickly(HWND hwnd)
|
|
{
|
|
TreeView_SelectItem(hwnd, NULL);
|
|
TreeView_DeleteAllItems(hwnd);
|
|
}
|
|
|
|
#define NSC_CHILDREN_REMOVE 0
|
|
#define NSC_CHILDREN_ADD 1
|
|
#define NSC_CHILDREN_FORCE 2
|
|
#define NSC_CHILDREN_CALLBACK 3
|
|
|
|
void TreeView_SetChildren(HWND hwnd, HTREEITEM hti, UINT uFlag)
|
|
{
|
|
TV_ITEM tvi;
|
|
tvi.mask = TVIF_CHILDREN | TVIF_HANDLE; // only change the number of children
|
|
tvi.hItem = hti;
|
|
|
|
switch (uFlag)
|
|
{
|
|
case NSC_CHILDREN_REMOVE:
|
|
tvi.cChildren = IsOS(OS_WHISTLERORGREATER) ? I_CHILDRENAUTO : 0;
|
|
break;
|
|
|
|
case NSC_CHILDREN_ADD:
|
|
tvi.cChildren = IsOS(OS_WHISTLERORGREATER) ? I_CHILDRENAUTO : 1;
|
|
break;
|
|
|
|
case NSC_CHILDREN_FORCE:
|
|
tvi.cChildren = 1;
|
|
break;
|
|
|
|
case NSC_CHILDREN_CALLBACK:
|
|
tvi.cChildren = I_CHILDRENCALLBACK;
|
|
break;
|
|
|
|
default:
|
|
ASSERTMSG(FALSE, "wrong parameter passed to TreeView_SetChildren in nsc");
|
|
break;
|
|
}
|
|
|
|
TreeView_SetItem(hwnd, &tvi);
|
|
}
|
|
|
|
void TreeView_DeleteChildren(HWND hwnd, HTREEITEM hti)
|
|
{
|
|
for (HTREEITEM htiTemp = TreeView_GetChild(hwnd, hti); htiTemp;)
|
|
{
|
|
HTREEITEM htiDelete = htiTemp;
|
|
htiTemp = TreeView_GetNextSibling(hwnd, htiTemp);
|
|
TreeView_DeleteItem(hwnd, htiDelete);
|
|
}
|
|
}
|
|
|
|
BOOL IsParentOfItem(HWND hwnd, HTREEITEM htiParent, HTREEITEM htiChild)
|
|
{
|
|
for (HTREEITEM hti = htiChild; (hti != TVI_ROOT) && (hti != NULL); hti = TreeView_GetParent(hwnd, hti))
|
|
if (hti == htiParent)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
STDAPI CNscTree_CreateInstance(IUnknown * punkOuter, IUnknown ** ppunk, LPCOBJECTINFO poi)
|
|
{
|
|
HRESULT hr;
|
|
CComObject<CNscTree> *pnsct;
|
|
|
|
CComObject<CNscTree>::CreateInstance(&pnsct);
|
|
if (pnsct)
|
|
{
|
|
hr = S_OK;
|
|
*ppunk = pnsct->GetUnknown();
|
|
ASSERT(*ppunk);
|
|
(*ppunk)->AddRef(); // atl doesn't addref in create instance or getunknown about
|
|
}
|
|
else
|
|
{
|
|
*ppunk = NULL;
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
INSCTree2 *CNscTree_CreateInstance(void)
|
|
{
|
|
INSCTree2 *pnsct = NULL;
|
|
IUnknown *punk;
|
|
if (SUCCEEDED(CNscTree_CreateInstance(NULL, &punk, NULL)))
|
|
{
|
|
punk->QueryInterface(IID_PPV_ARG(INSCTree2, &pnsct));
|
|
punk->Release();
|
|
}
|
|
return pnsct;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
CNscTree::CNscTree() : _iDragSrc(-1), _iDragDest(-1), _fOnline(!SHIsGlobalOffline())
|
|
{
|
|
// This object is a COM object so it will always be on the heap.
|
|
// ASSERT that our member variables were zero initialized.
|
|
ASSERT(!_fInitialized);
|
|
ASSERT(!_dwTVFlags);
|
|
ASSERT(!_hdpaColumns);
|
|
ASSERT(!_hdpaViews);
|
|
|
|
m_bWindowOnly = TRUE;
|
|
|
|
_mode = MODE_FAVORITES | MODE_CONTROL; //everyone sets the mode except organize favorites
|
|
_csidl = CSIDL_FAVORITES;
|
|
_dwFlags = NSS_DROPTARGET | NSS_BROWSERSELECT; //this should be default only in control mode
|
|
_grfFlags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS;
|
|
|
|
_ulSortCol = _ulDisplayCol = (ULONG)-1;
|
|
|
|
// Enable the notifications from wininet that tell us when to gray items
|
|
// or update a pinned glyph
|
|
_inetNotify.Enable();
|
|
|
|
InitializeCriticalSection(&_csBackgroundData);
|
|
}
|
|
|
|
CNscTree::~CNscTree()
|
|
{
|
|
Pidl_Set(&_pidlSelected, NULL);
|
|
|
|
// This needs to be destroyed or we leak the icon handle.
|
|
if (_hicoPinned)
|
|
DestroyIcon(_hicoPinned);
|
|
|
|
if (_hdpaColumns)
|
|
{
|
|
DPA_DestroyCallback(_hdpaColumns, DPADeleteItemCB, NULL);
|
|
_hdpaColumns = NULL;
|
|
}
|
|
|
|
if (_hdpaViews)
|
|
{
|
|
DPA_DestroyCallback(_hdpaViews, DPADeletePidlsCB, NULL);
|
|
_hdpaViews = NULL;
|
|
}
|
|
|
|
EnterCriticalSection(&_csBackgroundData);
|
|
while (_pbeddList)
|
|
{
|
|
// Extract the first element of the list
|
|
NSC_BKGDENUMDONEDATA * pbedd = _pbeddList;
|
|
_pbeddList = pbedd->pNext;
|
|
delete pbedd;
|
|
}
|
|
LeaveCriticalSection(&_csBackgroundData);
|
|
|
|
DeleteCriticalSection(&_csBackgroundData);
|
|
}
|
|
|
|
void CNscTree::_ReleaseCachedShellFolder()
|
|
{
|
|
ATOMICRELEASE(_psfCache);
|
|
ATOMICRELEASE(_psf2Cache);
|
|
_ulSortCol = _ulDisplayCol = (ULONG)-1;
|
|
_htiCache = NULL;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void CNscTree::TraceHTREE(HTREEITEM hti, LPCTSTR pszDebugMsg)
|
|
{
|
|
TCHAR szDebug[MAX_PATH] = TEXT("Root");
|
|
|
|
if (hti != TVI_ROOT && hti)
|
|
{
|
|
TVITEM tvi;
|
|
tvi.mask = TVIF_TEXT | TVIF_HANDLE;
|
|
tvi.hItem = hti;
|
|
tvi.pszText = szDebug;
|
|
tvi.cchTextMax = MAX_PATH;
|
|
TreeView_GetItem(_hwndTree, &tvi);
|
|
}
|
|
|
|
TraceMsg(TF_NSC, "NSCBand: %s - %s", pszDebugMsg, szDebug);
|
|
}
|
|
|
|
void CNscTree::TracePIDL(LPCITEMIDLIST pidl, LPCTSTR pszDebugMsg)
|
|
{
|
|
TCHAR szDebugName[MAX_URL_STRING] = TEXT("Desktop");
|
|
STRRET str;
|
|
if (_psfCache &&
|
|
SUCCEEDED(_psfCache->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &str)))
|
|
{
|
|
StrRetToBuf(&str, pidl, szDebugName, ARRAYSIZE(szDebugName));
|
|
}
|
|
TraceMsg(TF_NSC, "NSCBand: %s - %s", pszDebugMsg, szDebugName);
|
|
}
|
|
|
|
void CNscTree::TracePIDLAbs(LPCITEMIDLIST pidl, LPCTSTR pszDebugMsg)
|
|
{
|
|
TCHAR szDebugName[MAX_URL_STRING] = TEXT("Desktop");
|
|
IEGetDisplayName(pidl, szDebugName, SHGDN_FORPARSING);
|
|
TraceMsg(TF_NSC, "NSCBand: %s - %s", pszDebugMsg, szDebugName);
|
|
}
|
|
#endif
|
|
|
|
void CNscTree::_AssignPidl(PORDERITEM poi, LPITEMIDLIST pidlNew)
|
|
{
|
|
if (poi && pidlNew)
|
|
{
|
|
// We are assuming that its only replacing the last element...
|
|
ASSERT(ILFindLastID(pidlNew) == pidlNew);
|
|
|
|
LPITEMIDLIST pidlParent = ILCloneParent(poi->pidl);
|
|
if (pidlParent)
|
|
{
|
|
LPITEMIDLIST pidlT = ILCombine(pidlParent, pidlNew);
|
|
if (pidlT)
|
|
{
|
|
Pidl_Set(&poi->pidl, pidlT);
|
|
ILFree(pidlT);
|
|
}
|
|
ILFree(pidlParent);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*****************************************************\
|
|
DESCRIPTION:
|
|
We want to unsubclass/subclass everytime we
|
|
change roots so we get the correct notifications
|
|
for everything in that subtree of the shell
|
|
name space.
|
|
\*****************************************************/
|
|
void CNscTree::_SubClass(LPCITEMIDLIST pidlRoot)
|
|
{
|
|
LPITEMIDLIST pidlToFree = NULL;
|
|
|
|
if (NULL == pidlRoot) // (NULL == CSIDL_DESKTOP)
|
|
{
|
|
SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, (LPITEMIDLIST *) &pidlRoot);
|
|
pidlToFree = (LPITEMIDLIST) pidlRoot;
|
|
}
|
|
|
|
// It's necessary
|
|
if (!_fSubClassed && pidlRoot)
|
|
{
|
|
if (_SubclassWindow(_hwndTree))
|
|
{
|
|
_RegisterWindow(_hwndTree, pidlRoot,
|
|
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,
|
|
((_mode & MODE_HISTORY) ? SHCNRF_ShellLevel : SHCNRF_ShellLevel | SHCNRF_InterruptLevel));
|
|
}
|
|
|
|
ASSERT(_hwndTree);
|
|
_fSubClassed = SetWindowSubclass(_hwndTree, s_SubClassTreeWndProc,
|
|
ID_NSCTREE, (DWORD_PTR)this);
|
|
}
|
|
|
|
if (pidlToFree) // Did we have to alloc our own pidl?
|
|
ILFree(pidlToFree); // Yes.
|
|
}
|
|
|
|
|
|
/*****************************************************\
|
|
DESCRIPTION:
|
|
We want to unsubclass/subclass everytime we
|
|
change roots so we get the correct notifications
|
|
for everything in that subtree of the shell
|
|
name space.
|
|
\*****************************************************/
|
|
void CNscTree::_UnSubClass(void)
|
|
{
|
|
if (_fSubClassed)
|
|
{
|
|
_fSubClassed = FALSE;
|
|
RemoveWindowSubclass(_hwndTree, s_SubClassTreeWndProc, ID_NSCTREE);
|
|
_UnregisterWindow(_hwndTree);
|
|
_UnsubclassWindow(_hwndTree);
|
|
}
|
|
}
|
|
|
|
|
|
void CNscTree::_ReleasePidls(void)
|
|
{
|
|
Pidl_Set(&_pidlRoot, NULL);
|
|
Pidl_Set(&_pidlNavigatingTo, NULL);
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::ShowWindow(BOOL fShow)
|
|
{
|
|
if (fShow)
|
|
_TvOnShow();
|
|
else
|
|
_TvOnHide();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::SetSite(IUnknown *punkSite)
|
|
{
|
|
ATOMICRELEASE(_pnscProxy);
|
|
|
|
if (!punkSite)
|
|
{
|
|
// We need to prepare to go away and squirel
|
|
// away the currently selected pidl(s) because
|
|
// the caller may call INSCTree::GetSelectedItem()
|
|
// after the tree is gone.
|
|
_OnWindowCleanup();
|
|
}
|
|
else
|
|
{
|
|
punkSite->QueryInterface(IID_PPV_ARG(INamespaceProxy, &_pnscProxy));
|
|
}
|
|
|
|
return CObjectWithSite::SetSite(punkSite);
|
|
}
|
|
|
|
DWORD BackgroundDestroyScheduler(void *pvData)
|
|
{
|
|
IShellTaskScheduler *pTaskScheduler = (IShellTaskScheduler *)pvData;
|
|
|
|
pTaskScheduler->Release();
|
|
return 0;
|
|
}
|
|
|
|
EXTERN_C const GUID TASKID_BackgroundEnum;
|
|
|
|
HRESULT CNscTree::_OnWindowCleanup(void)
|
|
{
|
|
_fClosing = TRUE;
|
|
|
|
if (_hwndTree)
|
|
{
|
|
ASSERT(::IsWindow(_hwndTree)); // make sure it has not been destroyed (it is a child)
|
|
_TvOnHide();
|
|
|
|
::KillTimer(_hwndTree, IDT_SELECTION);
|
|
::SendMessage(_hwndTree, WM_SETREDRAW, FALSE, 0);
|
|
TreeView_DeleteAllItemsQuickly(_hwndTree);
|
|
_UnSubClass();
|
|
|
|
_hwndTree = NULL;
|
|
}
|
|
|
|
// Squirel away the selected pidl in case the caller asks for it after the
|
|
// treeview is gone.
|
|
if (!_fIsSelectionCached)
|
|
{
|
|
_fIsSelectionCached = TRUE;
|
|
Pidl_Set(&_pidlSelected, NULL);
|
|
GetSelectedItem(&_pidlSelected, 0);
|
|
}
|
|
|
|
ATOMICRELEASE(_pFilter);
|
|
|
|
if (_pTaskScheduler)
|
|
{
|
|
_pTaskScheduler->RemoveTasks(TOID_NULL, ITSAT_DEFAULT_LPARAM, FALSE);
|
|
if (_pTaskScheduler->CountTasks(TASKID_BackgroundEnum) == 0)
|
|
{
|
|
_pTaskScheduler->Release();
|
|
}
|
|
// We need to keep Browseui loaded because we depend on the CShellTaskScheduler
|
|
// to be still around when our background task executes. Browseui can be unloaded by COM when
|
|
// we CoUninit from this thread.
|
|
else if (!SHQueueUserWorkItem(BackgroundDestroyScheduler, (void *)_pTaskScheduler, 0, NULL, NULL, "browseui.dll", 0))
|
|
{
|
|
_pTaskScheduler->Release();
|
|
}
|
|
|
|
_pTaskScheduler = NULL;
|
|
}
|
|
|
|
_ReleasePidls();
|
|
_ReleaseCachedShellFolder();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
ITEMINFO *CNscTree::_GetTreeItemInfo(HTREEITEM hti)
|
|
{
|
|
TV_ITEM tvi;
|
|
|
|
tvi.mask = TVIF_PARAM | TVIF_HANDLE;
|
|
tvi.hItem = hti;
|
|
if (!TreeView_GetItem(_hwndTree, &tvi))
|
|
return NULL;
|
|
return (ITEMINFO *)tvi.lParam;
|
|
}
|
|
|
|
PORDERITEM CNscTree::_GetTreeOrderItem(HTREEITEM hti)
|
|
{
|
|
ITEMINFO *pii = _GetTreeItemInfo(hti);
|
|
return pii ? pii->poi : NULL;
|
|
}
|
|
|
|
// builds a fully qualified IDLIST from a given tree node by walking up the tree
|
|
// be sure to free this when you are done!
|
|
|
|
LPITEMIDLIST CNscTree::_GetFullIDList(HTREEITEM hti)
|
|
{
|
|
LPITEMIDLIST pidl, pidlT = NULL;
|
|
|
|
if ((hti == TVI_ROOT) || (hti == NULL)) // evil root
|
|
{
|
|
pidlT = ILClone(_pidlRoot);
|
|
return pidlT;
|
|
}
|
|
// now lets get the information about the item
|
|
PORDERITEM poi = _GetTreeOrderItem(hti);
|
|
if (!poi)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
pidl = ILClone(poi->pidl);
|
|
if (pidl && _pidlRoot)
|
|
{
|
|
while ((hti = TreeView_GetParent(_hwndTree, hti)))
|
|
{
|
|
poi = _GetTreeOrderItem(hti);
|
|
if (!poi)
|
|
return pidl; // will assume I messed up...
|
|
|
|
if (poi->pidl)
|
|
pidlT = ILCombine(poi->pidl, pidl);
|
|
else
|
|
pidlT = NULL;
|
|
|
|
ILFree(pidl);
|
|
pidl = pidlT;
|
|
if (pidl == NULL)
|
|
break; // outta memory
|
|
}
|
|
if (pidl)
|
|
{
|
|
// MODE_NORMAL has the pidl root in the tree
|
|
if (_mode != MODE_NORMAL)
|
|
{
|
|
pidlT = ILCombine(_pidlRoot, pidl); // gotta get the silent root
|
|
ILFree(pidl);
|
|
}
|
|
else
|
|
pidlT = pidl;
|
|
}
|
|
}
|
|
return pidlT;
|
|
}
|
|
|
|
|
|
BOOL _IsItemFileSystem(IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
return (SHGetAttributes(psf, pidl, SFGAO_FOLDER | SFGAO_FILESYSTEM) == (SFGAO_FOLDER | SFGAO_FILESYSTEM));
|
|
}
|
|
|
|
HTREEITEM CNscTree::_AddItemToTree(HTREEITEM htiParent, LPITEMIDLIST pidl,
|
|
int cChildren, int iPos, HTREEITEM htiAfter, /* = TVI_LAST*/
|
|
BOOL fCheckForDups, /* = TRUE */ BOOL fMarked /*= FALSE */)
|
|
{
|
|
HTREEITEM htiRet = NULL;
|
|
|
|
BOOL fCached;
|
|
|
|
// So we need to cached the shell folder of the parent item. But, this is a little interesting:
|
|
if (_mode == MODE_NORMAL && htiParent == TVI_ROOT)
|
|
{
|
|
// In "Normal" mode, or "Display root in NSC" mode, there is only 1 item that is parented to
|
|
// TVI_ROOT. So when we do an _AddItemToTree, we need the shell folder that contains _pidlRoot or
|
|
// the Parent of TVI_ROOT.
|
|
fCached = (NULL != _CacheParentShellFolder(htiParent, NULL));
|
|
}
|
|
else
|
|
{
|
|
// But, in the "Favorites, Control or History" if htiParent is TVI_ROOT, then we are not adding _pidlRoot,
|
|
// so we actually need the folder that IS TVI_ROOT.
|
|
fCached = _CacheShellFolder(htiParent);
|
|
}
|
|
|
|
if (fCached)
|
|
{
|
|
LPITEMIDLIST pidlNew = ILClone(pidl);
|
|
if (pidlNew)
|
|
{
|
|
PORDERITEM poi = OrderItem_Create(pidlNew, iPos);
|
|
if (poi)
|
|
{
|
|
ITEMINFO *pii = (ITEMINFO *)LocalAlloc(LPTR, sizeof(*pii));
|
|
if (pii)
|
|
{
|
|
pii->dwSig = _dwSignature++;
|
|
pii->poi = poi;
|
|
|
|
// For the normal case, we need a relative pidl for this add, but the lParam needs to have a full
|
|
// pidl (This is so that arbitrary mounting works, as well as desktop case).
|
|
pidl = pidlNew; //reuse variable
|
|
if (_mode == MODE_NORMAL && htiParent == TVI_ROOT)
|
|
{
|
|
pidl = ILFindLastID(pidl);
|
|
}
|
|
|
|
if (!fCheckForDups || (NULL == (htiRet = _FindChild(_psfCache, htiParent, pidl))))
|
|
{
|
|
TV_INSERTSTRUCT tii;
|
|
// Initialize item to add with callback for everything
|
|
tii.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_CHILDREN | TVIF_STATE;
|
|
tii.hParent = htiParent;
|
|
tii.hInsertAfter = htiAfter;
|
|
tii.item.iImage = I_IMAGECALLBACK;
|
|
tii.item.iSelectedImage = I_IMAGECALLBACK;
|
|
tii.item.pszText = LPSTR_TEXTCALLBACK;
|
|
tii.item.cChildren = cChildren;
|
|
tii.item.lParam = (LPARAM)pii;
|
|
tii.item.stateMask = TVIS_STATEIMAGEMASK;
|
|
tii.item.state = (fMarked ? NSC_TVIS_MARKED : 0);
|
|
|
|
#ifdef DEBUG
|
|
TracePIDL(pidl, TEXT("Inserting"));
|
|
TraceMsg(TF_NSC, "_AddItemToTree(htiParent=%#08lx, htiAfter=%#08lx, fCheckForDups=%d, _psfCache=%#08lx)",
|
|
htiParent, htiAfter, fCheckForDups, _psfCache);
|
|
|
|
#endif // DEBUG
|
|
|
|
pii->fNavigable = !_IsItemFileSystem(_psfCache, pidl);
|
|
|
|
htiRet = TreeView_InsertItem(_hwndTree, &tii);
|
|
if (htiRet)
|
|
{
|
|
pii = NULL; // don't free
|
|
poi = NULL; // don't free
|
|
pidlNew = NULL;
|
|
}
|
|
}
|
|
if (pii)
|
|
{
|
|
LocalFree(pii);
|
|
pii = NULL;
|
|
}
|
|
}
|
|
if (poi)
|
|
OrderItem_Free(poi, FALSE);
|
|
}
|
|
ILFree(pidlNew);
|
|
}
|
|
}
|
|
|
|
return htiRet;
|
|
}
|
|
|
|
DWORD CNscTree::_SetExStyle(DWORD dwExStyle)
|
|
{
|
|
DWORD dwOldStyle = _dwExStyle;
|
|
|
|
_dwExStyle = dwExStyle;
|
|
return dwOldStyle;
|
|
}
|
|
|
|
DWORD CNscTree::_SetStyle(DWORD dwStyle)
|
|
{
|
|
dwStyle |= TVS_EDITLABELS | TVS_SHOWSELALWAYS | TVS_NONEVENHEIGHT;
|
|
|
|
if (dwStyle & WS_HSCROLL)
|
|
dwStyle &= ~WS_HSCROLL;
|
|
else
|
|
dwStyle |= TVS_NOHSCROLL;
|
|
|
|
|
|
if (TVS_HASLINES & dwStyle)
|
|
dwStyle &= ~TVS_FULLROWSELECT; // If it has TVS_HASLINES, it can't have TVS_FULLROWSELECT
|
|
|
|
// If the parent window is mirrored then the treeview window will inheret the mirroring flag
|
|
// And we need the reading order to be Left to right, which is the right to left in the mirrored mode.
|
|
|
|
if (((_mode & MODE_HISTORY) || (MODE_NORMAL == _mode)) && IS_WINDOW_RTL_MIRRORED(_hwndParent))
|
|
{
|
|
// This means left to right reading order because this window will be mirrored.
|
|
dwStyle |= TVS_RTLREADING;
|
|
}
|
|
|
|
// According to Bug#241601, Tooltips display too quickly. The problem is
|
|
// the original designer of the InfoTips in the Treeview merged the "InfoTip" tooltip and
|
|
// the "I'm too small to display correctly" tooltips. This is really unfortunate because you
|
|
// cannot control the display of these tooltips independantly. Therefore we are turning off
|
|
// infotips in normal mode. (lamadio) 4.7.99
|
|
AssertMsg(_mode != MODE_NORMAL || !(dwStyle & TVS_INFOTIP), TEXT("can't have infotip with normal mode in nsc"));
|
|
|
|
DWORD dwOldStyle = _style;
|
|
_style = dwStyle | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VSCROLL | WS_TABSTOP;
|
|
_fSingleExpand = BOOLIFY(_style & TVS_SINGLEEXPAND);
|
|
|
|
return dwOldStyle;
|
|
|
|
}
|
|
|
|
HRESULT CNscTree::CreateTree(HWND hwndParent, DWORD dwStyles, HWND *phwnd)
|
|
{
|
|
return CreateTree2(hwndParent, dwStyles, 0, phwnd);
|
|
}
|
|
|
|
HRESULT CNscTree::CreateTree2(HWND hwndParent, DWORD dwStyle, DWORD dwExStyle, HWND *phwnd)
|
|
{
|
|
_fIsSelectionCached = FALSE;
|
|
if (*phwnd)
|
|
return S_OK;
|
|
|
|
_hwndParent = hwndParent;
|
|
_SetStyle(dwStyle);
|
|
_SetExStyle(dwExStyle);
|
|
*phwnd = _CreateTreeview();
|
|
if (*phwnd == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
::ShowWindow(_hwndTree, SW_SHOW);
|
|
return S_OK;
|
|
}
|
|
|
|
HWND CNscTree::_CreateTreeview()
|
|
{
|
|
ASSERT(_hwndTree == NULL);
|
|
|
|
LONG lTop = 0;
|
|
RECT rcParent;
|
|
::GetClientRect(_hwndParent, &rcParent);
|
|
|
|
TCHAR szTitle[40];
|
|
if (_mode & (MODE_HISTORY | MODE_FAVORITES))
|
|
{
|
|
// create with a window title so that msaa can expose name
|
|
int id = (_mode & MODE_HISTORY) ? IDS_BAND_HISTORY : IDS_BAND_FAVORITES;
|
|
MLLoadString(id, szTitle, ARRAYSIZE(szTitle));
|
|
}
|
|
else
|
|
{
|
|
szTitle[0] = 0;
|
|
}
|
|
|
|
_hwndTree = CreateWindowEx(0, WC_TREEVIEW, szTitle, _style | WS_VISIBLE,
|
|
0, lTop, rcParent.right, rcParent.bottom, _hwndParent, (HMENU)ID_CONTROL, HINST_THISDLL, NULL);
|
|
|
|
if (_hwndTree)
|
|
{
|
|
::SendMessage(_hwndTree, TVM_SETSCROLLTIME, 100, 0);
|
|
::SendMessage(_hwndTree, CCM_SETUNICODEFORMAT, DLL_IS_UNICODE, 0);
|
|
if (_dwExStyle)
|
|
TreeView_SetExtendedStyle(_hwndTree, _dwExStyle, _dwExStyle);
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_ERROR, "_hwndTree failed");
|
|
}
|
|
|
|
return _hwndTree;
|
|
}
|
|
|
|
UINT GetControlCharWidth(HWND hwnd)
|
|
{
|
|
SIZE siz = {0};
|
|
CClientDC dc(HWND_DESKTOP);
|
|
|
|
if (dc.m_hDC)
|
|
{
|
|
HFONT hfOld = dc.SelectFont(FORWARD_WM_GETFONT(hwnd, SendMessage));
|
|
|
|
if (hfOld)
|
|
{
|
|
GetTextExtentPoint(dc.m_hDC, TEXT("0"), 1, &siz);
|
|
|
|
dc.SelectFont(hfOld);
|
|
}
|
|
}
|
|
|
|
return siz.cx;
|
|
}
|
|
|
|
HWND CNscTree::_CreateHeader()
|
|
{
|
|
if (!_hwndHdr)
|
|
{
|
|
_hwndHdr = CreateWindowEx(0, WC_HEADER, NULL, HDS_HORZ | WS_CHILD, 0, 0, 0, 0,
|
|
_hwndParent, (HMENU)ID_HEADER, HINST_THISDLL, NULL);
|
|
if (_hwndHdr)
|
|
{
|
|
HD_LAYOUT layout;
|
|
WINDOWPOS wpos;
|
|
RECT rcClient;
|
|
int cxChar = GetControlCharWidth(_hwndTree);
|
|
|
|
layout.pwpos = &wpos;
|
|
::GetClientRect(_hwndParent, &rcClient);
|
|
layout.prc = &rcClient;
|
|
if (Header_Layout(_hwndHdr, &layout))
|
|
{
|
|
::MoveWindow(_hwndTree, 0, wpos.cy, RECTWIDTH(rcClient), RECTHEIGHT(rcClient)-wpos.cy, TRUE);
|
|
for (int i = 0; i < DPA_GetPtrCount(_hdpaColumns);)
|
|
{
|
|
HEADERINFO *phinfo = (HEADERINFO *)DPA_GetPtr(_hdpaColumns, i);
|
|
if (EVAL(phinfo))
|
|
{
|
|
HD_ITEM item;
|
|
|
|
item.mask = HDI_TEXT | HDI_FORMAT | HDI_WIDTH;
|
|
item.pszText = phinfo->szName;
|
|
item.fmt = phinfo->fmt;
|
|
item.cxy = cxChar * phinfo->cxChar;
|
|
|
|
if (Header_InsertItem(_hwndHdr, i, &item) == -1)
|
|
{
|
|
DPA_DeletePtr(_hdpaColumns, i);
|
|
LocalFree(phinfo);
|
|
phinfo = NULL;
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
if (_hwndTree)
|
|
{
|
|
HFONT hfont = (HFONT)::SendMessage(_hwndTree, WM_GETFONT, 0, 0);
|
|
|
|
if (hfont)
|
|
::SendMessage(_hwndHdr, WM_SETFONT, (WPARAM)hfont, MAKELPARAM(TRUE, 0));
|
|
}
|
|
::SetWindowPos(_hwndHdr, wpos.hwndInsertAfter, wpos.x, wpos.y,
|
|
wpos.cx, wpos.cy, wpos.flags | SWP_SHOWWINDOW);
|
|
}
|
|
}
|
|
}
|
|
|
|
return _hwndHdr;
|
|
}
|
|
|
|
void CNscTree::_TvOnHide()
|
|
{
|
|
_DtRevoke();
|
|
::SetWindowPos(_hwndTree, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW);
|
|
}
|
|
|
|
void CNscTree::_TvOnShow()
|
|
{
|
|
::SetWindowPos(_hwndTree, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
|
|
_DtRegister();
|
|
}
|
|
|
|
HRESULT IUnknown_GetAmbientProperty(IUnknown *punk, DISPID dispid, VARTYPE vt, void *pData)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
if (punk)
|
|
{
|
|
IDispatch *pdisp;
|
|
hr = punk->QueryInterface(IID_PPV_ARG(IDispatch, &pdisp));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DISPPARAMS dp = {0};
|
|
VARIANT v;
|
|
VariantInit(&v);
|
|
hr = pdisp->Invoke(dispid, IID_NULL, 0, DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
VARIANT vDest;
|
|
VariantInit(&vDest);
|
|
// we've got the variant, so now go an coerce it to the type
|
|
// that the user wants.
|
|
//
|
|
hr = VariantChangeType(&vDest, &v, 0, vt);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*((DWORD *)pData) = *((DWORD *)&vDest.lVal);
|
|
VariantClear(&vDest);
|
|
}
|
|
VariantClear(&v);
|
|
}
|
|
pdisp->Release();
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNscTree::_HandleWinIniChange()
|
|
{
|
|
COLORREF clrBk;
|
|
|
|
if (FAILED(IUnknown_GetAmbientProperty(_punkSite, DISPID_AMBIENT_BACKCOLOR, VT_I4, &clrBk)))
|
|
clrBk = GetSysColor(COLOR_WINDOW);
|
|
|
|
TreeView_SetBkColor(_hwndTree, clrBk);
|
|
|
|
if (!(_dwFlags & NSS_NORMALTREEVIEW))
|
|
{
|
|
// make things a bit more spaced out
|
|
int cyItem = TreeView_GetItemHeight(_hwndTree);
|
|
cyItem += LOGOGAP + 1;
|
|
TreeView_SetItemHeight(_hwndTree, cyItem);
|
|
}
|
|
|
|
// Show compressed files in different color...
|
|
SHELLSTATE ss;
|
|
SHGetSetSettings(&ss, SSF_SHOWCOMPCOLOR, FALSE);
|
|
_fShowCompColor = ss.fShowCompColor;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNscTree::Initialize(LPCITEMIDLIST pidlRoot, DWORD grfEnumFlags, DWORD dwFlags)
|
|
{
|
|
HRESULT hr;
|
|
|
|
_grfFlags = grfEnumFlags; // IShellFolder::EnumObjects() flags.
|
|
if (!(_mode & MODE_CUSTOM))
|
|
{
|
|
if (_mode != MODE_NORMAL)
|
|
{
|
|
dwFlags |= NSS_BORDER;
|
|
}
|
|
else
|
|
{
|
|
dwFlags |= NSS_NORMALTREEVIEW;
|
|
}
|
|
}
|
|
_dwFlags = dwFlags; // Behavior Flags
|
|
if (_dwFlags & NSS_NORMALTREEVIEW)
|
|
_dwFlags &= ~NSS_HEADER;// multi-select requires owner draw
|
|
|
|
if (!_fInitialized)
|
|
{
|
|
::SendMessage(_hwndTree, WM_SETREDRAW, FALSE, 0);
|
|
|
|
_fInitialized = TRUE;
|
|
|
|
HIMAGELIST himl;
|
|
Shell_GetImageLists(NULL, &himl);
|
|
|
|
TreeView_SetImageList(_hwndTree, himl, TVSIL_NORMAL);
|
|
_DtRegister();
|
|
|
|
//failure ignored intentionally
|
|
THR(CoCreateInstance(CLSID_ShellTaskScheduler, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARG(IShellTaskScheduler, &_pTaskScheduler)));
|
|
if (_pTaskScheduler)
|
|
_pTaskScheduler->Status(ITSSFLAG_KILL_ON_DESTROY, ITSS_THREAD_TIMEOUT_NO_CHANGE);
|
|
|
|
hr = Init(); // init lock and scroll handles for CDelegateDropTarget
|
|
|
|
ASSERT(SUCCEEDED(hr));
|
|
|
|
if (_dwFlags & NSS_BORDER)
|
|
{
|
|
// set borders and space out for all, much cleaner.
|
|
TreeView_SetBorder(_hwndTree, TVSBF_XBORDER, 2 * LOGOGAP, 0);
|
|
}
|
|
|
|
// init some settings
|
|
_HandleWinIniChange();
|
|
|
|
// pidlRoot may equal NULL because that is equal to CSIDL_DESKTOP.
|
|
if ((LPITEMIDLIST)INVALID_HANDLE_VALUE != pidlRoot)
|
|
{
|
|
_UnSubClass();
|
|
_SetRoot(pidlRoot, 1, NULL, NSSR_CREATEPIDL);
|
|
_SubClass(pidlRoot);
|
|
}
|
|
|
|
// need top level frame available for D&D if possible.
|
|
|
|
_hwndDD = ::GetParent(_hwndTree);
|
|
IOleWindow *pOleWindow;
|
|
if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_STopLevelBrowser, IID_PPV_ARG(IOleWindow, &pOleWindow))))
|
|
{
|
|
pOleWindow->GetWindow(&_hwndDD);
|
|
pOleWindow->Release();
|
|
}
|
|
|
|
//this is a non-ML resource
|
|
_hicoPinned = (HICON)LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDI_PINNED), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
|
|
ASSERT(_hicoPinned);
|
|
|
|
::SendMessage(_hwndTree, WM_SETREDRAW, TRUE, 0);
|
|
}
|
|
else
|
|
hr = _ChangePidlRoot(pidlRoot);
|
|
|
|
return hr;
|
|
}
|
|
|
|
// set the root of the name space control.
|
|
//
|
|
// in:
|
|
// pidlRoot NULL means the desktop
|
|
// HIWORD 0 -> LOWORD == ID of special folder (CSIDL_* values)
|
|
//
|
|
// flags,
|
|
// pidlRoot, PIDL, NULL for desktop, or CSIDL for shell special folder
|
|
// iExpandDepth, how many levels to expand the tree
|
|
// pidlExpandTo NULL, or PIDL to expand to
|
|
//
|
|
|
|
BOOL CNscTree::_SetRoot(LPCITEMIDLIST pidlRoot, int iExpandDepth, LPCITEMIDLIST pidlExpandTo, NSSR_FLAGS flags)
|
|
{
|
|
_ReleasePidls();
|
|
// review chrisny: clean up this psr stuff.
|
|
// HIWORD/LOWORD stuff is to support pidl IDs instead of full pidl here
|
|
if (HIWORD(pidlRoot))
|
|
{
|
|
_pidlRoot = ILClone(pidlRoot);
|
|
}
|
|
else
|
|
{
|
|
SHGetSpecialFolderLocation(NULL, LOWORD(pidlRoot) ? LOWORD(pidlRoot) : CSIDL_DESKTOP, &_pidlRoot);
|
|
}
|
|
|
|
if (_pidlRoot)
|
|
{
|
|
HTREEITEM htiRoot = TVI_ROOT;
|
|
if (_mode == MODE_NORMAL)
|
|
{
|
|
// Since we'll be adding this into the tree, we need
|
|
// to clone it: We have a copy for the class, and we
|
|
// have one for the tree itself (Makes life easier so
|
|
// we don't have to special case TVI_ROOT).
|
|
htiRoot = _AddItemToTree(TVI_ROOT, _pidlRoot, 1, 0);
|
|
if (htiRoot)
|
|
{
|
|
TreeView_SelectItem(_hwndTree, htiRoot);
|
|
TraceMsg(TF_NSC, "NSCBand: Setting Root to \"Desktop\"");
|
|
}
|
|
else
|
|
{
|
|
htiRoot = TVI_ROOT;
|
|
}
|
|
}
|
|
|
|
BOOL fOrdered = _fOrdered;
|
|
_LoadSF(htiRoot, _pidlRoot, &fOrdered); // load the roots (actual children of _pidlRoot.
|
|
// this is probably redundant since _LoadSF->_LoadOrder sets this
|
|
_fOrdered = BOOLIFY(fOrdered);
|
|
|
|
#ifdef DEBUG
|
|
TracePIDLAbs(_pidlRoot, TEXT("Setting Root to"));
|
|
#endif // DEBUG
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
TraceMsg(DM_ERROR, "set root failed");
|
|
_ReleasePidls();
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// cache the shell folder for a given tree item
|
|
// in:
|
|
// hti tree node to cache shell folder for. this my be
|
|
// NULL indicating the root item.
|
|
//
|
|
|
|
BOOL CNscTree::_CacheShellFolder(HTREEITEM hti)
|
|
{
|
|
// in the cache?
|
|
if ((hti != _htiCache) || (_psfCache == NULL))
|
|
{
|
|
// cache miss, do the work
|
|
LPITEMIDLIST pidl;
|
|
BOOL fRet = FALSE;
|
|
|
|
_fpsfCacheIsTopLevel = FALSE;
|
|
_ReleaseCachedShellFolder();
|
|
|
|
if ((hti == NULL) || (hti == TVI_ROOT))
|
|
{
|
|
pidl = ILClone(_pidlRoot);
|
|
}
|
|
else
|
|
{
|
|
pidl = _GetFullIDList(hti);
|
|
}
|
|
|
|
if (pidl)
|
|
{
|
|
if (SUCCEEDED(IEBindToObject(pidl, &_psfCache)))
|
|
{
|
|
if (_pnscProxy)
|
|
_pnscProxy->CacheItem(pidl);
|
|
ASSERT(_psfCache);
|
|
_htiCache = hti; // this is for the cache match
|
|
_fpsfCacheIsTopLevel = (hti == TVI_ROOT || hti == NULL);
|
|
_psfCache->QueryInterface(IID_PPV_ARG(IShellFolder2, &_psf2Cache));
|
|
fRet = TRUE;
|
|
}
|
|
|
|
ILFree(pidl);
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
#define TVI_ROOTPARENT ((HTREEITEM)(ULONG_PTR)-0xF000)
|
|
|
|
// pidlItem is typically a relative pidl, except in the case of the root where
|
|
// it can be a fully qualified pidl
|
|
|
|
LPITEMIDLIST CNscTree::_CacheParentShellFolder(HTREEITEM hti, LPITEMIDLIST pidl)
|
|
{
|
|
// need parent shell folder of TVI_ROOT, special case for drop insert into root level of tree.
|
|
if (hti == TVI_ROOT ||
|
|
hti == NULL ||
|
|
(_mode == MODE_NORMAL &&
|
|
TreeView_GetParent(_hwndTree, hti) == NULL)) // If we have a null parent and we're a normal,
|
|
// than that's the same as root.
|
|
{
|
|
if (_htiCache != TVI_ROOTPARENT)
|
|
{
|
|
_ReleaseCachedShellFolder();
|
|
IEBindToParentFolder(_pidlRoot, &_psfCache, NULL);
|
|
|
|
if (!ILIsEmpty(_pidlRoot))
|
|
_htiCache = TVI_ROOTPARENT;
|
|
}
|
|
return ILFindLastID(_pidlRoot);
|
|
}
|
|
|
|
if (_CacheShellFolder(TreeView_GetParent(_hwndTree, hti)))
|
|
{
|
|
if (pidl == NULL)
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(hti);
|
|
if (!poi)
|
|
return NULL;
|
|
|
|
pidl = poi->pidl;
|
|
}
|
|
|
|
return ILFindLastID(pidl);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
typedef struct _SORTPARAMS
|
|
{
|
|
CNscTree *pnsc;
|
|
IShellFolder *psf;
|
|
} SORTPARAMS;
|
|
|
|
int CALLBACK CNscTree::_TreeCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
|
|
{
|
|
SORTPARAMS *pSortParams = (SORTPARAMS *)lParamSort;
|
|
PORDERITEM poi1 = GetPoi(lParam1), poi2 = GetPoi(lParam2);
|
|
|
|
HRESULT hr = pSortParams->pnsc->_CompareIDs(pSortParams->psf, poi1->pidl, poi2->pidl);
|
|
return (short)SCODE_CODE(hr);
|
|
}
|
|
|
|
int CALLBACK CNscTree::_TreeOrder(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
|
|
{
|
|
HRESULT hr;
|
|
PORDERITEM poi1 = GetPoi(lParam1), poi2 = GetPoi(lParam2);
|
|
|
|
ASSERT((poi1 != NULL) && (poi1 != NULL));
|
|
if (poi1->nOrder == poi2->nOrder)
|
|
hr = 0;
|
|
else
|
|
// do unsigned compare so -1 goes to end of list
|
|
hr = (poi1->nOrder < poi2->nOrder ? -1 : 1);
|
|
|
|
return (short)SCODE_CODE(hr);
|
|
}
|
|
// review chrisny: instead of sort, insert items on the fly.
|
|
void CNscTree::_Sort(HTREEITEM hti, IShellFolder *psf)
|
|
{
|
|
TV_SORTCB scb;
|
|
SORTPARAMS SortParams = {this, psf};
|
|
BOOL fOrdering = _IsOrdered(hti);
|
|
#ifdef DEBUG
|
|
TraceHTREE(hti, TEXT("Sorting"));
|
|
#endif
|
|
|
|
scb.hParent = hti;
|
|
scb.lpfnCompare = !fOrdering ? _TreeCompare : _TreeOrder;
|
|
|
|
scb.lParam = (LPARAM)&SortParams;
|
|
TreeView_SortChildrenCB(_hwndTree, &scb, FALSE);
|
|
}
|
|
|
|
BOOL CNscTree::_IsOrdered(HTREEITEM htiRoot)
|
|
{
|
|
if ((htiRoot == TVI_ROOT) || (htiRoot == NULL))
|
|
return _fOrdered;
|
|
else
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(htiRoot);
|
|
if (poi)
|
|
{
|
|
// LParam Is a Boolean:
|
|
// TRUE: It has an order.
|
|
// FALSE: It does not have an order.
|
|
// Question: Where is that order stored? _hdpaOrder?
|
|
return poi->lParam;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//helper function to init _hdpaOrd
|
|
//MUST be followed by a call to _FreeOrderList
|
|
HRESULT CNscTree::_PopulateOrderList(HTREEITEM htiRoot)
|
|
{
|
|
int i = 0;
|
|
HTREEITEM hti = NULL;
|
|
#ifdef DEBUG
|
|
TraceHTREE(htiRoot, TEXT("Populating Order List from tree node"));
|
|
#endif
|
|
|
|
if (_hdpaOrd)
|
|
DPA_Destroy(_hdpaOrd);
|
|
|
|
_hdpaOrd = DPA_Create(4);
|
|
if (_hdpaOrd == NULL)
|
|
return E_FAIL;
|
|
|
|
for (hti = TreeView_GetChild(_hwndTree, htiRoot); hti; hti = TreeView_GetNextSibling(_hwndTree, hti))
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(hti);
|
|
if (poi)
|
|
{
|
|
poi->nOrder = i; // reset the positions of the nodes.
|
|
DPA_SetPtr(_hdpaOrd, i++, (void *)poi);
|
|
}
|
|
}
|
|
|
|
//set the root's ordered flag
|
|
if (htiRoot == TVI_ROOT)
|
|
{
|
|
_fOrdered = TRUE;
|
|
}
|
|
else
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(htiRoot);
|
|
if (poi)
|
|
{
|
|
poi->lParam = TRUE;
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//helper function to free _hdpaOrd
|
|
//MUST be preceded by a call to _PopulateOrderList
|
|
|
|
void CNscTree::_FreeOrderList(HTREEITEM htiRoot)
|
|
{
|
|
ASSERT(_hdpaOrd);
|
|
#ifdef DEBUG
|
|
TraceHTREE(htiRoot, TEXT("Freeing OrderList"));
|
|
#endif
|
|
|
|
_ReleaseCachedShellFolder();
|
|
|
|
// Persist the new order out to the registry
|
|
LPITEMIDLIST pidl = _GetFullIDList(htiRoot);
|
|
if (pidl)
|
|
{
|
|
IStream* pstm = GetOrderStream(pidl, STGM_WRITE | STGM_CREATE);
|
|
if (pstm)
|
|
{
|
|
if (_CacheShellFolder(htiRoot))
|
|
{
|
|
#ifdef DEBUG
|
|
for (int i=0; i<DPA_GetPtrCount(_hdpaOrd); i++)
|
|
{
|
|
PORDERITEM poi = (PORDERITEM)DPA_GetPtr(_hdpaOrd, i);
|
|
if (poi)
|
|
{
|
|
ASSERTMSG(poi->nOrder >= 0, "nsc saving bogus order list nOrder (%d), get reljai", poi->nOrder);
|
|
}
|
|
}
|
|
#endif
|
|
OrderList_SaveToStream(pstm, _hdpaOrd, _psfCache);
|
|
pstm->Release();
|
|
|
|
// Notify everyone that the order changed
|
|
SHSendChangeMenuNotify(this, SHCNEE_ORDERCHANGED, SHCNF_FLUSH, _pidlRoot);
|
|
_dwOrderSig++;
|
|
|
|
TraceMsg(TF_NSC, "NSCBand: Sent SHCNE_EXTENDED_EVENT : SHCNEE_ORDERCHANGED");
|
|
|
|
// Remove this notify message immediately (so _fDropping is set
|
|
// and we'll ignore this event in above OnChange method)
|
|
//
|
|
// _FlushNotifyMessages(_hwndTree);
|
|
}
|
|
else
|
|
pstm->Release();
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
|
|
DPA_Destroy(_hdpaOrd);
|
|
_hdpaOrd = NULL;
|
|
}
|
|
|
|
//removes any order the user has set and goes back to alphabetical sort
|
|
HRESULT CNscTree::ResetSort(void)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
void CNscTree::MoveItemUpOrDown(BOOL fUp)
|
|
{
|
|
HTREEITEM htiSelected = TreeView_GetSelection(_hwndTree);
|
|
HTREEITEM htiToSwap = (fUp) ? TreeView_GetPrevSibling(_hwndTree, htiSelected) :
|
|
TreeView_GetNextSibling(_hwndTree, htiSelected);
|
|
HTREEITEM htiParent = TreeView_GetParent(_hwndTree, htiSelected);
|
|
if (htiParent == NULL)
|
|
htiParent = TVI_ROOT;
|
|
ASSERT(htiSelected);
|
|
|
|
_fWeChangedOrder = TRUE;
|
|
if (FAILED(_PopulateOrderList(htiParent)))
|
|
return;
|
|
|
|
if (htiSelected && htiToSwap)
|
|
{
|
|
PORDERITEM poiSelected = _GetTreeOrderItem(htiSelected);
|
|
PORDERITEM poiToSwap = _GetTreeOrderItem(htiToSwap);
|
|
|
|
if (poiSelected && poiToSwap)
|
|
{
|
|
int iOrder = poiSelected->nOrder;
|
|
poiSelected->nOrder = poiToSwap->nOrder;
|
|
poiToSwap->nOrder = iOrder;
|
|
}
|
|
|
|
_CacheShellFolder(htiParent);
|
|
|
|
if (_psfCache)
|
|
_Sort(htiParent, _psfCache);
|
|
}
|
|
TreeView_SelectItem(_hwndTree, htiSelected);
|
|
|
|
_FreeOrderList(htiParent);
|
|
_fWeChangedOrder = FALSE;
|
|
}
|
|
|
|
BOOL CNscTree::_OnItemExpandingMsg(NM_TREEVIEW *pnm)
|
|
{
|
|
HCURSOR hCursorOld = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
|
|
BOOL bRet = _OnItemExpanding(pnm->itemNew.hItem, pnm->action,
|
|
(pnm->itemNew.state & TVIS_EXPANDEDONCE), (pnm->itemNew.state & TVIS_EXPANDPARTIAL));
|
|
|
|
SetCursor(hCursorOld);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
//
|
|
// The NSC item is expandable if it is a regular folder and it's not one
|
|
// of those funky non-expandable channel folders.
|
|
//
|
|
BOOL CNscTree::_IsExpandable(HTREEITEM hti)
|
|
{
|
|
BOOL fExpandable = FALSE;
|
|
LPCITEMIDLIST pidlItem = _CacheParentShellFolder(hti, NULL);
|
|
if (pidlItem)
|
|
{
|
|
// make sure item is actually a folder and not a non-expandable channel folder
|
|
// except: in org favs, never expand channel folders
|
|
LPITEMIDLIST pidlTarget = NULL;
|
|
DWORD dwAttr = SHGetAttributes(_psfCache, pidlItem, SFGAO_FOLDER);
|
|
if (dwAttr &&
|
|
!(SUCCEEDED(SHGetNavigateTarget(_psfCache, pidlItem, &pidlTarget, &dwAttr)) &&
|
|
((_mode & MODE_CONTROL) ? TRUE : !IsExpandableChannelFolder(_psfCache, pidlItem))))
|
|
{
|
|
fExpandable = TRUE;
|
|
}
|
|
ILFree(pidlTarget);
|
|
}
|
|
return fExpandable;
|
|
}
|
|
|
|
BOOL CNscTree::_OnItemExpanding(HTREEITEM htiToActivate, UINT action, BOOL fExpandedOnce, BOOL fIsExpandPartial)
|
|
{
|
|
BOOL fReturn = FALSE; // false means let treeview proceed
|
|
if (action != TVE_EXPAND)
|
|
{
|
|
htiToActivate = TreeView_GetParent(_hwndTree, htiToActivate);
|
|
}
|
|
else if (fExpandedOnce && !fIsExpandPartial)
|
|
{
|
|
// Do nothing
|
|
}
|
|
else
|
|
{
|
|
if (_IsExpandable(htiToActivate))
|
|
{
|
|
LPITEMIDLIST pidlParent = _GetFullIDList(htiToActivate);
|
|
if (pidlParent)
|
|
{
|
|
BOOL fOrdered;
|
|
// If we were previously partially expanded, then we need to do a full expand
|
|
_LoadSF(htiToActivate, pidlParent, &fOrdered);
|
|
ILFree(pidlParent);
|
|
}
|
|
}
|
|
|
|
// do not remove + on downlevel because inserting items would not expand htiToActivate
|
|
// instead we will remove the plus if nothing gets added
|
|
if (!fIsExpandPartial && MODE_NORMAL == _mode && IsOS(OS_WHISTLERORGREATER))
|
|
{
|
|
// If we did not add anything we should update this item to let
|
|
// the user know something happened.
|
|
TreeView_SetChildren(_hwndTree, htiToActivate, NSC_CHILDREN_REMOVE);
|
|
}
|
|
|
|
// keep the old behavior for favorites/history/...
|
|
if (MODE_NORMAL == _mode)
|
|
{
|
|
// cannot let treeview proceed with expansion, nothing will be added
|
|
// until background thread is done enumerating
|
|
fReturn = TRUE;
|
|
}
|
|
}
|
|
|
|
_UpdateActiveBorder(htiToActivate);
|
|
return fReturn;
|
|
}
|
|
|
|
HTREEITEM CNscTree::_FindFromRoot(HTREEITEM htiRoot, LPCITEMIDLIST pidl)
|
|
{
|
|
HTREEITEM htiRet = NULL;
|
|
LPITEMIDLIST pidlParent, pidlChild;
|
|
BOOL fFreePidlParent = FALSE;
|
|
#ifdef DEBUG
|
|
TracePIDLAbs(pidl, TEXT("Finding this pidl"));
|
|
TraceHTREE(htiRoot, TEXT("from this root"));
|
|
#endif
|
|
|
|
if (!htiRoot)
|
|
{
|
|
// When in "Normal" mode, we need to use the first child, not the root
|
|
// in order to calculate, because there is no "Invisible" root. On the
|
|
// other hand, History and Favorites have an invisible root: Their
|
|
// parent folder, so they need this fudge.
|
|
htiRoot = (MODE_NORMAL == _mode) ? TreeView_GetChild(_hwndTree, 0) : TVI_ROOT;
|
|
pidlParent = _pidlRoot; // the invisible root.
|
|
}
|
|
else
|
|
{
|
|
pidlParent = _GetFullIDList(htiRoot);
|
|
fFreePidlParent = TRUE;
|
|
}
|
|
|
|
if (pidlParent == NULL)
|
|
return NULL;
|
|
|
|
if (ILIsEqual(pidlParent, pidl))
|
|
{
|
|
if (fFreePidlParent)
|
|
ILFree(pidlParent);
|
|
return htiRoot;
|
|
}
|
|
|
|
pidlChild = ILFindChild(pidlParent, pidl);
|
|
if (pidlChild == NULL)
|
|
{
|
|
if (fFreePidlParent)
|
|
ILFree(pidlParent);
|
|
return NULL; // not root match, no hti
|
|
}
|
|
|
|
// root match, carry on . . .
|
|
|
|
// Are we rooted under the Desktop (i.e. Empty pidl or ILIsEmpty(_pidlRoot))
|
|
IShellFolder *psf = NULL;
|
|
HRESULT hr = IEBindToObject(pidlParent, &psf);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
if (fFreePidlParent)
|
|
ILFree(pidlParent);
|
|
return htiRet;
|
|
}
|
|
|
|
while (htiRoot && psf)
|
|
{
|
|
LPITEMIDLIST pidlItem = ILCloneFirst(pidlChild);
|
|
if (!pidlItem)
|
|
break;
|
|
|
|
htiRoot = _FindChild(psf, htiRoot, pidlItem);
|
|
IShellFolder *psfNext = NULL;
|
|
hr = psf->BindToObject(pidlItem, NULL, IID_PPV_ARG(IShellFolder, &psfNext));
|
|
ILFree(pidlItem);
|
|
if (!htiRoot)
|
|
{
|
|
ATOMICRELEASE(psfNext);
|
|
break;
|
|
}
|
|
psf->Release();
|
|
psf = psfNext;
|
|
pidlChild = _ILNext(pidlChild);
|
|
// if we're down to an empty pidl, we've found it!
|
|
if (ILIsEmpty(pidlChild))
|
|
{
|
|
htiRet = htiRoot;
|
|
break;
|
|
}
|
|
if (FAILED(hr))
|
|
{
|
|
ASSERT(psfNext == NULL);
|
|
break;
|
|
}
|
|
}
|
|
if (psf)
|
|
psf->Release();
|
|
if (fFreePidlParent)
|
|
ILFree(pidlParent);
|
|
#ifdef DEBUG
|
|
TraceHTREE(htiRet, TEXT("Found at"));
|
|
#endif
|
|
|
|
return htiRet;
|
|
}
|
|
|
|
BOOL CNscTree::_FIsItem(IShellFolder *psf, LPCITEMIDLIST pidl, HTREEITEM hti)
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(hti);
|
|
return poi && poi->pidl && 0 == ShortFromResult(psf->CompareIDs(0, poi->pidl, pidl));
|
|
}
|
|
|
|
HRESULT CNscTree::_OnSHNotifyDelete(LPCITEMIDLIST pidl, int *piPosDeleted, HTREEITEM *phtiParent)
|
|
{
|
|
HRESULT hr = S_FALSE;
|
|
HTREEITEM hti = _FindFromRoot(NULL, pidl);
|
|
|
|
if (hti == TVI_ROOT)
|
|
return E_INVALIDARG; // invalid arg, DELETION OF TVI_ROOT
|
|
// need to clear _pidlDrag if the one being deleted is _pidlDrag.
|
|
// handles case where dragging into another folder from within or dragging out.
|
|
if (_pidlDrag)
|
|
{
|
|
LPCITEMIDLIST pidltst = _CacheParentShellFolder(hti, NULL);
|
|
if (pidltst)
|
|
{
|
|
if (0 == ShortFromResult(_psfCache->CompareIDs(0, pidltst, _pidlDrag)))
|
|
_pidlDrag = NULL;
|
|
}
|
|
}
|
|
|
|
if (pidl && (hti != NULL))
|
|
{
|
|
_fIgnoreNextItemExpanding = TRUE;
|
|
|
|
HTREEITEM htiParent = TreeView_GetParent(_hwndTree, hti);
|
|
|
|
if (phtiParent)
|
|
*phtiParent = htiParent;
|
|
|
|
//if caller wants the position of the deleted item, don't reorder the other items
|
|
if (piPosDeleted)
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(hti);
|
|
if (poi)
|
|
{
|
|
*piPosDeleted = poi->nOrder;
|
|
hr = S_OK;
|
|
}
|
|
TreeView_DeleteItem(_hwndTree, hti);
|
|
}
|
|
else
|
|
{
|
|
if (htiParent == NULL)
|
|
htiParent = TVI_ROOT;
|
|
if (TreeView_DeleteItem(_hwndTree, hti))
|
|
{
|
|
_ReorderChildren(htiParent);
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
|
|
_fIgnoreNextItemExpanding = FALSE;
|
|
|
|
if (hti == _htiCut)
|
|
{
|
|
_htiCut = NULL;
|
|
_TreeNukeCutState();
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
BOOL CNscTree::_IsItemNameInTree(LPCITEMIDLIST pidl)
|
|
{
|
|
BOOL fReturn = FALSE;
|
|
HTREEITEM hti = _FindFromRoot(NULL, pidl);
|
|
if (hti)
|
|
{
|
|
WCHAR szTree[MAX_PATH];
|
|
TV_ITEM tvi;
|
|
|
|
tvi.mask = TVIF_TEXT;
|
|
tvi.hItem = hti;
|
|
tvi.pszText = szTree;
|
|
tvi.cchTextMax = ARRAYSIZE(szTree);
|
|
if (TreeView_GetItem(_hwndTree, &tvi))
|
|
{
|
|
IShellFolder* psf;
|
|
LPCITEMIDLIST pidlChild;
|
|
if (SUCCEEDED(_ParentFromItem(pidl, &psf, &pidlChild)))
|
|
{
|
|
WCHAR szName[MAX_PATH];
|
|
if (SUCCEEDED(DisplayNameOf(psf, pidlChild, SHGDN_INFOLDER, szName, ARRAYSIZE(szName))))
|
|
{
|
|
fReturn = (StrCmp(szName, szTree) == 0);
|
|
}
|
|
psf->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
return fReturn;
|
|
}
|
|
//
|
|
// Attempt to perform a rename-in-place. Returns
|
|
//
|
|
// S_OK - rename succeeded
|
|
// S_FALSE - original object not found
|
|
// error - rename failed
|
|
//
|
|
|
|
HRESULT CNscTree::_OnSHNotifyRename(LPCITEMIDLIST pidl, LPCITEMIDLIST pidlNew)
|
|
{
|
|
HTREEITEM hti, htiParent = NULL;
|
|
HRESULT hr = S_FALSE;
|
|
|
|
//
|
|
// If the source and destination belong to the same folder, then
|
|
// it's an in-folder rename.
|
|
//
|
|
LPITEMIDLIST pidlParent = ILCloneParent(pidl);
|
|
LPITEMIDLIST pidlNewParent = ILCloneParent(pidlNew);
|
|
|
|
if (pidlParent && pidlNewParent && IEILIsEqual(pidlParent, pidlNewParent, TRUE) && (hti = _FindFromRoot(NULL, pidl)))
|
|
{
|
|
// to avoid reentering problems
|
|
if (!_IsItemNameInTree(pidlNew))
|
|
{
|
|
HTREEITEM htiSelected = TreeView_GetSelection(_hwndTree);
|
|
|
|
::SendMessage(_hwndTree, WM_SETREDRAW, FALSE, 0);
|
|
if ((_OnSHNotifyDelete(pidl, NULL, &htiParent) != E_INVALIDARG) // invalid arg indication of bogus rename, do not continue.
|
|
&& (_OnSHNotifyCreate(pidlNew, DEFAULTORDERPOSITION, htiParent) == S_OK))
|
|
{
|
|
if (hti == htiSelected)
|
|
{
|
|
hti = _FindFromRoot(NULL, pidlNew);
|
|
_SelectNoExpand(_hwndTree, hti); // do not expand this guy
|
|
}
|
|
// NTRAID 89444: If we renamed the item the user is sitting on,
|
|
// SHBrowseForFolder doesn't realize it and doesn't update the
|
|
// edit control.
|
|
|
|
hr = S_OK;
|
|
}
|
|
::SendMessage(_hwndTree, WM_SETREDRAW, TRUE, 0);
|
|
}
|
|
}
|
|
// rename can be a move, so do not depend on the delete happening successfully.
|
|
else if ((_OnSHNotifyDelete(pidl, NULL, &htiParent) != E_INVALIDARG) // invalid arg indication of bogus rename, do not continue.
|
|
&& (_OnSHNotifyCreate(pidlNew, DEFAULTORDERPOSITION, htiParent) == S_OK))
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
|
|
ILFree(pidlParent);
|
|
ILFree(pidlNewParent);
|
|
|
|
// if user created a new folder and changed the default name but is still in edit mode in defview
|
|
// and then clicked on the + of the parent folder we start enumerating the folder (or stealing items
|
|
// from defview) before defview had time to change the name of the new folder. The result is
|
|
// we enumerate the old name and before we transfer it to the foreground thread shell change notify rename
|
|
// kicks in and we change the item already in the tree. We then merge the items from the enumeration
|
|
// which results in extra folder with the old name.
|
|
// to avoid this we force the reenumeration...
|
|
_dwOrderSig++;
|
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
//
|
|
// To update an item, just find it and invalidate it.
|
|
//
|
|
void CNscTree::_OnSHNotifyUpdateItem(LPCITEMIDLIST pidl, LPITEMIDLIST pidlReal)
|
|
{
|
|
HTREEITEM hti = _FindFromRoot(NULL, pidl);
|
|
if (hti)
|
|
{
|
|
_TreeInvalidateItemInfo(hti, TVIF_TEXT);
|
|
|
|
if (pidlReal && hti != TVI_ROOT)
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(hti);
|
|
_AssignPidl(poi, pidlReal);
|
|
}
|
|
}
|
|
}
|
|
|
|
LPITEMIDLIST CNscTree::_FindHighestDeadItem(LPCITEMIDLIST pidl)
|
|
{
|
|
LPITEMIDLIST pidlRet = NULL;
|
|
LPITEMIDLIST pidlParent = ILCloneParent(pidl);
|
|
if (pidlParent)
|
|
{
|
|
IShellFolder* psf;
|
|
LPCITEMIDLIST pidlChild;
|
|
if (SUCCEEDED(_ParentFromItem(pidlParent, &psf, &pidlChild)))
|
|
{
|
|
DWORD dwAttrib = SFGAO_VALIDATE;
|
|
if (FAILED(psf->GetAttributesOf(1, (LPCITEMIDLIST*)&pidlChild, &dwAttrib)))
|
|
{
|
|
pidlRet = _FindHighestDeadItem(pidlParent);
|
|
}
|
|
|
|
psf->Release();
|
|
}
|
|
ILFree(pidlParent);
|
|
}
|
|
return pidlRet ? pidlRet : ILClone(pidl);
|
|
}
|
|
|
|
void CNscTree::_RemoveDeadBranch(LPCITEMIDLIST pidl)
|
|
{
|
|
LPITEMIDLIST pidlTop = _FindHighestDeadItem(pidl);
|
|
if (pidlTop)
|
|
{
|
|
HTREEITEM hti = _FindFromRoot(NULL, pidlTop);
|
|
if (hti)
|
|
{
|
|
if (!TreeView_DeleteItem(_hwndTree, hti))
|
|
{
|
|
ASSERTMSG(FALSE, "CNscTree::_RemoveDeadBranch: DeleteItem failed in tree control"); // somethings hosed in the tree.
|
|
}
|
|
}
|
|
ILFree(pidlTop);
|
|
}
|
|
}
|
|
|
|
HRESULT CNscTree::_OnSHNotifyUpdateDir(LPCITEMIDLIST pidl)
|
|
{
|
|
HRESULT hr = S_FALSE;
|
|
HTREEITEM hti = _FindFromRoot(NULL, pidl);
|
|
if (hti)
|
|
{ // folder exists in tree refresh folder now if had been loaded by expansion.
|
|
IShellFolder* psf = NULL;
|
|
LPCITEMIDLIST pidlChild;
|
|
if (SUCCEEDED(_ParentFromItem(pidl, &psf, &pidlChild)))
|
|
{
|
|
LPITEMIDLIST pidlReal;
|
|
DWORD dwAttrib = SFGAO_VALIDATE;
|
|
// pidlChild is read-only, so we start
|
|
// off our double validation with getting the "real"
|
|
// pidl which will fall back to a clone
|
|
if (SUCCEEDED(_IdlRealFromIdlSimple(psf, pidlChild, &pidlReal))
|
|
&& SUCCEEDED(psf->GetAttributesOf(1, (LPCITEMIDLIST *)&pidlReal, &dwAttrib)))
|
|
{
|
|
TV_ITEM tvi;
|
|
tvi.mask = TVIF_STATE;
|
|
tvi.stateMask = (TVIS_EXPANDEDONCE | TVIS_EXPANDED | TVIS_EXPANDPARTIAL);
|
|
tvi.hItem = (HTREEITEM)hti;
|
|
if (hti != TVI_ROOT)
|
|
{
|
|
if (!TreeView_GetItem(_hwndTree, &tvi))
|
|
tvi.state = 0;
|
|
}
|
|
|
|
if (hti == TVI_ROOT || tvi.state & TVIS_EXPANDEDONCE)
|
|
{
|
|
hr = _UpdateDir(hti, TRUE);
|
|
}
|
|
else if (!(tvi.state & TVIS_EXPANDEDONCE))
|
|
{
|
|
TreeView_SetChildren(_hwndTree, hti, NSC_CHILDREN_CALLBACK);
|
|
}
|
|
|
|
if (hti != TVI_ROOT)
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(hti);
|
|
_AssignPidl(poi, pidlReal);
|
|
}
|
|
|
|
ILFree(pidlReal);
|
|
}
|
|
else
|
|
{
|
|
_RemoveDeadBranch(pidl);
|
|
}
|
|
|
|
psf->Release();
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNscTree::_GetEnumFlags(IShellFolder *psf, LPCITEMIDLIST pidlFolder, DWORD *pgrfFlags, HWND *phwnd)
|
|
{
|
|
HWND hwnd = NULL;
|
|
DWORD grfFlags = _grfFlags;
|
|
|
|
if (_pFilter)
|
|
{
|
|
LPITEMIDLIST pidlFree = NULL;
|
|
if (pidlFolder == NULL)
|
|
{
|
|
SHGetIDListFromUnk(psf, &pidlFree);
|
|
pidlFolder = pidlFree;
|
|
}
|
|
_pFilter->GetEnumFlags(psf, pidlFolder, &hwnd, &grfFlags);
|
|
|
|
ILFree(pidlFree);
|
|
}
|
|
*pgrfFlags = grfFlags;
|
|
|
|
if (phwnd)
|
|
*phwnd = hwnd;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNscTree::_GetEnum(IShellFolder *psf, LPCITEMIDLIST pidlFolder, IEnumIDList **ppenum)
|
|
{
|
|
HWND hwnd = NULL;
|
|
DWORD grfFlags;
|
|
|
|
_GetEnumFlags(psf, pidlFolder, &grfFlags, &hwnd);
|
|
|
|
// get the enumerator and add the child items for any given pidl
|
|
// REARCHITECT: right now, we don't detect if we actually are dealing with a folder (shell32.dll
|
|
// allows you to create an IShellfolder to a non folder object, so we get bad
|
|
// dialogs, by not passing the hwnd, we don't get the dialogs. we should fix this better. by caching
|
|
// in the tree whether it is a folder or not.
|
|
return psf->EnumObjects(/* _fAutoExpanding ?*/ hwnd, grfFlags, ppenum);
|
|
}
|
|
|
|
BOOL CNscTree::_ShouldShow(IShellFolder* psf, LPCITEMIDLIST pidlFolder, LPCITEMIDLIST pidlItem)
|
|
{
|
|
BOOL bRet = TRUE;
|
|
if (_pFilter)
|
|
{
|
|
LPITEMIDLIST pidlFree = NULL;
|
|
if (pidlFolder == NULL)
|
|
{
|
|
SHGetIDListFromUnk(psf, &pidlFree);
|
|
pidlFolder = pidlFree;
|
|
}
|
|
bRet = (S_OK == _pFilter->ShouldShow(psf, pidlFolder, pidlItem));
|
|
|
|
if (pidlFree)
|
|
ILFree(pidlFree);
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
// updates existing dir only. Not new load.
|
|
HRESULT CNscTree::_UpdateDir(HTREEITEM hti, BOOL fUpdatePidls)
|
|
{
|
|
HRESULT hr = S_FALSE;
|
|
LPITEMIDLIST pidlParent = _GetFullIDList(hti);
|
|
if (pidlParent)
|
|
{
|
|
BOOL fOrdered;
|
|
_fUpdate = TRUE;
|
|
hr = _StartBackgroundEnum(hti, pidlParent, &fOrdered, fUpdatePidls);
|
|
_fUpdate = FALSE;
|
|
ILFree(pidlParent);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
int CNscTree::_TreeItemIndexInHDPA(HDPA hdpa, IShellFolder *psfParent, HTREEITEM hti, int iReverseStart)
|
|
{
|
|
int iIndex = -1;
|
|
|
|
ASSERT(hti);
|
|
|
|
PORDERITEM poi = _GetTreeOrderItem(hti);
|
|
if (poi)
|
|
{
|
|
int celt = DPA_GetPtrCount(hdpa);
|
|
ASSERT(iReverseStart <= celt && iReverseStart >= 0);
|
|
for (int i = iReverseStart-1; i >= 0; i--)
|
|
{
|
|
PORDERITEM poi2 = (PORDERITEM)DPA_GetPtr(hdpa, i);
|
|
if (poi2)
|
|
{
|
|
if (ShortFromResult(_psfCache->CompareIDs(0, poi->pidl, poi2->pidl)) == 0)
|
|
{
|
|
iIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return iIndex;
|
|
}
|
|
|
|
HRESULT CNscTree::_Expand(LPCITEMIDLIST pidl, int iDepth)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
HTREEITEM hti = _ExpandToItem(pidl);
|
|
if (hti)
|
|
{
|
|
hr = _ExpandNode(hti, TVE_EXPAND, iDepth);
|
|
// tvi_root is not a pointer and treeview doesn't check for special
|
|
// values so don't select root to prevent fault
|
|
if (hti != TVI_ROOT)
|
|
_SelectNoExpand(_hwndTree, hti);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNscTree::_ExpandNode(HTREEITEM htiParent, int iCode, int iDepth)
|
|
{
|
|
// nothing to expand
|
|
if (!iDepth)
|
|
return S_OK;
|
|
|
|
_fInExpand = TRUE;
|
|
_uDepth = (UINT)iDepth-1;
|
|
HRESULT hr = TreeView_Expand(_hwndTree, htiParent, iCode) ? S_OK : E_FAIL;
|
|
_uDepth = 0;
|
|
_fInExpand = FALSE;
|
|
|
|
return hr;
|
|
}
|
|
|
|
HTREEITEM CNscTree::_FindChild(IShellFolder *psf, HTREEITEM htiParent, LPCITEMIDLIST pidlChild)
|
|
{
|
|
HTREEITEM hti;
|
|
for (hti = TreeView_GetChild(_hwndTree, htiParent); hti; hti = TreeView_GetNextSibling(_hwndTree, hti))
|
|
{
|
|
if (_FIsItem(psf, pidlChild, hti))
|
|
break;
|
|
}
|
|
return hti;
|
|
}
|
|
|
|
void CNscTree::_ReorderChildren(HTREEITEM htiParent)
|
|
{
|
|
int i = 0;
|
|
HTREEITEM hti;
|
|
for (hti = TreeView_GetChild(_hwndTree, htiParent); hti; hti = TreeView_GetNextSibling(_hwndTree, hti))
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(hti);
|
|
if (poi)
|
|
{
|
|
poi->nOrder = i++; // reset the positions of the nodes.
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::_InsertChild(HTREEITEM htiParent, IShellFolder *psfParent, LPCITEMIDLIST pidlChild,
|
|
BOOL fExpand, BOOL fSimpleToRealIDL, int iPosition, HTREEITEM *phti)
|
|
{
|
|
LPITEMIDLIST pidlReal;
|
|
HRESULT hr;
|
|
HTREEITEM htiNew = NULL;
|
|
|
|
if (fSimpleToRealIDL)
|
|
{
|
|
hr = _IdlRealFromIdlSimple(psfParent, pidlChild, &pidlReal);
|
|
}
|
|
else
|
|
{
|
|
hr = SHILClone(pidlChild, &pidlReal);
|
|
}
|
|
|
|
// review chrisny: no sort here, use compareitems to insert item instead.
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
HTREEITEM htiAfter = TVI_LAST;
|
|
BOOL fOrdered = _IsOrdered(htiParent);
|
|
if (iPosition != DEFAULTORDERPOSITION || !fOrdered)
|
|
{
|
|
if (iPosition == 0)
|
|
htiAfter = TVI_FIRST;
|
|
else
|
|
{
|
|
if (!fOrdered)
|
|
htiAfter = TVI_FIRST;
|
|
|
|
for (HTREEITEM hti = TreeView_GetChild(_hwndTree, htiParent); hti; hti = TreeView_GetNextSibling(_hwndTree, hti))
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(hti);
|
|
if (poi)
|
|
{
|
|
if (fOrdered)
|
|
{
|
|
if (poi->nOrder == iPosition-1)
|
|
{
|
|
htiAfter = hti;
|
|
#ifdef DEBUG
|
|
TraceHTREE(htiAfter, TEXT("Inserting After"));
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ShortFromResult(_CompareIDs(psfParent, pidlReal, poi->pidl)) > 0)
|
|
htiAfter = hti;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((_FindChild(psfParent, htiParent, pidlReal) == NULL))
|
|
{
|
|
int cChildren = 1;
|
|
if (MODE_NORMAL == _mode)
|
|
{
|
|
DWORD dwAttrib = SFGAO_FOLDER | SFGAO_STREAM;
|
|
hr = psfParent->GetAttributesOf(1, (LPCITEMIDLIST*)&pidlReal, &dwAttrib);
|
|
if (SUCCEEDED(hr))
|
|
cChildren = _GetChildren(psfParent, pidlReal, dwAttrib);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
htiNew = _AddItemToTree(htiParent, pidlReal, cChildren, iPosition, htiAfter, TRUE, _IsMarked(htiParent));
|
|
if (htiNew)
|
|
{
|
|
_ReorderChildren(htiParent);
|
|
|
|
if (fExpand)
|
|
_ExpandNode(htiParent, TVE_EXPAND, 1); // force expansion to show new item.
|
|
|
|
//ensure the item is visible after a rename (or external drop, but that should always be a noop)
|
|
if (iPosition != DEFAULTORDERPOSITION)
|
|
TreeView_EnsureVisible(_hwndTree, htiNew);
|
|
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
hr = S_FALSE;
|
|
}
|
|
}
|
|
}
|
|
ILFree(pidlReal);
|
|
}
|
|
|
|
if (phti)
|
|
*phti = htiNew;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CheckForExpandOnce(HWND hwndTree, HTREEITEM hti)
|
|
{
|
|
// Root node always expanded.
|
|
if (hti == TVI_ROOT)
|
|
return S_OK;
|
|
|
|
TV_ITEM tvi;
|
|
tvi.mask = TVIF_STATE | TVIF_CHILDREN;
|
|
tvi.stateMask = (TVIS_EXPANDEDONCE | TVIS_EXPANDED | TVIS_EXPANDPARTIAL);
|
|
tvi.hItem = (HTREEITEM)hti;
|
|
|
|
if (TreeView_GetItem(hwndTree, &tvi))
|
|
{
|
|
if (!(tvi.state & TVIS_EXPANDEDONCE) && (tvi.cChildren == 0))
|
|
{
|
|
TreeView_SetChildren(hwndTree, hti, NSC_CHILDREN_FORCE);
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT _InvokeCommandThunk(IContextMenu * pcm, HWND hwndParent)
|
|
{
|
|
CMINVOKECOMMANDINFOEX ici = {0};
|
|
|
|
ici.cbSize = sizeof(ici);
|
|
ici.hwnd = hwndParent;
|
|
ici.nShow = SW_NORMAL;
|
|
ici.lpVerb = CMDSTR_NEWFOLDERA;
|
|
ici.fMask = CMIC_MASK_UNICODE | CMIC_MASK_FLAG_NO_UI;
|
|
ici.lpVerbW = CMDSTR_NEWFOLDERW;
|
|
|
|
return pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)(&ici));
|
|
}
|
|
|
|
BOOL CNscTree::_IsItemExpanded(HTREEITEM hti)
|
|
{
|
|
// if it's not open, then use it's parent
|
|
TV_ITEM tvi;
|
|
tvi.mask = TVIF_STATE;
|
|
tvi.stateMask = TVIS_EXPANDED;
|
|
tvi.hItem = (HTREEITEM)hti;
|
|
|
|
return (TreeView_GetItem(_hwndTree, &tvi) && (tvi.state & TVIS_EXPANDED));
|
|
}
|
|
|
|
HRESULT CNscTree::CreateNewFolder(HTREEITEM hti)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (hti)
|
|
{
|
|
// If the user selected a folder item (file), we need
|
|
// to bind set the cache to the parent folder.
|
|
LPITEMIDLIST pidl = _GetFullIDList(hti);
|
|
if (pidl)
|
|
{
|
|
ULONG ulAttr = SFGAO_FOLDER; // make sure item is actually a folder
|
|
if (SUCCEEDED(IEGetAttributesOf(pidl, &ulAttr)))
|
|
{
|
|
HTREEITEM htiTarget; // tree item in which new folder is created
|
|
|
|
// Is it a folder?
|
|
if (ulAttr & SFGAO_FOLDER)
|
|
{
|
|
// non-Normal modes (!MODE_NORMAL) wants the new folder to be created as
|
|
// a sibling instead of as a child of the selected folder if it's
|
|
// closed. I assume their reasoning is that closed folders are often
|
|
// selected by accident/default because these views are mostly 1 level.
|
|
// We don't want this functionality for the normal mode.
|
|
if ((MODE_NORMAL != _mode) && !_IsItemExpanded(hti))
|
|
{
|
|
htiTarget = TreeView_GetParent(_hwndTree, hti); // yes, so fine.
|
|
}
|
|
else
|
|
{
|
|
htiTarget = hti;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
htiTarget = TreeView_GetParent(_hwndTree, hti); // No, so bind to the parent.
|
|
}
|
|
|
|
if (NULL == htiTarget)
|
|
{
|
|
htiTarget = TVI_ROOT; // should be synonymous
|
|
}
|
|
|
|
// ensure that this pidl has MenuOrder information (see IE55 #94868)
|
|
if (!_IsOrdered(htiTarget) && _mode != MODE_NORMAL)
|
|
{
|
|
// its not "ordered" (doesn't have reg key persisting order of folder)
|
|
// then create make it ordered
|
|
if (SUCCEEDED(_PopulateOrderList(htiTarget)))
|
|
{
|
|
ASSERT(_hdpaOrd);
|
|
|
|
_FreeOrderList(htiTarget);
|
|
}
|
|
}
|
|
|
|
_CacheShellFolder(htiTarget);
|
|
}
|
|
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
|
|
// If no item is selected, we should still create a folder in whatever
|
|
// the user most recently dinked with. This is important if the
|
|
// Favorites folder is completely empty.
|
|
|
|
if (_psfCache)
|
|
{
|
|
IContextMenu *pcm;
|
|
hr = CoCreateInstance(CLSID_NewMenu, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IContextMenu, &pcm));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
HMENU hmContext = CreatePopupMenu();
|
|
hr = pcm->QueryContextMenu(hmContext, 0, 1, 256, 0);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
_pidlNewFolderParent = _GetFullIDList(_htiCache);
|
|
|
|
IShellExtInit *psei;
|
|
if (SUCCEEDED(pcm->QueryInterface(IID_PPV_ARG(IShellExtInit, &psei))))
|
|
{
|
|
psei->Initialize(_pidlNewFolderParent, NULL, NULL);
|
|
psei->Release();
|
|
}
|
|
hr = _InvokeCommandThunk(pcm, _hwndParent);
|
|
SHChangeNotifyHandleEvents(); // Flush the events to it doesn't take forever to shift into edit mode
|
|
Pidl_Set(&_pidlNewFolderParent, NULL);
|
|
}
|
|
|
|
IUnknown_SetSite(pcm, NULL);
|
|
DestroyMenu(hmContext);
|
|
pcm->Release();
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::_EnterNewFolderEditMode(LPCITEMIDLIST pidlNewFolder)
|
|
{
|
|
HTREEITEM htiNewFolder = _FindFromRoot(NULL, pidlNewFolder);
|
|
LPITEMIDLIST pidlParent = NULL;
|
|
|
|
// 1. Flush all the notifications.
|
|
// 2. Find the new dir in the tree.
|
|
// Expand the parent if needed.
|
|
// 3. Put it into the rename mode.
|
|
SetSelectedItem(pidlNewFolder, FALSE, FALSE, 0);
|
|
|
|
if (htiNewFolder == NULL)
|
|
{
|
|
pidlParent = ILClone(pidlNewFolder);
|
|
ILRemoveLastID(pidlParent);
|
|
HTREEITEM htiParent = _FindFromRoot(NULL, pidlParent);
|
|
|
|
// We are looking for the parent folder. If this is NOT
|
|
// the root, then we need to expand it to show it.
|
|
|
|
// NOTE: If it is root, Tree view will
|
|
// try and deref TVI_ROOT and faults.
|
|
if (htiParent != TVI_ROOT)
|
|
{
|
|
// Try expanding the parent and finding again.
|
|
CheckForExpandOnce(_hwndTree, htiParent);
|
|
TreeView_SelectItem(_hwndTree, htiParent);
|
|
_ExpandNode(htiParent, TVE_EXPAND, 1);
|
|
}
|
|
|
|
htiNewFolder = _FindFromRoot(NULL, pidlNewFolder);
|
|
}
|
|
|
|
if (htiNewFolder == NULL)
|
|
{
|
|
// Something went very wrong here. We are not able to find newly added node.
|
|
// One last try after refreshing the entire tree. (slow)
|
|
// May be we didn't get notification.
|
|
Refresh();
|
|
|
|
htiNewFolder = _FindFromRoot(NULL, pidlNewFolder);
|
|
if (htiNewFolder && (htiNewFolder != TVI_ROOT))
|
|
{
|
|
HTREEITEM htiParent = _FindFromRoot(NULL, pidlParent);
|
|
|
|
// We are looking for the parent folder. If this is NOT
|
|
// the root, then we need to expand it to show it.
|
|
|
|
// NOTE: If it is root, Tree view will
|
|
// try and deref TVI_ROOT and faults.
|
|
if (htiParent != TVI_ROOT)
|
|
{
|
|
CheckForExpandOnce(_hwndTree, htiParent);
|
|
TreeView_SelectItem(_hwndTree, htiParent);
|
|
_ExpandNode(htiParent, TVE_EXPAND, 1);
|
|
}
|
|
}
|
|
|
|
htiNewFolder = _FindFromRoot(NULL, pidlNewFolder);
|
|
}
|
|
|
|
// Put Edit label on the item for possible renaming by user.
|
|
if (htiNewFolder)
|
|
{
|
|
_fOkToRename = TRUE; //otherwise label editing is canceled
|
|
TreeView_EditLabel(_hwndTree, htiNewFolder);
|
|
_fOkToRename = FALSE;
|
|
}
|
|
|
|
if (pidlParent)
|
|
ILFree(pidlParent);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::_OnSHNotifyCreate(LPCITEMIDLIST pidl, int iPosition, HTREEITEM htiParent)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
HTREEITEM hti = NULL;
|
|
|
|
if (ILIsParent(_pidlRoot, pidl, FALSE))
|
|
{
|
|
LPITEMIDLIST pidlParent = ILCloneParent(pidl);
|
|
if (pidlParent)
|
|
{
|
|
hti = _FindFromRoot(NULL, pidlParent);
|
|
ILFree(pidlParent);
|
|
}
|
|
|
|
if (hti)
|
|
{
|
|
// folder exists in tree, if item expanded, load the node, else bag out.
|
|
if (_mode != MODE_NORMAL)
|
|
{
|
|
TV_ITEM tvi;
|
|
if (hti != TVI_ROOT)
|
|
{
|
|
tvi.mask = TVIF_STATE | TVIF_CHILDREN;
|
|
tvi.stateMask = (TVIS_EXPANDEDONCE | TVIS_EXPANDED | TVIS_EXPANDPARTIAL);
|
|
tvi.hItem = (HTREEITEM)hti;
|
|
|
|
if (!TreeView_GetItem(_hwndTree, &tvi))
|
|
return hr;
|
|
|
|
// If we drag and item over to a node which has never beem expanded
|
|
// before we will always fail to add the new node.
|
|
if (!(tvi.state & TVIS_EXPANDEDONCE))
|
|
{
|
|
CheckForExpandOnce(_hwndTree, hti);
|
|
|
|
tvi.mask = TVIF_STATE;
|
|
tvi.stateMask = (TVIS_EXPANDEDONCE | TVIS_EXPANDED | TVIS_EXPANDPARTIAL);
|
|
tvi.hItem = (HTREEITEM)hti;
|
|
|
|
// We need to reset this. This is causing some weird behaviour during drag and drop.
|
|
_fAsyncDrop = FALSE;
|
|
|
|
if (!TreeView_GetItem(_hwndTree, &tvi))
|
|
return hr;
|
|
}
|
|
}
|
|
else
|
|
tvi.state = (TVIS_EXPANDEDONCE); // evil root is always expanded.
|
|
|
|
if (tvi.state & TVIS_EXPANDEDONCE)
|
|
{
|
|
LPCITEMIDLIST pidlChild;
|
|
IShellFolder *psf;
|
|
hr = _ParentFromItem(pidl, &psf, &pidlChild);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (_fAsyncDrop) // inserted via drag/drop
|
|
{
|
|
int iNewPos = _fInsertBefore ? (_iDragDest - 1) : _iDragDest;
|
|
LPITEMIDLIST pidlReal;
|
|
if (SUCCEEDED(_IdlRealFromIdlSimple(psf, pidlChild, &pidlReal)))
|
|
{
|
|
if (_MoveNode(_iDragSrc, iNewPos, pidlReal))
|
|
{
|
|
TraceMsg(TF_NSC, "NSCBand: Reordering Item");
|
|
_fDropping = TRUE;
|
|
_Dropped();
|
|
_fAsyncDrop = FALSE;
|
|
_fDropping = FALSE;
|
|
}
|
|
ILFree(pidlReal);
|
|
}
|
|
_htiCur = NULL;
|
|
_fDragging = _fInserting = _fDropping = FALSE;
|
|
_iDragDest = _iDragSrc = -1;
|
|
}
|
|
else // standard shell notify create or drop with no insert, rename.
|
|
{
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (_iDragDest >= 0)
|
|
iPosition = _iDragDest;
|
|
hr = _InsertChild(hti, psf, pidlChild, BOOLIFY(tvi.state & TVIS_SELECTED), TRUE, iPosition, NULL);
|
|
if (_iDragDest >= 0 &&
|
|
SUCCEEDED(_PopulateOrderList(hti)))
|
|
{
|
|
_fDropping = TRUE;
|
|
_Dropped();
|
|
_fDropping = FALSE;
|
|
}
|
|
}
|
|
}
|
|
psf->Release();
|
|
}
|
|
}
|
|
}
|
|
else // MODE_NORMAL
|
|
{
|
|
// no need to do anything, this item hasn't been expanded yet
|
|
if (TreeView_GetItemState(_hwndTree, hti, TVIS_EXPANDEDONCE) & TVIS_EXPANDEDONCE)
|
|
{
|
|
LPCITEMIDLIST pidlChild;
|
|
IShellFolder *psf;
|
|
if (SUCCEEDED(_ParentFromItem(pidl, &psf, &pidlChild)))
|
|
{
|
|
LPITEMIDLIST pidlReal;
|
|
if (SUCCEEDED(_IdlRealFromIdlSimple(psf, pidlChild, &pidlReal)))
|
|
{
|
|
do // scope
|
|
{
|
|
DWORD dwEnumFlags;
|
|
_GetEnumFlags(psf, pidlChild, &dwEnumFlags, NULL);
|
|
|
|
DWORD dwAttributes = SHGetAttributes(psf, pidlReal, SFGAO_FOLDER | SFGAO_HIDDEN | SFGAO_STREAM);
|
|
// filter out zip files (they are both folders and files but we treat them as files)
|
|
// on downlevel SFGAO_STREAM is the same as SFGAO_HASSTORAGE so we'll let zip files slide through (oh well)
|
|
// better than not adding filesystem folders (that have storage)
|
|
DWORD dwFlags = SFGAO_FOLDER | SFGAO_STREAM;
|
|
if ((dwAttributes & dwFlags) == SFGAO_FOLDER)
|
|
{
|
|
if (!(dwEnumFlags & SHCONTF_FOLDERS))
|
|
break; // item is folder but client does not want folders
|
|
}
|
|
else if (!(dwEnumFlags & SHCONTF_NONFOLDERS))
|
|
break; // item is file, but client only wants folders
|
|
|
|
if (!(dwEnumFlags & SHCONTF_INCLUDEHIDDEN) &&
|
|
(dwAttributes & SFGAO_HIDDEN))
|
|
break;
|
|
|
|
hr = _InsertChild(hti, psf, pidlReal, FALSE, TRUE, iPosition, NULL);
|
|
if (S_OK == hr)
|
|
{
|
|
TreeView_SetChildren(_hwndTree, hti, NSC_CHILDREN_ADD);
|
|
}
|
|
} while (0); // Execute the block only once
|
|
|
|
ILFree(pidlReal);
|
|
}
|
|
psf->Release();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TreeView_SetChildren(_hwndTree, hti, NSC_CHILDREN_CALLBACK);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//if the item is being moved from a folder and we have it's position, we need to fix up the order in the old folder
|
|
if (_mode != MODE_NORMAL && iPosition >= 0) //htiParent && (htiParent != hti) &&
|
|
{
|
|
//item was deleted, need to fixup order info
|
|
_ReorderChildren(htiParent);
|
|
}
|
|
|
|
_UpdateActiveBorder(_htiActiveBorder);
|
|
return hr;
|
|
}
|
|
|
|
//FEATURE: make this void
|
|
HRESULT CNscTree::_OnDeleteItem(NM_TREEVIEW *pnm)
|
|
{
|
|
if (_htiActiveBorder == pnm->itemOld.hItem)
|
|
_htiActiveBorder = NULL;
|
|
|
|
ITEMINFO * pii = (ITEMINFO *) pnm->itemOld.lParam;
|
|
pnm->itemOld.lParam = NULL;
|
|
|
|
OrderItem_Free(pii->poi, TRUE);
|
|
LocalFree(pii);
|
|
pii = NULL;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
void CNscTree::_GetDefaultIconIndex(LPCITEMIDLIST pidl, ULONG ulAttrs, TVITEM *pitem, BOOL fFolder)
|
|
{
|
|
if (_iDefaultFavoriteIcon == 0)
|
|
{
|
|
int iTemp = 0;
|
|
WCHAR psz[MAX_PATH];
|
|
DWORD cchSize = ARRAYSIZE(psz);
|
|
if (SUCCEEDED(AssocQueryString(0, ASSOCSTR_DEFAULTICON, TEXT("InternetShortcut"), NULL, psz, &cchSize)))
|
|
iTemp = PathParseIconLocation(psz);
|
|
|
|
_iDefaultFavoriteIcon = Shell_GetCachedImageIndex(psz, iTemp, 0);
|
|
|
|
cchSize = ARRAYSIZE(psz);
|
|
|
|
if (SUCCEEDED(AssocQueryString(0, ASSOCSTR_DEFAULTICON, TEXT("Folder"), NULL, psz, &cchSize)))
|
|
iTemp = PathParseIconLocation(psz);
|
|
|
|
_iDefaultFolderIcon = Shell_GetCachedImageIndex(psz, iTemp, 0);
|
|
}
|
|
|
|
pitem->iImage = pitem->iSelectedImage = (fFolder) ? _iDefaultFolderIcon : _iDefaultFavoriteIcon;
|
|
}
|
|
|
|
BOOL CNscTree::_LoadOrder(HTREEITEM hti, LPCITEMIDLIST pidl, IShellFolder* psf, HDPA* phdpa)
|
|
{
|
|
BOOL fOrdered = FALSE;
|
|
HDPA hdpaOrder = NULL;
|
|
IStream *pstm = GetOrderStream(pidl, STGM_READ);
|
|
if (pstm)
|
|
{
|
|
OrderList_LoadFromStream(pstm, &hdpaOrder, psf);
|
|
pstm->Release();
|
|
}
|
|
|
|
fOrdered = !((hdpaOrder == NULL) || (DPA_GetPtrCount(hdpaOrder) == 0));
|
|
|
|
//set the tree item's ordered flag
|
|
PORDERITEM poi;
|
|
if (hti == TVI_ROOT)
|
|
{
|
|
_fOrdered = fOrdered;
|
|
}
|
|
else if ((poi = _GetTreeOrderItem(hti)) != NULL)
|
|
{
|
|
poi->lParam = fOrdered;
|
|
}
|
|
|
|
*phdpa = hdpaOrder;
|
|
|
|
return fOrdered;
|
|
}
|
|
|
|
// load shell folder and deal with persisted ordering.
|
|
HRESULT CNscTree::_LoadSF(HTREEITEM htiRoot, LPCITEMIDLIST pidl, BOOL *pfOrdered)
|
|
{
|
|
ASSERT(pfOrdered);
|
|
#ifdef DEBUG
|
|
TraceHTREE(htiRoot, TEXT("Loading the Shell Folder for"));
|
|
#endif
|
|
HRESULT hr = S_OK;
|
|
IDVGetEnum *pdvge;
|
|
if (_pidlNavigatingTo && ILIsEqual(pidl, _pidlNavigatingTo) && SUCCEEDED(IUnknown_QueryService(_punkSite, SID_SFolderView, IID_PPV_ARG(IDVGetEnum, &pdvge))))
|
|
{
|
|
pdvge->Release(); // we don't need this, just checking if view supports enumeration stealing
|
|
// If we want to expand the item that we are navigating to,
|
|
// then let's wait for the CDefView to populate so that we
|
|
// can go steal its contents
|
|
_fExpandNavigateTo = TRUE;
|
|
if (_fNavigationFinished)
|
|
{
|
|
_CacheShellFolder(htiRoot); // make sure we cache folder in case it is misbehaving shell extension
|
|
LPITEMIDLIST pidlClone;
|
|
hr = SHILClone(pidl, &pidlClone);
|
|
if (SUCCEEDED(hr))
|
|
hr = RightPaneNavigationFinished(pidlClone); // function takes ownership of pidl
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = _StartBackgroundEnum(htiRoot, pidl, pfOrdered, FALSE);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNscTree::_StartBackgroundEnum(HTREEITEM htiRoot, LPCITEMIDLIST pidl,
|
|
BOOL *pfOrdered, BOOL fUpdatePidls)
|
|
{
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
if (_CacheShellFolder(htiRoot))
|
|
{
|
|
HDPA hdpaOrder = NULL;
|
|
IShellFolder *psfItem = _psfCache;
|
|
|
|
psfItem->AddRef(); // hang on as adding items may change the cached psfCache
|
|
|
|
*pfOrdered = _LoadOrder(htiRoot, pidl, psfItem, &hdpaOrder);
|
|
DWORD grfFlags;
|
|
DWORD dwSig = 0;
|
|
_GetEnumFlags(psfItem, pidl, &grfFlags, NULL);
|
|
if (htiRoot && htiRoot != TVI_ROOT)
|
|
{
|
|
ITEMINFO *pii = _GetTreeItemInfo(htiRoot);
|
|
if (pii)
|
|
dwSig = pii->dwSig;
|
|
}
|
|
else
|
|
{
|
|
htiRoot = TVI_ROOT;
|
|
}
|
|
|
|
if (_pTaskScheduler)
|
|
{
|
|
// AddNscEnumTask takes ownership of hdpaOrder, but not the pidls
|
|
hr = AddNscEnumTask(_pTaskScheduler, pidl, s_NscEnumCallback, this,
|
|
(UINT_PTR)htiRoot, dwSig, grfFlags, hdpaOrder,
|
|
_pidlExpandingTo, _dwOrderSig, !_fInExpand,
|
|
_uDepth, _fUpdate, fUpdatePidls);
|
|
if (SUCCEEDED(hr) && !_fInExpand)
|
|
{
|
|
_fShouldShowAppStartCursor = TRUE;
|
|
}
|
|
}
|
|
|
|
psfItem->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
// s_NscEnumCallback : Callback function for the background enumration.
|
|
// This function takes ownership of the hdpa and the pidls.
|
|
void CNscTree::s_NscEnumCallback(CNscTree *pns, LPITEMIDLIST pidl, UINT_PTR uId, DWORD dwSig, HDPA hdpa,
|
|
LPITEMIDLIST pidlExpandingTo, DWORD dwOrderSig, UINT uDepth,
|
|
BOOL fUpdate, BOOL fUpdatePidls)
|
|
{
|
|
NSC_BKGDENUMDONEDATA * pbedd = new NSC_BKGDENUMDONEDATA;
|
|
if (pbedd)
|
|
{
|
|
pbedd->pidl = pidl;
|
|
pbedd->hitem = (HTREEITEM)uId;
|
|
pbedd->dwSig = dwSig;
|
|
pbedd->hdpa = hdpa;
|
|
pbedd->pidlExpandingTo = pidlExpandingTo;
|
|
pbedd->dwOrderSig = dwOrderSig;
|
|
pbedd->uDepth = uDepth;
|
|
pbedd->fUpdate = fUpdate;
|
|
pbedd->fUpdatePidls = fUpdatePidls;
|
|
|
|
// get the lock so that we can add the data to the end of the list
|
|
NSC_BKGDENUMDONEDATA **ppbeddWalk = NULL;
|
|
EnterCriticalSection(&pns->_csBackgroundData);
|
|
|
|
// Start at the head. We use a pointer to pointer here to eliminate special cases
|
|
ppbeddWalk = &pns->_pbeddList;
|
|
|
|
// First walk to the end of the list
|
|
while (*ppbeddWalk)
|
|
ppbeddWalk = &(*ppbeddWalk)->pNext;
|
|
|
|
*ppbeddWalk = pbedd;
|
|
LeaveCriticalSection(&pns->_csBackgroundData);
|
|
|
|
// It's ok to ignore the return value here. The data will be cleaned up when the
|
|
// CNscTree object gets destroyed
|
|
if (::IsWindow(pns->_hwndTree))
|
|
::PostMessage(pns->_hwndTree, WM_NSCBACKGROUNDENUMDONE, (WPARAM)NULL, (LPARAM)NULL);
|
|
}
|
|
else
|
|
{
|
|
ILFree(pidl);
|
|
ILFree(pidlExpandingTo);
|
|
OrderList_Destroy(&hdpa, TRUE);
|
|
}
|
|
}
|
|
|
|
BOOL OrderList_Insert(HDPA hdpa, int iIndex, LPITEMIDLIST pidl, int nOrder)
|
|
{
|
|
PORDERITEM poi = OrderItem_Create(pidl, nOrder);
|
|
if (poi)
|
|
{
|
|
if (-1 != DPA_InsertPtr(hdpa, iIndex, poi))
|
|
return TRUE;
|
|
|
|
OrderItem_Free(poi, TRUE); // free pid
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void CNscTree::_EnumBackgroundDone(NSC_BKGDENUMDONEDATA *pbedd)
|
|
{
|
|
HCURSOR hCursorOld = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
|
|
HTREEITEM hti = pbedd->hitem;
|
|
TVITEM tvi;
|
|
tvi.mask = TVIF_PARAM;
|
|
tvi.hItem = hti;
|
|
|
|
// This can fail if the item was moved before the async icon
|
|
// extraction finished for that item.
|
|
ITEMINFO* pii = NULL;
|
|
if (hti != TVI_ROOT && TreeView_GetItem(_hwndTree, &tvi))
|
|
{
|
|
pii = GetPii(tvi.lParam);
|
|
|
|
// Check if we have the right guy
|
|
if (pii->dwSig != pbedd->dwSig)
|
|
{
|
|
// Try to find it using the pidl
|
|
hti = _FindFromRoot(NULL, pbedd->pidl);
|
|
if (hti)
|
|
pii = _GetTreeItemInfo(hti);
|
|
}
|
|
}
|
|
|
|
if ((hti == TVI_ROOT || (pii && pii->dwSig == pbedd->dwSig)) && _CacheShellFolder(hti))
|
|
{
|
|
// Check if the ordering has changed while we were doing the background enumeration
|
|
if (pbedd->dwOrderSig == _dwOrderSig)
|
|
{
|
|
IShellFolder *psfItem = _psfCache;
|
|
psfItem->AddRef(); // hang on as adding items may change the cached psfCache
|
|
|
|
BOOL fInRename = _fInLabelEdit;
|
|
HTREEITEM htiWasRenaming = fInRename ? _htiRenaming : NULL;
|
|
|
|
HTREEITEM htiExpandTo = NULL;
|
|
if (pbedd->pidlExpandingTo)
|
|
htiExpandTo = _FindChild(psfItem, hti, pbedd->pidlExpandingTo);
|
|
|
|
BOOL fParentMarked = _IsMarked(hti);
|
|
BOOL fItemWasAdded = FALSE;
|
|
BOOL fItemAlreadyIn = FALSE;
|
|
|
|
::SendMessage(_hwndTree, WM_SETREDRAW, FALSE, 0);
|
|
|
|
HTREEITEM htiTemp;
|
|
HTREEITEM htiLast = NULL;
|
|
// find last child
|
|
for (htiTemp = TreeView_GetChild(_hwndTree, hti); htiTemp;)
|
|
{
|
|
htiLast = htiTemp;
|
|
htiTemp = TreeView_GetNextSibling(_hwndTree, htiTemp);
|
|
}
|
|
|
|
HTREEITEM htiCur = htiLast;
|
|
BOOL bReorder = FALSE;
|
|
int iCur = DPA_GetPtrCount(pbedd->hdpa);
|
|
for (htiTemp = htiLast; htiTemp;)
|
|
{
|
|
HTREEITEM htiNextChild = TreeView_GetPrevSibling(_hwndTree, htiTemp);
|
|
// must delete in this way or break the linkage of tree.
|
|
int iIndex = _TreeItemIndexInHDPA(pbedd->hdpa, psfItem, htiTemp, iCur);
|
|
if (-1 == iIndex)
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(htiTemp);
|
|
if (poi)
|
|
{
|
|
DWORD dwAttrib = SFGAO_VALIDATE;
|
|
if (FAILED(psfItem->GetAttributesOf(1, (LPCITEMIDLIST*)&poi->pidl, &dwAttrib)))
|
|
{
|
|
TreeView_DeleteItem(_hwndTree, htiTemp);
|
|
if (htiCur == htiTemp)
|
|
{
|
|
htiCur = htiNextChild;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the item is valid but it didn't get enumerated (possible in partial network enumeration)
|
|
// we need to add it to our list of new items
|
|
LPITEMIDLIST pidl = ILClone(poi->pidl);
|
|
if (pidl)
|
|
{
|
|
if (!OrderList_Insert(pbedd->hdpa, iCur, pidl, -1)) //frees the pidl
|
|
{
|
|
// must delete item or our insertion below will be out of whack
|
|
TreeView_DeleteItem(_hwndTree, htiTemp);
|
|
if (htiCur == htiTemp)
|
|
{
|
|
htiCur = htiNextChild;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bReorder = TRUE; // we reinserted the item into the order list, must reorder
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iCur = iIndex; // our next orderlist insertion point
|
|
}
|
|
|
|
htiTemp = htiNextChild;
|
|
}
|
|
|
|
if (!_fOrdered)
|
|
{
|
|
int cAdded = DPA_GetPtrCount(pbedd->hdpa);
|
|
|
|
// htiCur contains the last sibling in that branch
|
|
HTREEITEM htiInsertPosition = htiCur ? htiCur : TVI_FIRST;
|
|
|
|
// Now adding all the new elements starting from the last, since adding at the end of the tree
|
|
// is very slow
|
|
for (int i = cAdded-1; i >= 0; i--)
|
|
{
|
|
PORDERITEM pitoi = (PORDERITEM)DPA_FastGetPtr(pbedd->hdpa, i);
|
|
if (pitoi == NULL)
|
|
break;
|
|
|
|
if (htiCur)
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(htiCur);
|
|
if (poi)
|
|
{
|
|
HRESULT hr = psfItem->CompareIDs(0, pitoi->pidl, poi->pidl);
|
|
// If the item is already there, let's not add it again
|
|
if (ShortFromResult(hr) == 0)
|
|
{
|
|
fItemAlreadyIn = TRUE;
|
|
if (pbedd->fUpdatePidls)
|
|
{
|
|
_AssignPidl(poi, pitoi->pidl);
|
|
}
|
|
// Get to the next item
|
|
htiCur = TreeView_GetPrevSibling(_hwndTree, htiCur);
|
|
htiInsertPosition = htiCur;
|
|
if (!htiCur)
|
|
htiInsertPosition = TVI_FIRST;
|
|
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_ShouldShow(psfItem, pbedd->pidl, pitoi->pidl))
|
|
{
|
|
int cChildren = 1;
|
|
if (MODE_NORMAL == _mode)
|
|
{
|
|
DWORD dwAttrib = SHGetAttributes(psfItem, pitoi->pidl, SFGAO_FOLDER | SFGAO_STREAM);
|
|
cChildren = _GetChildren(psfItem, pitoi->pidl, dwAttrib);
|
|
}
|
|
|
|
// If this is a normal NSC, we need to display the plus sign correctly.
|
|
if (_AddItemToTree(hti, pitoi->pidl, cChildren, pitoi->nOrder, htiInsertPosition, FALSE, fParentMarked))
|
|
{
|
|
fItemWasAdded = TRUE;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // _fOrdered
|
|
{
|
|
if (bReorder)
|
|
{
|
|
OrderList_Reorder(pbedd->hdpa);
|
|
}
|
|
|
|
LPITEMIDLIST pidlParent = _GetFullIDList(hti);
|
|
if (pidlParent)
|
|
{
|
|
int celt = DPA_GetPtrCount(pbedd->hdpa);
|
|
for (int i = 0; i < celt; i++)
|
|
{
|
|
PORDERITEM pitoi = (PORDERITEM)DPA_FastGetPtr(pbedd->hdpa, i);
|
|
if (pitoi == NULL)
|
|
break;
|
|
|
|
LPITEMIDLIST pidlFull = ILCombine(pidlParent, pitoi->pidl);
|
|
if (pidlFull)
|
|
{
|
|
htiTemp = _FindFromRoot(hti, pidlFull);
|
|
// if we DON'T FIND IT add it to the tree . . .
|
|
if (!htiTemp)
|
|
{
|
|
if (_AddItemToTree(hti, pitoi->pidl, 1, pitoi->nOrder, TVI_LAST, FALSE, fParentMarked))
|
|
{
|
|
fItemWasAdded = TRUE;
|
|
}
|
|
else
|
|
{
|
|
ILFree(pidlFull);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PORDERITEM poiItem = _GetTreeOrderItem(htiTemp);
|
|
if (poiItem)
|
|
{
|
|
poiItem->nOrder = pitoi->nOrder;
|
|
}
|
|
fItemAlreadyIn = TRUE;
|
|
}
|
|
ILFree(pidlFull);
|
|
}
|
|
}
|
|
ILFree(pidlParent);
|
|
}
|
|
_Sort(hti, _psfCache);
|
|
}
|
|
|
|
if (fItemWasAdded || fItemAlreadyIn)
|
|
{
|
|
//make sure something is selected, otherwise first click selects instead of expanding/collapsing/navigating
|
|
HTREEITEM htiSelected = TreeView_GetSelection(_hwndTree);
|
|
if (!htiSelected)
|
|
{
|
|
htiSelected = TreeView_GetFirstVisible(_hwndTree);
|
|
_SelectNoExpand(_hwndTree, htiSelected); // do not expand this guy
|
|
}
|
|
|
|
if (hti != TVI_ROOT)
|
|
{
|
|
// if this is updatedir, don't expand the node
|
|
if (!pbedd->fUpdate)
|
|
{
|
|
// Check to see if it's expanded.
|
|
tvi.mask = TVIF_STATE;
|
|
tvi.stateMask = (TVIS_EXPANDEDONCE | TVIS_EXPANDED | TVIS_EXPANDPARTIAL);
|
|
tvi.hItem = hti;
|
|
if (TreeView_GetItem(_hwndTree, &tvi))
|
|
{
|
|
if (!(tvi.state & TVIS_EXPANDED) || (tvi.state & TVIS_EXPANDPARTIAL))
|
|
{
|
|
_fIgnoreNextItemExpanding = TRUE;
|
|
_ExpandNode(hti, TVE_EXPAND, 1);
|
|
_fIgnoreNextItemExpanding = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle full recursive expansion case.
|
|
if (pbedd->uDepth)
|
|
{
|
|
for (htiTemp = TreeView_GetChild(_hwndTree, hti); htiTemp;)
|
|
{
|
|
HTREEITEM htiNextChild = TreeView_GetNextSibling(_hwndTree, htiTemp);
|
|
_ExpandNode(htiTemp, TVE_EXPAND, pbedd->uDepth);
|
|
htiTemp = htiNextChild;
|
|
}
|
|
|
|
if (TVI_ROOT != htiSelected)
|
|
TreeView_EnsureVisible(_hwndTree, htiSelected);
|
|
}
|
|
}
|
|
}
|
|
|
|
// we're doing refresh/update dir, we don't care if items were added or not
|
|
if (pbedd->fUpdate)
|
|
{
|
|
for (htiTemp = TreeView_GetChild(_hwndTree, hti); htiTemp; htiTemp = TreeView_GetNextSibling(_hwndTree, htiTemp))
|
|
{
|
|
PORDERITEM pitoi = _GetTreeOrderItem(htiTemp);
|
|
if (!pitoi)
|
|
break;
|
|
|
|
if (SHGetAttributes(psfItem, pitoi->pidl, SFGAO_FOLDER | SFGAO_STREAM) == SFGAO_FOLDER)
|
|
{
|
|
UINT uState = TVIS_EXPANDED;
|
|
if (TVI_ROOT != htiTemp)
|
|
uState = TreeView_GetItemState(_hwndTree, htiTemp, TVIS_EXPANDEDONCE | TVIS_EXPANDED | TVIS_EXPANDPARTIAL);
|
|
|
|
if (uState & TVIS_EXPANDED)
|
|
{
|
|
LPITEMIDLIST pidlFull = ILCombine(pbedd->pidl, pitoi->pidl);
|
|
if (pidlFull)
|
|
{
|
|
BOOL fOrdered;
|
|
_fUpdate = TRUE;
|
|
_fInExpand = BOOLIFY(uState & TVIS_EXPANDPARTIAL);
|
|
_StartBackgroundEnum(htiTemp, pidlFull, &fOrdered, pbedd->fUpdatePidls);
|
|
_fInExpand = FALSE;
|
|
_fUpdate = FALSE;
|
|
ILFree(pidlFull);
|
|
}
|
|
}
|
|
else if (uState & TVIS_EXPANDEDONCE)
|
|
{
|
|
TreeView_DeleteChildren(_hwndTree, htiTemp);
|
|
TreeView_SetChildren(_hwndTree, htiTemp, NSC_CHILDREN_CALLBACK);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
::SendMessage(_hwndTree, WM_SETREDRAW, TRUE, 0);
|
|
if (htiExpandTo)
|
|
TreeView_EnsureVisible(_hwndTree, htiExpandTo);
|
|
|
|
if (fItemWasAdded && fInRename)
|
|
{
|
|
_fOkToRename = TRUE; //otherwise label editing is canceled
|
|
TreeView_EditLabel(_hwndTree, htiWasRenaming);
|
|
_fOkToRename = FALSE;
|
|
}
|
|
|
|
|
|
psfItem->Release();
|
|
}
|
|
else
|
|
{
|
|
BOOL fOrdered;
|
|
// The order has changed, we need start over again using the new order
|
|
_StartBackgroundEnum(hti, pbedd->pidl, &fOrdered, pbedd->fUpdatePidls);
|
|
}
|
|
}
|
|
|
|
delete pbedd;
|
|
|
|
SetCursor(hCursorOld);
|
|
}
|
|
|
|
|
|
// review chrisny: get rid of this function.
|
|
int CNscTree::_GetChildren(IShellFolder *psf, LPCITEMIDLIST pidl, ULONG ulAttrs)
|
|
{
|
|
int cChildren = 0; // assume none
|
|
|
|
// treat zip folders as files (they are both folders and files but we treat them as files)
|
|
// on downlevel SFGAO_STREAM is the same as SFGAO_HASSTORAGE so we'll let zip files slide through (oh well)
|
|
// better than not adding filesystem folders (that have storage)
|
|
|
|
if (ulAttrs & SFGAO_FOLDER)
|
|
{
|
|
cChildren = I_CHILDRENAUTO; // let treeview handle +'s
|
|
|
|
if (_grfFlags & SHCONTF_FOLDERS)
|
|
{
|
|
// if just folders we can peek at the attributes
|
|
if (SHGetAttributes(psf, pidl, SFGAO_HASSUBFOLDER))
|
|
cChildren = 1;
|
|
}
|
|
|
|
if (cChildren != 1 && (_grfFlags & SHCONTF_NONFOLDERS))
|
|
{
|
|
// there is no SFGAO_ bit that includes non folders so we need to enum
|
|
IShellFolder *psfItem;
|
|
if (SUCCEEDED(psf->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfItem))))
|
|
{
|
|
// if we are showing non folders we have to do an enum to peek down at items below
|
|
IEnumIDList *penum;
|
|
if (S_OK == _GetEnum(psfItem, NULL, &penum))
|
|
{
|
|
ULONG celt;
|
|
LPITEMIDLIST pidlTemp;
|
|
|
|
if (penum->Next(1, &pidlTemp, &celt) == S_OK && celt == 1)
|
|
{
|
|
//do not call ShouldShow here because we will end up without + if the item is filtered out
|
|
//it's better to have an extra + that is going to go away when user clicks on it
|
|
//than to not be able to expand item with valid children
|
|
cChildren = 1;
|
|
ILFree(pidlTemp);
|
|
}
|
|
penum->Release();
|
|
}
|
|
psfItem->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
return cChildren;
|
|
}
|
|
|
|
void CNscTree::_OnGetDisplayInfo(TV_DISPINFO *pnm)
|
|
{
|
|
PORDERITEM poi = GetPoi(pnm->item.lParam);
|
|
LPCITEMIDLIST pidl = _CacheParentShellFolder(pnm->item.hItem, poi->pidl);
|
|
ASSERT(pidl);
|
|
if (pidl == NULL)
|
|
return;
|
|
ASSERT(_psfCache);
|
|
ASSERT(pnm->item.mask & (TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT | TVIF_CHILDREN));
|
|
if (pnm->item.mask & TVIF_TEXT)
|
|
{
|
|
SHELLDETAILS details;
|
|
if (SUCCEEDED(_GetDisplayNameOf(pidl, SHGDN_INFOLDER, &details)))
|
|
StrRetToBuf(&details.str, pidl, pnm->item.pszText, pnm->item.cchTextMax);
|
|
}
|
|
// make sure we set the attributes for those flags that need them
|
|
if (pnm->item.mask & (TVIF_CHILDREN | TVIF_IMAGE | TVIF_SELECTEDIMAGE))
|
|
{
|
|
ULONG ulAttrs = SHGetAttributes(_psfCache, pidl, SFGAO_FOLDER | SFGAO_STREAM | SFGAO_NEWCONTENT);
|
|
// review chrisny: still need to handle notify of changes from
|
|
// other navs.
|
|
|
|
// HACKHACK!!! we're using the TVIS_FOCUSED bit to stored whether there's
|
|
// new content or not.
|
|
if (ulAttrs & SFGAO_NEWCONTENT)
|
|
{
|
|
pnm->item.mask |= TVIF_STATE;
|
|
pnm->item.stateMask = TVIS_FOCUSED; // init state mask to bold
|
|
pnm->item.state = TVIS_FOCUSED; // init state mask to bold
|
|
}
|
|
// Also see if this guy has any child folders
|
|
if (pnm->item.mask & TVIF_CHILDREN)
|
|
pnm->item.cChildren = _GetChildren(_psfCache, pidl, ulAttrs);
|
|
|
|
if (pnm->item.mask & (TVIF_IMAGE | TVIF_SELECTEDIMAGE))
|
|
// We now need to map the item into the right image index.
|
|
_GetDefaultIconIndex(pidl, ulAttrs, &pnm->item, (ulAttrs & SFGAO_FOLDER));
|
|
|
|
_UpdateItemDisplayInfo(pnm->item.hItem);
|
|
}
|
|
// force the treeview to store this so we don't get called back again
|
|
pnm->item.mask |= TVIF_DI_SETITEM;
|
|
}
|
|
|
|
#define SZ_CUTA "cut"
|
|
#define SZ_CUT TEXT(SZ_CUTA)
|
|
#define SZ_RENAMEA "rename"
|
|
#define SZ_RENAME TEXT(SZ_RENAMEA)
|
|
|
|
void CNscTree::_ApplyCmd(HTREEITEM hti, IContextMenu *pcm, UINT idCmd)
|
|
{
|
|
TCHAR szCommandString[40];
|
|
BOOL fHandled = FALSE;
|
|
BOOL fCutting = FALSE;
|
|
|
|
// We need to special case the rename command
|
|
if (SUCCEEDED(ContextMenu_GetCommandStringVerb(pcm, idCmd, szCommandString, ARRAYSIZE(szCommandString))))
|
|
{
|
|
if (StrCmpI(szCommandString, SZ_RENAME)==0)
|
|
{
|
|
TreeView_EditLabel(_hwndTree, hti);
|
|
fHandled = TRUE;
|
|
}
|
|
else if (!StrCmpI(szCommandString, SZ_CUT))
|
|
{
|
|
fCutting = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!fHandled)
|
|
{
|
|
CMINVOKECOMMANDINFO ici = {
|
|
sizeof(CMINVOKECOMMANDINFO),
|
|
0,
|
|
_hwndTree,
|
|
MAKEINTRESOURCEA(idCmd),
|
|
NULL, NULL,
|
|
SW_NORMAL,
|
|
};
|
|
|
|
HRESULT hr = pcm->InvokeCommand(&ici);
|
|
if (fCutting && SUCCEEDED(hr))
|
|
{
|
|
TV_ITEM tvi;
|
|
tvi.mask = TVIF_STATE;
|
|
tvi.stateMask = TVIS_CUT;
|
|
tvi.state = TVIS_CUT;
|
|
tvi.hItem = hti;
|
|
TreeView_SetItem(_hwndTree, &tvi);
|
|
|
|
// _hwndNextViewer = SetClipboardViewer(_hwndTree);
|
|
// _htiCut = hti;
|
|
}
|
|
|
|
//hack to force a selection update, so oc can update it's status text
|
|
if (_mode & MODE_CONTROL)
|
|
{
|
|
HTREEITEM hti = TreeView_GetSelection(_hwndTree);
|
|
|
|
::SendMessage(_hwndTree, WM_SETREDRAW, FALSE, 0);
|
|
TreeView_SelectItem(_hwndTree, NULL);
|
|
|
|
//only select the item if the handle is still valid
|
|
if (hti)
|
|
TreeView_SelectItem(_hwndTree, hti);
|
|
::SendMessage(_hwndTree, WM_SETREDRAW, TRUE, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// perform actions like they were chosen from the context menu, but without showing the menu
|
|
// review: this shouldn't be bstr, we only pass const strings here
|
|
HRESULT CNscTree::_InvokeContextMenuCommand(BSTR strCommand)
|
|
{
|
|
ASSERT(strCommand);
|
|
HTREEITEM htiSelected = TreeView_GetSelection(_hwndTree);
|
|
|
|
if (htiSelected)
|
|
{
|
|
if (StrCmpIW(strCommand, L"rename") == 0)
|
|
{
|
|
_fOkToRename = TRUE; //otherwise label editing is canceled
|
|
TreeView_EditLabel(_hwndTree, htiSelected);
|
|
_fOkToRename = FALSE;
|
|
}
|
|
else
|
|
{
|
|
LPCITEMIDLIST pidl = _CacheParentShellFolder(htiSelected, NULL);
|
|
if (pidl)
|
|
{
|
|
IContextMenu *pcm;
|
|
if (SUCCEEDED(_psfCache->GetUIObjectOf(_hwndTree, 1, &pidl, IID_PPV_ARG_NULL(IContextMenu, &pcm))))
|
|
{
|
|
CHAR szCommand[MAX_PATH];
|
|
SHUnicodeToAnsi(strCommand, szCommand, ARRAYSIZE(szCommand));
|
|
|
|
// QueryContextMenu, even though unused, initializes the folder properly (fixes delete subscription problems)
|
|
HMENU hmenu = CreatePopupMenu();
|
|
if (hmenu)
|
|
pcm->QueryContextMenu(hmenu, 0, 0, 0x7fff, CMF_NORMAL);
|
|
|
|
/* Need to try twice, in case callee is ANSI-only */
|
|
CMINVOKECOMMANDINFOEX ici =
|
|
{
|
|
CMICEXSIZE_NT4, /* Be NT4-compat */
|
|
CMIC_MASK_UNICODE,
|
|
_hwndTree,
|
|
szCommand,
|
|
NULL, NULL,
|
|
SW_NORMAL,
|
|
0, NULL,
|
|
NULL,
|
|
strCommand,
|
|
NULL, NULL,
|
|
NULL,
|
|
};
|
|
|
|
HRESULT hr = pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici);
|
|
if (hr == E_INVALIDARG)
|
|
{
|
|
// Recipient didn't like the unicode command; send an ANSI one
|
|
ici.cbSize = sizeof(CMINVOKECOMMANDINFO);
|
|
ici.fMask &= ~CMIC_MASK_UNICODE;
|
|
hr = pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici);
|
|
}
|
|
|
|
// do any visuals for cut state
|
|
if (SUCCEEDED(hr) && StrCmpIW(strCommand, L"cut") == 0)
|
|
{
|
|
HTREEITEM hti = TreeView_GetSelection(_hwndTree);
|
|
if (hti)
|
|
{
|
|
_TreeSetItemState(hti, TVIS_CUT, TVIS_CUT);
|
|
ASSERT(!_hwndNextViewer);
|
|
_hwndNextViewer = ::SetClipboardViewer(_hwndTree);
|
|
_htiCut = hti;
|
|
}
|
|
}
|
|
if (hmenu)
|
|
DestroyMenu(hmenu);
|
|
pcm->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
//if properties was invoked, who knows what might have changed, so force a reselect
|
|
if (StrCmpNW(strCommand, L"properties", 10) == 0)
|
|
{
|
|
TreeView_SelectItem(_hwndTree, htiSelected);
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// pcm = IContextMenu for the item the user selected
|
|
// hti = the item the user selected
|
|
//
|
|
// Okay, this menu thing is kind of funky.
|
|
//
|
|
// If "Favorites", then everybody gets "Create new folder".
|
|
//
|
|
// If expandable:
|
|
// Show "Expand" or "Collapse"
|
|
// (accordingly) and set it as the default.
|
|
//
|
|
// If not expandable:
|
|
// The default menu of the underlying context menu is
|
|
// used as the default; or use the first item if nobody
|
|
// picked a default.
|
|
//
|
|
// We replace the existing "Open" command with our own.
|
|
//
|
|
|
|
HMENU CNscTree::_CreateContextMenu(IContextMenu *pcm, HTREEITEM hti)
|
|
{
|
|
BOOL fExpandable = _IsExpandable(hti);
|
|
HMENU hmenu = CreatePopupMenu();
|
|
if (hmenu)
|
|
{
|
|
pcm->QueryContextMenu(hmenu, 0, RSVIDM_CONTEXT_START, 0x7fff, CMF_EXPLORE | CMF_CANRENAME);
|
|
|
|
// Always delete "Create shortcut" from the context menu.
|
|
ContextMenu_DeleteCommandByName(pcm, hmenu, RSVIDM_CONTEXT_START, L"link");
|
|
|
|
// Sometimes we need to delete "Open":
|
|
//
|
|
// History mode always. The context menu for history mode folders
|
|
// has "Open" but it doesn't work, so we need to replace it with
|
|
// Expand/Collapse. And the context menu for history mode items
|
|
// has "Open" but it opens in a new window. We want to navigate.
|
|
//
|
|
// Favorites mode, expandable: Leave "Open" alone -- it will open
|
|
// the expandable thing in a new window.
|
|
//
|
|
// Favorites mode, non-expandable: Delete the original "Open" and
|
|
// replace it with ours that does a navigate.
|
|
//
|
|
BOOL fReplaceOpen = (_mode & MODE_HISTORY) || (!fExpandable && (_mode & MODE_FAVORITES));
|
|
if (fReplaceOpen)
|
|
ContextMenu_DeleteCommandByName(pcm, hmenu, RSVIDM_CONTEXT_START, L"open");
|
|
|
|
// Load the NSC part of the context menu and party on it separately.
|
|
// By doing this, we save the trouble of having to do a SHPrettyMenu
|
|
// after we dork it -- Shell_MergeMenus does all the prettying
|
|
// automatically. NOTE: this is totally bogus reasoning - cleaner code the other way around...
|
|
|
|
HMENU hmenuctx = LoadMenuPopup_PrivateNoMungeW(POPUP_CONTEXT_NSC);
|
|
if (hmenuctx)
|
|
{
|
|
// create new folder doesn't make sense outside of favorites
|
|
// (actually, it does, but there's no interface to it)
|
|
if (!(_mode & MODE_FAVORITES))
|
|
DeleteMenu(hmenuctx, RSVIDM_NEWFOLDER, MF_BYCOMMAND);
|
|
|
|
// Of "Expand", "Collapse", or "Open", we will keep at most one of
|
|
// them. idmKeep is the one we choose to keep.
|
|
//
|
|
UINT idmKeep;
|
|
if (fExpandable)
|
|
{
|
|
// Even if the item has no children, we still show Expand.
|
|
// The reason is that an item that has never been expanded
|
|
// is marked as "children: unknown" so we show an Expand
|
|
// and then the user picks it and nothing expands. And then
|
|
// the user clicks it again and the Expand option is gone!
|
|
// (Because the second time, we know that the item isn't
|
|
// expandable.)
|
|
//
|
|
// Better to be consistently wrong than randomly wrong.
|
|
//
|
|
if (_IsItemExpanded(hti))
|
|
idmKeep = RSVIDM_COLLAPSE;
|
|
else
|
|
idmKeep = RSVIDM_EXPAND;
|
|
}
|
|
else if (!(_mode & MODE_CONTROL))
|
|
{
|
|
idmKeep = RSVIDM_OPEN;
|
|
}
|
|
else
|
|
{
|
|
idmKeep = 0;
|
|
}
|
|
|
|
// Now go decide which of RSVIDM_COLLAPSE, RSVIDM_EXPAND, or
|
|
// RSVIDM_OPEN we want to keep.
|
|
//
|
|
if (idmKeep != RSVIDM_EXPAND)
|
|
DeleteMenu(hmenuctx, RSVIDM_EXPAND, MF_BYCOMMAND);
|
|
if (idmKeep != RSVIDM_COLLAPSE)
|
|
DeleteMenu(hmenuctx, RSVIDM_COLLAPSE, MF_BYCOMMAND);
|
|
if (idmKeep != RSVIDM_OPEN)
|
|
DeleteMenu(hmenuctx, RSVIDM_OPEN, MF_BYCOMMAND);
|
|
|
|
// in normal mode we want to gray out expand if folder cannot be expanded
|
|
if (idmKeep == RSVIDM_EXPAND && _mode == MODE_NORMAL)
|
|
{
|
|
TV_ITEM tvi;
|
|
tvi.mask = TVIF_CHILDREN;
|
|
tvi.hItem = hti;
|
|
if (TreeView_GetItem(_hwndTree, &tvi) && !tvi.cChildren)
|
|
{
|
|
EnableMenuItem(hmenuctx, RSVIDM_EXPAND, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
|
|
}
|
|
}
|
|
|
|
Shell_MergeMenus(hmenu, hmenuctx, 0, 0, 0xFFFF, fReplaceOpen ? 0 : MM_ADDSEPARATOR);
|
|
|
|
DestroyMenu(hmenuctx);
|
|
|
|
if (idmKeep)
|
|
SetMenuDefaultItem(hmenu, idmKeep, MF_BYCOMMAND);
|
|
}
|
|
|
|
// Menu item "Open in New Window" needs to be disabled if the restriction is set
|
|
if( SHRestricted2W(REST_NoOpeninNewWnd, NULL, 0))
|
|
{
|
|
EnableMenuItem(hmenu, RSVIDM_CONTEXT_START + RSVIDM_OPEN_NEWWINDOW, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
|
|
}
|
|
|
|
_SHPrettyMenu(hmenu);
|
|
}
|
|
return hmenu;
|
|
}
|
|
|
|
LRESULT CNscTree::_OnContextMenu(short x, short y)
|
|
{
|
|
if (!SHRestricted(REST_NOVIEWCONTEXTMENU))
|
|
{
|
|
HTREEITEM hti;
|
|
POINT ptPopup; // in screen coordinate
|
|
|
|
//assert that the SetFocus() below won't be ripping focus away from anyone
|
|
ASSERT((_mode & MODE_CONTROL) ? (GetFocus() == _hwndTree) : TRUE);
|
|
|
|
if (x == -1 && y == -1)
|
|
{
|
|
// Keyboard-driven: Get the popup position from the selected item.
|
|
hti = TreeView_GetSelection(_hwndTree);
|
|
if (hti)
|
|
{
|
|
RECT rc;
|
|
//
|
|
// Note that TV_GetItemRect returns it in client coordinate!
|
|
//
|
|
TreeView_GetItemRect(_hwndTree, hti, &rc, TRUE);
|
|
//cannot point to middle of item rect because if item name cannot fit into control rect
|
|
//treeview puts tooltip on top and rect returned above is from tooltip whose middle
|
|
//may not be in Treeview which causes problems later in the function
|
|
ptPopup.x = rc.left + 1;
|
|
ptPopup.y = (rc.top + rc.bottom) / 2;
|
|
::MapWindowPoints(_hwndTree, HWND_DESKTOP, &ptPopup, 1);
|
|
}
|
|
//so we can go into rename mode
|
|
_fOkToRename = TRUE;
|
|
}
|
|
else
|
|
{
|
|
TV_HITTESTINFO tvht;
|
|
|
|
// Mouse-driven: Pick the treeitem from the position.
|
|
ptPopup.x = x;
|
|
ptPopup.y = y;
|
|
|
|
tvht.pt = ptPopup;
|
|
::ScreenToClient(_hwndTree, &tvht.pt);
|
|
|
|
hti = TreeView_HitTest(_hwndTree, &tvht);
|
|
}
|
|
|
|
if (hti)
|
|
{
|
|
LPCITEMIDLIST pidl = _CacheParentShellFolder(hti, NULL);
|
|
if (pidl)
|
|
{
|
|
IContextMenu *pcm;
|
|
|
|
TreeView_SelectDropTarget(_hwndTree, hti);
|
|
|
|
if (SUCCEEDED(_psfCache->GetUIObjectOf(_hwndTree, 1, &pidl, IID_PPV_ARG_NULL(IContextMenu, &pcm))))
|
|
{
|
|
pcm->QueryInterface(IID_PPV_ARG(IContextMenu2, &_pcmSendTo));
|
|
|
|
HMENU hmenu = _CreateContextMenu(pcm, hti);
|
|
if (hmenu)
|
|
{
|
|
_pcm = pcm; // for IContextMenu2 code
|
|
|
|
// use _hwnd so menu msgs go there and I can forward them
|
|
// using IContextMenu2 so "Sent To" works
|
|
|
|
// review chrisny: useTrackPopupMenuEx for clipping etc.
|
|
UINT idCmd = TrackPopupMenu(hmenu,
|
|
TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN,
|
|
ptPopup.x, ptPopup.y, 0, _hwndTree, NULL);
|
|
// Note: must requery selected item to verify that the hti is good. This
|
|
// solves the problem where the hti was deleted, hence pointed to something
|
|
// bogus, then we write to it causing heap corruption, while the menu was up.
|
|
TV_HITTESTINFO tvht;
|
|
tvht.pt = ptPopup;
|
|
::ScreenToClient(_hwndTree, &tvht.pt);
|
|
hti = TreeView_HitTest(_hwndTree, &tvht);
|
|
if (hti && idCmd)
|
|
{
|
|
switch (idCmd)
|
|
{
|
|
case RSVIDM_OPEN:
|
|
case RSVIDM_EXPAND:
|
|
case RSVIDM_COLLAPSE:
|
|
TreeView_SelectItem(_hwndTree, hti);
|
|
// turn off flag, so select will have an effect.
|
|
_fOkToRename = FALSE;
|
|
_OnSelChange(FALSE); // selection has changed, force the navigation.
|
|
// SelectItem may not expand (if was closed and selected)
|
|
TreeView_Expand(_hwndTree, hti, idCmd == RSVIDM_COLLAPSE ? TVE_COLLAPSE : TVE_EXPAND);
|
|
break;
|
|
|
|
// This WAS unix only, now win32 does it too
|
|
// IEUNIX : We allow new folder creation from context menu. since
|
|
// this control was used to organize favorites in IEUNIX4.0
|
|
case RSVIDM_NEWFOLDER:
|
|
CreateNewFolder(hti);
|
|
break;
|
|
|
|
default:
|
|
_ApplyCmd(hti, pcm, idCmd-RSVIDM_CONTEXT_START);
|
|
break;
|
|
}
|
|
|
|
//we must have had focus before (asserted above), but we might have lost it after a delete.
|
|
//get it back.
|
|
//this is only a problem in the nsc oc.
|
|
if ((_mode & MODE_CONTROL) && !_fInLabelEdit)
|
|
::SetFocus(_hwndTree);
|
|
}
|
|
ATOMICRELEASE(_pcmSendTo);
|
|
DestroyMenu(hmenu);
|
|
_pcm = NULL;
|
|
}
|
|
pcm->Release();
|
|
}
|
|
TreeView_SelectDropTarget(_hwndTree, NULL);
|
|
}
|
|
}
|
|
|
|
if (x == -1 && y == -1)
|
|
_fOkToRename = FALSE;
|
|
}
|
|
|
|
return S_FALSE; // So WM_CONTEXTMENU message will not come.
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::_QuerySelection(IContextMenu **ppcm, HTREEITEM *phti)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
HTREEITEM hti = TreeView_GetSelection(_hwndTree);
|
|
if (hti)
|
|
{
|
|
LPCITEMIDLIST pidl = _CacheParentShellFolder(hti, NULL);
|
|
if (pidl)
|
|
{
|
|
if (ppcm)
|
|
{
|
|
hr = _psfCache->GetUIObjectOf(_hwndTree, 1, &pidl, IID_PPV_ARG_NULL(IContextMenu, ppcm));
|
|
}
|
|
else
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (phti)
|
|
*phti = hti;
|
|
|
|
return hr;
|
|
}
|
|
|
|
LRESULT NSCEditBoxSubclassWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
|
|
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
if (uIdSubclass == ID_NSC_SUBCLASS && uMsg == WM_GETDLGCODE)
|
|
{
|
|
return DLGC_WANTMESSAGE;
|
|
}
|
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
LRESULT CNscTree::_OnBeginLabelEdit(TV_DISPINFO *ptvdi)
|
|
{
|
|
BOOL fCantRename = TRUE;
|
|
LPCITEMIDLIST pidl = _CacheParentShellFolder(ptvdi->item.hItem, NULL);
|
|
if (pidl)
|
|
{
|
|
if (SHGetAttributes(_psfCache, pidl, SFGAO_CANRENAME))
|
|
fCantRename = FALSE;
|
|
}
|
|
|
|
HWND hwndEdit = (HWND)::SendMessage(_hwndTree, TVM_GETEDITCONTROL, 0, 0);
|
|
if (hwndEdit)
|
|
{
|
|
WCHAR szName[MAX_PATH];
|
|
if (SUCCEEDED(DisplayNameOf(_psfCache, pidl, SHGDN_INFOLDER|SHGDN_FOREDITING, szName, ARRAYSIZE(szName))))
|
|
{
|
|
SHLimitInputEdit(hwndEdit, _psfCache);
|
|
::SetWindowText(hwndEdit, szName);
|
|
}
|
|
|
|
SetWindowSubclass(hwndEdit, NSCEditBoxSubclassWndProc, ID_NSC_SUBCLASS, NULL);
|
|
}
|
|
|
|
_fInLabelEdit = !fCantRename;
|
|
if (_fInLabelEdit)
|
|
_htiRenaming = ptvdi->item.hItem;
|
|
|
|
return fCantRename;
|
|
}
|
|
|
|
//
|
|
// Utility function for CNSCTree::_OnEndLabelEdit
|
|
// Does not set the new value in the tree view if the old
|
|
// value is the same.
|
|
//
|
|
BOOL CNscTree::_LabelEditIsNewValueValid(TV_DISPINFO *ptvdi)
|
|
{
|
|
ASSERT(ptvdi && ptvdi->item.hItem);
|
|
|
|
TCHAR szOldValue[MAX_PATH];
|
|
|
|
szOldValue[0] = '\0';
|
|
|
|
TV_ITEM tvi;
|
|
tvi.mask = TVIF_TEXT;
|
|
tvi.hItem = (HTREEITEM)ptvdi->item.hItem;
|
|
tvi.pszText = szOldValue;
|
|
tvi.cchTextMax = ARRAYSIZE(szOldValue);
|
|
TreeView_GetItem(_hwndTree, &tvi);
|
|
|
|
//
|
|
// is the old value in the control unequal to the new one?
|
|
//
|
|
return (0 != StrCmp(tvi.pszText, ptvdi->item.pszText));
|
|
}
|
|
|
|
LRESULT CNscTree::_OnEndLabelEdit(TV_DISPINFO *ptvdi)
|
|
{
|
|
HWND hwndEdit = (HWND)::SendMessage(_hwndTree, TVM_GETEDITCONTROL, 0, 0);
|
|
if (hwndEdit)
|
|
{
|
|
RemoveWindowSubclass(hwndEdit, NSCEditBoxSubclassWndProc, ID_NSC_SUBCLASS);
|
|
}
|
|
|
|
if ((ptvdi->item.pszText != NULL) && _LabelEditIsNewValueValid(ptvdi))
|
|
{
|
|
ASSERT(ptvdi->item.hItem);
|
|
|
|
LPCITEMIDLIST pidl = _CacheParentShellFolder(ptvdi->item.hItem, NULL);
|
|
if (pidl)
|
|
{
|
|
WCHAR wszName[MAX_PATH - 5]; //-5 to work around nt4 shell32 bug
|
|
SHTCharToUnicode(ptvdi->item.pszText, wszName, ARRAYSIZE(wszName));
|
|
|
|
if (SUCCEEDED(_psfCache->SetNameOf(_hwndTree, pidl, wszName, 0, NULL)))
|
|
{
|
|
// NOTES: pidl is no longer valid here.
|
|
|
|
// Set the handle to NULL in the notification to let
|
|
// the system know that the pointer is probably not
|
|
// valid anymore.
|
|
ptvdi->item.hItem = NULL;
|
|
_FlushNotifyMessages(_hwndTree); // do this last, else we get bad results
|
|
_fInLabelEdit = FALSE;
|
|
}
|
|
else
|
|
{
|
|
// not leaving label edit mode here, so do not set _fInLabelEdit to FALSE or we
|
|
// will not get ::TranslateAcceleratorIO() and backspace, etc, will not work.
|
|
_fOkToRename = TRUE; //otherwise label editing is canceled
|
|
::SendMessage(_hwndTree, TVM_EDITLABEL, (WPARAM)ptvdi->item.pszText, (LPARAM)ptvdi->item.hItem);
|
|
_fOkToRename = FALSE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
_fInLabelEdit = FALSE;
|
|
|
|
if (!_fInLabelEdit)
|
|
_htiRenaming = NULL;
|
|
|
|
//else user cancelled, nothing to do here.
|
|
return 0; // We always return 0, "we handled it".
|
|
}
|
|
|
|
BOOL _DidDropOnRecycleBin(IDataObject *pdtobj)
|
|
{
|
|
CLSID clsid;
|
|
return SUCCEEDED(DataObj_GetBlob(pdtobj, g_cfTargetCLSID, &clsid, sizeof(clsid))) &&
|
|
IsEqualCLSID(clsid, CLSID_RecycleBin);
|
|
}
|
|
|
|
void CNscTree::_OnBeginDrag(NM_TREEVIEW *pnmhdr)
|
|
{
|
|
LPCITEMIDLIST pidl = _CacheParentShellFolder(pnmhdr->itemNew.hItem, NULL);
|
|
_htiDragging = pnmhdr->itemNew.hItem; // item we are dragging.
|
|
if (pidl)
|
|
{
|
|
if (_pidlDrag)
|
|
{
|
|
ILFree(_pidlDrag);
|
|
_pidlDrag = NULL;
|
|
}
|
|
|
|
DWORD dwEffect = SHGetAttributes(_psfCache, pidl, DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK);
|
|
if (dwEffect)
|
|
{
|
|
IDataObject *pdtobj;
|
|
HRESULT hr = _psfCache->GetUIObjectOf(_hwndTree, 1, &pidl, IID_PPV_ARG_NULL(IDataObject, &pdtobj));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
HWND hwndTT;
|
|
|
|
_fDragging = TRUE;
|
|
if (hwndTT = TreeView_GetToolTips(_hwndTree))
|
|
::SendMessage(hwndTT, TTM_POP, (WPARAM) 0, (LPARAM) 0);
|
|
PORDERITEM poi = _GetTreeOrderItem(pnmhdr->itemNew.hItem);
|
|
if (poi)
|
|
{
|
|
_iDragSrc = poi->nOrder;
|
|
TraceMsg(TF_NSC, "NSCBand: Starting Drag");
|
|
_pidlDrag = ILClone(poi->pidl);
|
|
_htiFolderStart = TreeView_GetParent(_hwndTree, pnmhdr->itemNew.hItem);
|
|
if (_htiFolderStart == NULL)
|
|
_htiFolderStart = TVI_ROOT;
|
|
}
|
|
else
|
|
{
|
|
_iDragSrc = -1;
|
|
_pidlDrag = NULL;
|
|
_htiFolderStart = NULL;
|
|
}
|
|
|
|
//
|
|
// Don't allow drag and drop of channels if
|
|
// REST_NoRemovingChannels is set.
|
|
//
|
|
if (!SHRestricted2(REST_NoRemovingChannels, NULL, 0) ||
|
|
!_IsChannelFolder(_htiDragging))
|
|
{
|
|
HIMAGELIST himlDrag;
|
|
|
|
SHLoadOLE(SHELLNOTIFY_OLELOADED); // Browser Only - our shell32 doesn't know ole has been loaded
|
|
|
|
_fStartingDrag = TRUE;
|
|
IDragSourceHelper* pdsh = NULL;
|
|
if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARG(IDragSourceHelper, &pdsh))))
|
|
{
|
|
pdsh->InitializeFromWindow(_hwndTree, &pnmhdr->ptDrag, pdtobj);
|
|
_fStartingDrag = FALSE;
|
|
}
|
|
else
|
|
{
|
|
himlDrag = TreeView_CreateDragImage(_hwndTree, pnmhdr->itemNew.hItem);
|
|
_fStartingDrag = FALSE;
|
|
|
|
if (himlDrag)
|
|
{
|
|
DAD_SetDragImage(himlDrag, NULL);
|
|
}
|
|
}
|
|
|
|
hr = SHDoDragDrop(_hwndTree, pdtobj, NULL, dwEffect, &dwEffect);
|
|
|
|
// the below follows the logic in defview for non-filesystem deletes.
|
|
InitClipboardFormats();
|
|
if ((DRAGDROP_S_DROP == hr) &&
|
|
(DROPEFFECT_MOVE == dwEffect) &&
|
|
(DROPEFFECT_MOVE == DataObj_GetDWORD(pdtobj, g_cfPerformedEffect, DROPEFFECT_NONE)))
|
|
{
|
|
// enable UI for the recycle bin case (the data will be lost
|
|
// as the recycle bin really can't recycle stuff that is not files)
|
|
|
|
UINT uFlags = _DidDropOnRecycleBin(pdtobj) ? 0 : CMIC_MASK_FLAG_NO_UI;
|
|
SHInvokeCommandOnDataObject(_hwndTree, NULL, pdtobj, uFlags, "delete");
|
|
}
|
|
else if (dwEffect == DROPEFFECT_NONE)
|
|
{
|
|
// nothing happened when the d&d terminated, so clean up you fool.
|
|
ILFree(_pidlDrag);
|
|
_pidlDrag = NULL;
|
|
}
|
|
|
|
if (pdsh)
|
|
{
|
|
pdsh->Release();
|
|
}
|
|
else
|
|
{
|
|
DAD_SetDragImage((HIMAGELIST)-1, NULL);
|
|
ImageList_Destroy(himlDrag);
|
|
}
|
|
}
|
|
|
|
_iDragSrc = -1;
|
|
pdtobj->Release();
|
|
}
|
|
}
|
|
}
|
|
_htiDragging = NULL;
|
|
}
|
|
|
|
BOOL IsExpandableChannelFolder(IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
if (WhichPlatform() == PLATFORM_INTEGRATED)
|
|
return SHIsExpandableFolder(psf, pidl);
|
|
|
|
ASSERT(pidl);
|
|
ASSERT(psf);
|
|
|
|
BOOL fExpand = FALSE;
|
|
IShellFolder* psfChannelFolder;
|
|
if (pidl && psf && SUCCEEDED(SHBindToObject(psf, IID_X_PPV_ARG(IShellFolder, pidl, &psfChannelFolder))))
|
|
{
|
|
IEnumIDList *penum;
|
|
if (S_OK == psfChannelFolder->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &penum))
|
|
{
|
|
ULONG celt;
|
|
LPITEMIDLIST pidlTemp;
|
|
|
|
if (penum->Next(1, &pidlTemp, &celt) == S_OK && celt == 1)
|
|
{
|
|
ILFree(pidlTemp);
|
|
fExpand = FALSE;
|
|
}
|
|
if (penum->Next(1, &pidlTemp, &celt) == S_OK && celt == 1)
|
|
{
|
|
ILFree(pidlTemp);
|
|
fExpand = TRUE;
|
|
}
|
|
penum->Release();
|
|
}
|
|
psfChannelFolder->Release();
|
|
}
|
|
|
|
return fExpand;
|
|
}
|
|
|
|
BOOL CNscTree::_OnSelChange(BOOL fMark)
|
|
{
|
|
BOOL fExpand = TRUE;
|
|
HTREEITEM hti = TreeView_GetSelection(_hwndTree);
|
|
BOOL fMultiSelect = _dwFlags & NSS_MULTISELECT;
|
|
|
|
//if we're in control mode (where pnscProxy always null), never navigate
|
|
if (hti)
|
|
{
|
|
LPCITEMIDLIST pidlItem = _CacheParentShellFolder(hti, NULL);
|
|
if (pidlItem && !fMultiSelect)
|
|
{
|
|
if (_pnscProxy && !_fInSelectPidl)
|
|
{
|
|
ULONG ulAttrs = SFGAO_FOLDER | SFGAO_NEWCONTENT;
|
|
LPITEMIDLIST pidlTarget;
|
|
LPITEMIDLIST pidlFull = _GetFullIDList(hti);
|
|
HRESULT hr = _pnscProxy->GetNavigateTarget(pidlFull, &pidlTarget, &ulAttrs);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (hr == S_OK)
|
|
{
|
|
_pnscProxy->Invoke(pidlTarget);
|
|
ILFree(pidlTarget);
|
|
}
|
|
// review chrisny: still need to handle notify of changes from
|
|
// other navs.
|
|
if (ulAttrs & SFGAO_NEWCONTENT)
|
|
{
|
|
TV_ITEM tvi;
|
|
tvi.hItem = hti;
|
|
tvi.mask = TVIF_STATE | TVIF_HANDLE;
|
|
tvi.stateMask = TVIS_FOCUSED; // the BOLD bit is to be
|
|
tvi.state = 0; // cleared
|
|
|
|
TreeView_SetItem(_hwndTree, &tvi);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(SHGetAttributes(_psfCache, pidlItem, SFGAO_FOLDER)))
|
|
SHInvokeDefaultCommand(_hwndTree, _psfCache, pidlItem);
|
|
}
|
|
|
|
ILFree(pidlFull);
|
|
fExpand = hr != S_OK && (ulAttrs & SFGAO_FOLDER);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fMultiSelect)
|
|
{
|
|
if (fMark)
|
|
{
|
|
UINT uState = TreeView_GetItemState(_hwndTree, hti, NSC_TVIS_MARKED) & NSC_TVIS_MARKED;
|
|
|
|
uState ^= NSC_TVIS_MARKED;
|
|
_MarkChildren(hti, uState == NSC_TVIS_MARKED);
|
|
_htiActiveBorder = NULL;
|
|
}
|
|
}
|
|
else if (!_fSingleExpand && fExpand && (_mode != MODE_NORMAL))
|
|
{
|
|
TreeView_Expand(_hwndTree, hti, TVE_TOGGLE);
|
|
}
|
|
|
|
if (!fMultiSelect)
|
|
_UpdateActiveBorder(hti);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CNscTree::_OnSetSelection()
|
|
{
|
|
HTREEITEM hti = TreeView_GetSelection(_hwndTree);
|
|
LPITEMIDLIST pidlItem = _GetFullIDList(hti);
|
|
|
|
if (_pnscProxy && !_fInSelectPidl)
|
|
{
|
|
_pnscProxy->OnSelectionChanged(pidlItem);
|
|
}
|
|
|
|
ILFree(pidlItem);
|
|
}
|
|
|
|
void CNscTree::_OnGetInfoTip(NMTVGETINFOTIP* pnm)
|
|
{
|
|
// No info tip operation on drag/drop
|
|
if (_fDragging || _fDropping || _fClosing || _fHandlingShellNotification || _fInSelectPidl)
|
|
return;
|
|
|
|
PORDERITEM poi = GetPoi(pnm->lParam);
|
|
if (poi)
|
|
{
|
|
LPITEMIDLIST pidl = _CacheParentShellFolder(pnm->hItem, poi->pidl);
|
|
if (pidl)
|
|
{
|
|
// Use the imported Browseui function because the one in shell\lib does
|
|
// not work on browser-only platforms
|
|
GetInfoTip(_psfCache, pidl, pnm->pszText, pnm->cchTextMax);
|
|
}
|
|
}
|
|
}
|
|
|
|
LRESULT CNscTree::_OnSetCursor(NMMOUSE* pnm)
|
|
{
|
|
if (_mode == MODE_NORMAL && _fShouldShowAppStartCursor)
|
|
{
|
|
SetCursor(LoadCursor(NULL, IDC_APPSTARTING));
|
|
return 1;
|
|
}
|
|
|
|
if (!pnm->dwItemData)
|
|
{
|
|
SetCursor(LoadCursor(NULL, IDC_ARROW));
|
|
return 1;
|
|
}
|
|
|
|
if (!(_mode & MODE_CONTROL) && (_mode != MODE_NORMAL))
|
|
{
|
|
ITEMINFO* pii = GetPii(pnm->dwItemData);
|
|
if (pii)
|
|
{
|
|
if (!pii->fNavigable)
|
|
{
|
|
//folders always get the arrow
|
|
SetCursor(LoadCursor(NULL, IDC_ARROW));
|
|
}
|
|
else
|
|
{
|
|
//favorites always get some form of the hand
|
|
HCURSOR hCursor = pii->fGreyed ? (HCURSOR)LoadCursor(HINST_THISDLL, MAKEINTRESOURCE(IDC_OFFLINE_HAND)) :
|
|
LoadHandCursor(0);
|
|
if (hCursor)
|
|
SetCursor(hCursor);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//always show the arrow in org favs
|
|
SetCursor(LoadCursor(NULL, IDC_ARROW));
|
|
}
|
|
|
|
return 1; // 1 if We handled it, 0 otherwise
|
|
}
|
|
|
|
BOOL CNscTree::_IsTopParentItem(HTREEITEM hti)
|
|
{
|
|
return (hti && (!TreeView_GetParent(_hwndTree, hti)));
|
|
}
|
|
|
|
LRESULT CNscTree::_OnNotify(LPNMHDR pnm)
|
|
{
|
|
LRESULT lres = 0;
|
|
|
|
switch (pnm->idFrom)
|
|
{
|
|
case ID_CONTROL:
|
|
{
|
|
switch (pnm->code)
|
|
{
|
|
case NM_CUSTOMDRAW:
|
|
return _OnCDNotify((LPNMCUSTOMDRAW)pnm);
|
|
|
|
case TVN_GETINFOTIP:
|
|
// no info tips on drag/drop ops
|
|
// According to Bug#241601, Tooltips display too quickly. The problem is
|
|
// the original designer of the InfoTips in the Treeview merged the "InfoTip" tooltip and
|
|
// the "I'm too small to display correctly" tooltips. This is really unfortunate because you
|
|
// cannot control the display of these tooltips independantly. Therefore we are turning off
|
|
// infotips in normal mode.
|
|
if (!_fInLabelEdit && _mode != MODE_NORMAL)
|
|
_OnGetInfoTip((NMTVGETINFOTIP*)pnm);
|
|
else
|
|
return FALSE;
|
|
break;
|
|
|
|
case NM_SETCURSOR:
|
|
lres = _OnSetCursor((NMMOUSE*)pnm);
|
|
break;
|
|
|
|
case NM_SETFOCUS:
|
|
case NM_KILLFOCUS:
|
|
if (pnm->code == NM_KILLFOCUS)
|
|
{
|
|
_fHasFocus = FALSE;
|
|
|
|
//invalidate the item because tabbing away doesn't
|
|
RECT rc;
|
|
|
|
// Tree can focus and not have any items.
|
|
HTREEITEM hti = TreeView_GetSelection(_hwndTree);
|
|
if (hti)
|
|
{
|
|
TreeView_GetItemRect(_hwndTree, hti, &rc, FALSE);
|
|
//does this need to be UpdateWindow? only if focus rect gets left behind.
|
|
::InvalidateRect(_hwndTree, &rc, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_fHasFocus = TRUE;
|
|
}
|
|
|
|
// do this for both set and kill focus...
|
|
if (_dwFlags & NSS_MULTISELECT)
|
|
{
|
|
HTREEITEM hti = TreeView_GetNextItem(_hwndTree, NULL, TVGN_FIRSTVISIBLE);
|
|
while (hti)
|
|
{
|
|
UINT uState = TreeView_GetItemState(_hwndTree, hti, NSC_TVIS_MARKED);
|
|
if (uState & NSC_TVIS_MARKED)
|
|
{
|
|
RECT rc;
|
|
|
|
TreeView_GetItemRect(_hwndTree, hti, &rc, FALSE);
|
|
//does this need to be UpdateWindow? only if focus rect gets left behind.
|
|
::InvalidateRect(_hwndTree, &rc, FALSE);
|
|
}
|
|
hti = TreeView_GetNextItem(_hwndTree, hti, TVGN_NEXTVISIBLE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TVN_KEYDOWN:
|
|
{
|
|
TV_KEYDOWN *ptvkd = (TV_KEYDOWN *) pnm;
|
|
switch (ptvkd->wVKey)
|
|
{
|
|
case VK_RETURN:
|
|
case VK_SPACE:
|
|
_OnSelChange(TRUE);
|
|
lres = TRUE;
|
|
break;
|
|
|
|
case VK_DELETE:
|
|
if (!((_mode & MODE_HISTORY) && IsInetcplRestricted(L"History")))
|
|
{
|
|
// in explorer band we never come here
|
|
// and in browse for folder we cannot ignore the selection
|
|
// because we will end up with nothing selected
|
|
if (_mode != MODE_NORMAL)
|
|
_fIgnoreNextSelChange = TRUE;
|
|
InvokeContextMenuCommand(L"delete");
|
|
}
|
|
break;
|
|
|
|
case VK_UP:
|
|
case VK_DOWN:
|
|
//VK_MENU == VK_ALT
|
|
if ((_mode != MODE_HISTORY) && (_mode != MODE_NORMAL) && (GetKeyState(VK_MENU) < 0))
|
|
{
|
|
MoveItemUpOrDown(ptvkd->wVKey == VK_UP);
|
|
lres = 0;
|
|
_fIgnoreNextSelChange = TRUE;
|
|
}
|
|
break;
|
|
|
|
case VK_F2:
|
|
//only do this in org favs, because the band accel handler usually processes this
|
|
//SHBrowseForFolder doesn't have band to process it so do it in normal mode as well
|
|
if ((_mode & MODE_CONTROL) || _mode == MODE_NORMAL)
|
|
InvokeContextMenuCommand(L"rename");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!_fSingleExpand && !(_dwFlags & NSS_MULTISELECT))
|
|
_UpdateActiveBorder(TreeView_GetSelection(_hwndTree));
|
|
}
|
|
break;
|
|
|
|
case TVN_SELCHANGINGA:
|
|
case TVN_SELCHANGING:
|
|
{
|
|
//hack because treeview keydown ALWAYS does it's default processing
|
|
if (_fIgnoreNextSelChange)
|
|
{
|
|
_fIgnoreNextSelChange = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
NM_TREEVIEW * pnmtv = (NM_TREEVIEW *) pnm;
|
|
|
|
//if it's coming from somewhere weird (like a WM_SETFOCUS), don't let it select
|
|
return (pnmtv->action != TVC_BYKEYBOARD) && (pnmtv->action != TVC_BYMOUSE) && (pnmtv->action != TVC_UNKNOWN);
|
|
}
|
|
break;
|
|
|
|
case TVN_SELCHANGEDA:
|
|
case TVN_SELCHANGED:
|
|
if (_fSelectFromMouseClick)
|
|
{
|
|
_OnSetSelection();
|
|
}
|
|
else
|
|
{
|
|
::KillTimer(_hwndTree, IDT_SELECTION);
|
|
::SetTimer(_hwndTree, IDT_SELECTION, GetDoubleClickTime(), NULL);
|
|
}
|
|
break;
|
|
|
|
case TVN_GETDISPINFO:
|
|
_OnGetDisplayInfo((TV_DISPINFO *)pnm);
|
|
break;
|
|
|
|
case TVN_ITEMEXPANDING:
|
|
TraceMsg(TF_NSC, "NSCBand: Expanding");
|
|
if (!_fIgnoreNextItemExpanding)
|
|
{
|
|
lres = _OnItemExpandingMsg((LPNM_TREEVIEW)pnm);
|
|
}
|
|
else if (!_fInExpand) // pretend we processed it if we are expanding to avoid recursion
|
|
{
|
|
lres = TRUE;
|
|
}
|
|
break;
|
|
|
|
case TVN_DELETEITEM:
|
|
_OnDeleteItem((LPNM_TREEVIEW)pnm);
|
|
break;
|
|
|
|
case TVN_BEGINDRAG:
|
|
case TVN_BEGINRDRAG:
|
|
_OnBeginDrag((NM_TREEVIEW *)pnm);
|
|
break;
|
|
|
|
case TVN_BEGINLABELEDIT:
|
|
//this is to prevent slow double-click rename in favorites and history
|
|
if (_mode != MODE_NORMAL && !_fOkToRename)
|
|
return 1;
|
|
|
|
lres = _OnBeginLabelEdit((TV_DISPINFO *)pnm);
|
|
|
|
if (_punkSite)
|
|
IUnknown_UIActivateIO(_punkSite, TRUE, NULL);
|
|
break;
|
|
|
|
case TVN_ENDLABELEDIT:
|
|
lres = _OnEndLabelEdit((TV_DISPINFO *)pnm);
|
|
break;
|
|
|
|
case TVN_SINGLEEXPAND:
|
|
case NM_DBLCLK:
|
|
break;
|
|
|
|
case NM_CLICK:
|
|
{
|
|
//if someone clicks on the selected item, force a selection change (to force a navigate)
|
|
DWORD dwPos = GetMessagePos();
|
|
TV_HITTESTINFO tvht;
|
|
HTREEITEM hti;
|
|
tvht.pt.x = GET_X_LPARAM(dwPos);
|
|
tvht.pt.y = GET_Y_LPARAM(dwPos);
|
|
::ScreenToClient(_hwndTree, &tvht.pt);
|
|
hti = TreeView_HitTest(_hwndTree, &tvht);
|
|
|
|
// But not if they click on the button, since that means that they
|
|
// are merely expanding/contracting and not selecting
|
|
if (hti && !(tvht.flags & TVHT_ONITEMBUTTON))
|
|
{
|
|
_fSelectFromMouseClick = TRUE;
|
|
TreeView_SelectItem(_hwndTree, hti);
|
|
_OnSelChange(TRUE);
|
|
_fSelectFromMouseClick = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case NM_RCLICK:
|
|
{
|
|
DWORD dwPos = GetMessagePos();
|
|
_fOkToRename = TRUE;
|
|
lres = _OnContextMenu(GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos));
|
|
_fOkToRename = FALSE;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
} // case ID_CONTROL
|
|
|
|
case ID_HEADER:
|
|
{
|
|
switch (pnm->code)
|
|
{
|
|
case HDN_TRACK:
|
|
break;
|
|
|
|
case HDN_ENDTRACK:
|
|
::InvalidateRect(_hwndTree, NULL, TRUE);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return lres;
|
|
}
|
|
|
|
HRESULT CNscTree::OnChange(LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
|
|
{
|
|
// review chrisny: better error return here.
|
|
_fHandlingShellNotification = TRUE;
|
|
_OnChangeNotify(lEvent, pidl1, pidl2);
|
|
_fHandlingShellNotification = FALSE;
|
|
return S_OK;
|
|
}
|
|
|
|
// in comctl32 v5 there is no way to programmatically select an item (in single expand mode)
|
|
// without expanding it, so we fake it here by setting _fIgnoreNextItemExpanding to true and then
|
|
// rejecting expansion when it is set
|
|
void CNscTree::_SelectNoExpand(HWND hwnd, HTREEITEM hti)
|
|
{
|
|
_fInExpand = TRUE; // Treeview will force expand the parents, make sure we know it's not the user clicking on items
|
|
TreeView_Select(hwnd, hti, TVGN_CARET | TVSI_NOSINGLEEXPAND);
|
|
_fInExpand = FALSE;
|
|
_fIgnoreNextItemExpanding = FALSE;
|
|
}
|
|
|
|
void CNscTree::_SelectPidl(LPCITEMIDLIST pidl, BOOL fCreate, BOOL fReinsert)
|
|
{
|
|
HTREEITEM hti;
|
|
// _ExpandToItem doesn't play well with empty pidl (i.e. desktop)
|
|
if (_mode == MODE_NORMAL && ILIsEqual(pidl, _pidlRoot))
|
|
hti = _FindFromRoot(NULL, pidl);
|
|
else
|
|
hti = _ExpandToItem(pidl, fCreate, fReinsert);
|
|
|
|
if (hti != NULL)
|
|
{
|
|
_SelectNoExpand(_hwndTree, hti);
|
|
#ifdef DEBUG
|
|
TraceHTREE(hti, TEXT("Found"));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
HTREEITEM CNscTree::_ExpandToItem(LPCITEMIDLIST pidl, BOOL fCreate /*= TRUE*/, BOOL fReinsert /*= FALSE*/)
|
|
{
|
|
HTREEITEM hti = NULL;
|
|
LPITEMIDLIST pidlItem = NULL;
|
|
LPCITEMIDLIST pidlTemp = NULL;
|
|
LPITEMIDLIST pidlParent;
|
|
TV_ITEM tvi;
|
|
IShellFolder *psf = NULL;
|
|
IShellFolder *psfNext = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
#ifdef DEBUG
|
|
TracePIDLAbs(pidl, TEXT("Attempting to select"));
|
|
#endif
|
|
|
|
// We need to do this so items that are rooted at the Desktop, are found
|
|
// correctly.
|
|
HTREEITEM htiParent = (_mode == MODE_NORMAL) ? TreeView_GetRoot(_hwndTree) : TVI_ROOT;
|
|
ASSERT((_hwndTree != NULL) && (pidl != NULL));
|
|
|
|
if (_hwndTree == NULL)
|
|
goto LGone;
|
|
|
|
// We should unify the "FindFromRoot" code path and this one.
|
|
pidlParent = _pidlRoot;
|
|
if (ILIsEmpty(pidlParent))
|
|
{
|
|
pidlTemp = pidl;
|
|
SHGetDesktopFolder(&psf);
|
|
}
|
|
else
|
|
{
|
|
if ((pidlTemp = ILFindChild(pidlParent, pidl)) == NULL)
|
|
{
|
|
goto LGone; // not root match, no hti
|
|
}
|
|
|
|
// root match, carry on . . .
|
|
hr = IEBindToObject(pidlParent, &psf);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
goto LGone;
|
|
}
|
|
|
|
while (!ILIsEmpty(pidlTemp))
|
|
{
|
|
if ((pidlItem = ILCloneFirst(pidlTemp)) == NULL)
|
|
goto LGone;
|
|
pidlTemp = _ILNext(pidlTemp);
|
|
|
|
// Since we are selecting a pidl, we need to make sure it's parent is visible.
|
|
// We do it this before the insert, so that we don't have to check for duplicates.
|
|
// when enumerating NTDev it goes from about 10min to about 8 seconds.
|
|
if (htiParent != TVI_ROOT)
|
|
{
|
|
// Check to see if it's expanded.
|
|
tvi.mask = TVIF_STATE;
|
|
tvi.stateMask = (TVIS_EXPANDEDONCE | TVIS_EXPANDED | TVIS_EXPANDPARTIAL);
|
|
tvi.hItem = htiParent;
|
|
if (!TreeView_GetItem(_hwndTree, &tvi))
|
|
{
|
|
goto LGone;
|
|
}
|
|
|
|
// If not, Expand it.
|
|
if (!(tvi.state & TVIS_EXPANDED))
|
|
{
|
|
_pidlExpandingTo = pidlItem;
|
|
_ExpandNode(htiParent, TVE_EXPAND, 1);
|
|
_pidlExpandingTo = NULL;
|
|
}
|
|
}
|
|
|
|
// Now that we have it enumerated, check to see if the child if there.
|
|
hti = _FindChild(psf, htiParent, pidlItem);
|
|
// fReinsert will allow us to force the item to be reinserted
|
|
if (hti && fReinsert)
|
|
{
|
|
ASSERT(fCreate);
|
|
TreeView_DeleteItem(_hwndTree, hti);
|
|
hti = NULL;
|
|
}
|
|
|
|
// Do we have a child in the newly expanded tree?
|
|
if (NULL == hti)
|
|
{
|
|
// No. We must have to create it.
|
|
if (!fCreate)
|
|
{
|
|
// But, we're not allowed to... Shoot.
|
|
goto LGone;
|
|
}
|
|
|
|
if (S_OK != _InsertChild(htiParent, psf, pidlItem, FALSE, FALSE, DEFAULTORDERPOSITION, &hti))
|
|
{
|
|
goto LGone;
|
|
}
|
|
}
|
|
|
|
if (htiParent != TVI_ROOT)
|
|
{
|
|
tvi.mask = TVIF_STATE;
|
|
tvi.stateMask = (TVIS_EXPANDEDONCE | TVIS_EXPANDED | TVIS_EXPANDPARTIAL);
|
|
tvi.hItem = htiParent;
|
|
if (TreeView_GetItem(_hwndTree, &tvi))
|
|
{
|
|
if (!(tvi.state & TVIS_EXPANDED))
|
|
{
|
|
TreeView_SetChildren(_hwndTree, htiParent, NSC_CHILDREN_ADD); // Make sure the expand will do something
|
|
_fIgnoreNextItemExpanding = TRUE;
|
|
_ExpandNode(htiParent, TVE_EXPAND | TVE_EXPANDPARTIAL, 1);
|
|
_fIgnoreNextItemExpanding = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// we don't need to bind if its the last one
|
|
// -- a half-implemented ISF might not like this bind...
|
|
if (!ILIsEmpty(pidlTemp))
|
|
hr = psf->BindToObject(pidlItem, NULL, IID_PPV_ARG(IShellFolder, &psfNext));
|
|
|
|
ILFree(pidlItem);
|
|
pidlItem = NULL;
|
|
if (FAILED(hr))
|
|
goto LGone;
|
|
|
|
htiParent = hti;
|
|
psf->Release();
|
|
psf = psfNext;
|
|
psfNext = NULL;
|
|
}
|
|
LGone:
|
|
|
|
if (psf != NULL)
|
|
psf->Release();
|
|
if (psfNext != NULL)
|
|
psfNext->Release();
|
|
if (pidlItem != NULL)
|
|
ILFree(pidlItem);
|
|
|
|
return hti;
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::GetSelectedItem(LPITEMIDLIST * ppidl, int nItem)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
// nItem will be used in the future when we support multiple selections.
|
|
// GetSelectedItem() returns S_FALSE and (NULL == *ppidl) if not that many
|
|
// items are selected. Not yet implemented.
|
|
if (nItem > 0)
|
|
{
|
|
*ppidl = NULL;
|
|
return S_FALSE;
|
|
}
|
|
|
|
if (ppidl)
|
|
{
|
|
*ppidl = NULL;
|
|
// Is the ListView still there?
|
|
if (_fIsSelectionCached)
|
|
{
|
|
// No, so get the selection that was saved before
|
|
// the listview was destroyed.
|
|
if (_pidlSelected)
|
|
{
|
|
*ppidl = ILClone(_pidlSelected);
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
hr = S_FALSE;
|
|
}
|
|
else
|
|
{
|
|
HTREEITEM htiSelected = TreeView_GetSelection(_hwndTree);
|
|
if (htiSelected)
|
|
{
|
|
*ppidl = _GetFullIDList(htiSelected);
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
hr = S_FALSE;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::SetSelectedItem(LPCITEMIDLIST pidl, BOOL fCreate, BOOL fReinsert, int nItem)
|
|
{
|
|
// nItem will be used in the future when we support multiple selections.
|
|
// Not yet implemented.
|
|
if (nItem > 0)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
// Override fCreate if the object no longer exists
|
|
DWORD dwAttributes = SFGAO_VALIDATE;
|
|
fCreate = fCreate && SUCCEEDED(IEGetAttributesOf(pidl, &dwAttributes));
|
|
|
|
// We probably haven't seen the ChangeNotify yet, so we tell
|
|
// _SelectPidl to create any folders that are there
|
|
// Then select the pidl, expanding as necessary
|
|
_fInSelectPidl = TRUE;
|
|
_SelectPidl(pidl, fCreate, fReinsert);
|
|
_fInSelectPidl = FALSE;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//*** CNscTree::IWinEventHandler
|
|
HRESULT CNscTree::OnWinEvent(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plres)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
ULONG_PTR cookie = 0;
|
|
// FUSION: When nsc calls 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.
|
|
// Future enhancements to this codepath may include using the fusion manifest
|
|
// tab <noinherit> which basically surplants the activat(null) in the following
|
|
// codepath. This disables the automatic activation from user32 for the duration
|
|
// of this wndproc, essentially doing this null push.
|
|
// we need to do this here as well as in _SubClassTreeWndProc as someone could have
|
|
// set v6 context before getting in here (band site,...)
|
|
NT5_ActivateActCtx(NULL, &cookie);
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_NOTIFY:
|
|
*plres = _OnNotify((LPNMHDR)lParam);
|
|
hr = S_OK;
|
|
break;
|
|
|
|
case WM_PALETTECHANGED:
|
|
_OnPaletteChanged(wParam, lParam);
|
|
// are we really supposed to return E_FAIL here?
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (cookie != 0)
|
|
NT5_DeactivateActCtx(cookie);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
void CNscTree::_OnChangeNotify(LONG lEvent, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra)
|
|
{
|
|
switch (lEvent)
|
|
{
|
|
case SHCNE_RENAMEFOLDER:
|
|
case SHCNE_RENAMEITEM:
|
|
if (pidl && pidlExtra)
|
|
_OnSHNotifyRename(pidl, pidlExtra);
|
|
else
|
|
ASSERT(FALSE);
|
|
|
|
break;
|
|
|
|
case SHCNE_DELETE:
|
|
case SHCNE_RMDIR:
|
|
case SHCNE_DRIVEREMOVED:
|
|
if (pidl)
|
|
_OnSHNotifyDelete(pidl, NULL, NULL);
|
|
else
|
|
ASSERT(FALSE);
|
|
break;
|
|
|
|
|
|
case SHCNE_UPDATEITEM:
|
|
// when nsc browses other namespaces, sometimes an updateitem could be fired
|
|
// on a pidl thats actually expanded in the tree, so check for it.
|
|
if (pidl)
|
|
{
|
|
IShellFolder* psf = NULL;
|
|
LPCITEMIDLIST pidlChild;
|
|
if (SUCCEEDED(_ParentFromItem(pidl, &psf, &pidlChild)))
|
|
{
|
|
LPITEMIDLIST pidlReal;
|
|
if (SUCCEEDED(_IdlRealFromIdlSimple(psf, pidlChild, &pidlReal)) && pidlReal)
|
|
{
|
|
// zip files receive updateitem when they really mean updatedir
|
|
if (SHGetAttributes(psf, pidlReal, SFGAO_FOLDER | SFGAO_STREAM) == (SFGAO_FOLDER | SFGAO_STREAM))
|
|
{
|
|
_OnSHNotifyUpdateDir(pidl);
|
|
}
|
|
_OnSHNotifyUpdateItem(pidl, pidlReal);
|
|
ILFree(pidlReal);
|
|
}
|
|
psf->Release();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SHCNE_NETSHARE:
|
|
case SHCNE_NETUNSHARE:
|
|
if (pidl)
|
|
_OnSHNotifyUpdateItem(pidl, NULL);
|
|
break;
|
|
|
|
case SHCNE_CREATE:
|
|
case SHCNE_MKDIR:
|
|
case SHCNE_DRIVEADD:
|
|
if (pidl)
|
|
{
|
|
_OnSHNotifyCreate(pidl, DEFAULTORDERPOSITION, NULL);
|
|
if (SHCNE_MKDIR == lEvent &&
|
|
_pidlNewFolderParent &&
|
|
ILIsParent(_pidlNewFolderParent, pidl, TRUE)) // TRUE = immediate parent only
|
|
{
|
|
_EnterNewFolderEditMode(pidl);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SHCNE_UPDATEDIR:
|
|
if (pidl)
|
|
{
|
|
_OnSHNotifyUpdateDir(pidl);
|
|
}
|
|
break;
|
|
|
|
case SHCNE_MEDIAREMOVED:
|
|
case SHCNE_MEDIAINSERTED:
|
|
if (pidl)
|
|
{
|
|
HTREEITEM hti = _FindFromRoot(NULL, pidl);
|
|
if (hti)
|
|
{
|
|
if (lEvent == SHCNE_MEDIAREMOVED)
|
|
{
|
|
TreeView_DeleteChildren(_hwndTree, hti);
|
|
TreeView_Expand(_hwndTree, hti, TVE_COLLAPSE | TVE_COLLAPSERESET); // reset the item
|
|
TreeView_SetChildren(_hwndTree, hti, NSC_CHILDREN_REMOVE);
|
|
}
|
|
else
|
|
{
|
|
TreeView_SetChildren(_hwndTree, hti, NSC_CHILDREN_CALLBACK);
|
|
}
|
|
|
|
_TreeInvalidateItemInfo(hti, TVIF_TEXT);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SHCNE_DRIVEADDGUI:
|
|
case SHCNE_SERVERDISCONNECT:
|
|
case SHCNE_ASSOCCHANGED:
|
|
break;
|
|
|
|
case SHCNE_UPDATEIMAGE:
|
|
if (pidl)
|
|
{
|
|
int iIndex;
|
|
if (pidlExtra)
|
|
{ // new style update image notification.....
|
|
iIndex = SHHandleUpdateImage(pidlExtra);
|
|
if (iIndex == -1)
|
|
break;
|
|
}
|
|
else
|
|
iIndex = *(int UNALIGNED *)((BYTE*)pidl + 2);
|
|
|
|
_InvalidateImageIndex(NULL, iIndex);
|
|
}
|
|
break;
|
|
case SHCNE_EXTENDED_EVENT:
|
|
{
|
|
SHChangeDWORDAsIDList UNALIGNED *pdwidl = (SHChangeDWORDAsIDList UNALIGNED *)pidl;
|
|
|
|
INT_PTR iEvent = pdwidl->dwItem1;
|
|
|
|
switch (iEvent)
|
|
{
|
|
case SHCNEE_ORDERCHANGED:
|
|
if (pidl)
|
|
{
|
|
if (_fDropping || // If WE are dropping.
|
|
_fInLabelEdit || // We're editing a name (Kicks us out)
|
|
SHChangeMenuWasSentByMe(this, pidl) || // Ignore if we sent it.
|
|
(_mode == MODE_HISTORY)) // Always ignore history changes
|
|
{
|
|
TraceMsg(TF_NSC, "NSCBand: Ignoring Change Notify: We sent");
|
|
//ignore the notification
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_BAND, "NSCBand: OnChange SHCNEE_ORDERCHANGED accepted");
|
|
|
|
_dwOrderSig++;
|
|
|
|
HTREEITEM htiRoot = _FindFromRoot(TVI_ROOT, pidlExtra);
|
|
if (htiRoot != NULL)
|
|
_UpdateDir(htiRoot, FALSE);
|
|
}
|
|
}
|
|
break;
|
|
case SHCNEE_WININETCHANGED:
|
|
{
|
|
if (pdwidl->dwItem2 & (CACHE_NOTIFY_SET_ONLINE | CACHE_NOTIFY_SET_OFFLINE))
|
|
{
|
|
BOOL fOnline = !SHIsGlobalOffline();
|
|
if ((fOnline && !_fOnline) || (!fOnline && _fOnline))
|
|
{
|
|
// State changed
|
|
_fOnline = fOnline;
|
|
_OnSHNotifyOnlineChange(TVI_ROOT, _fOnline);
|
|
}
|
|
}
|
|
|
|
if (pdwidl->dwItem2 & (CACHE_NOTIFY_ADD_URL |
|
|
CACHE_NOTIFY_DELETE_URL |
|
|
CACHE_NOTIFY_DELETE_ALL |
|
|
CACHE_NOTIFY_URL_SET_STICKY |
|
|
CACHE_NOTIFY_URL_UNSET_STICKY))
|
|
{
|
|
// Something in the cache changed
|
|
_OnSHNotifyCacheChange(TVI_ROOT, pdwidl->dwItem2);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// note, this duplicates SHGetRealIDL() so we work in non integrated shell mode
|
|
// WARNING: if it is not a file system pidl SFGAO_FILESYSTEM, we don't need to do this...
|
|
// but this is only called in the case of SHCNE_CREATE for shell notify
|
|
// and all shell notify pidls are SFGAO_FILESYSTEM
|
|
HRESULT CNscTree::_IdlRealFromIdlSimple(IShellFolder *psf, LPCITEMIDLIST pidlSimple, LPITEMIDLIST *ppidlReal)
|
|
{
|
|
WCHAR wszPath[MAX_PATH];
|
|
ULONG cbEaten;
|
|
HRESULT hr = S_OK;
|
|
if (ILIsEmpty(pidlSimple) ||
|
|
FAILED(DisplayNameOf(psf, pidlSimple, SHGDN_FORPARSING | SHGDN_INFOLDER, wszPath, ARRAYSIZE(wszPath))) ||
|
|
FAILED(psf->ParseDisplayName(NULL, NULL, wszPath, &cbEaten, ppidlReal, NULL)))
|
|
{
|
|
hr = SHILClone(pidlSimple, ppidlReal); // we don't own the lifetime of pidlSimple
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::Refresh(void)
|
|
{
|
|
_bSynchId++;
|
|
if (_bSynchId >= 16)
|
|
_bSynchId = 0;
|
|
|
|
TraceMsg(TF_NSC, "Expensive Refresh of tree");
|
|
_htiActiveBorder = NULL;
|
|
HRESULT hr = S_OK;
|
|
if (_pnscProxy)
|
|
{
|
|
DWORD dwStyle, dwExStyle;
|
|
if (SUCCEEDED(_pnscProxy->RefreshFlags(&dwStyle, &dwExStyle, &_grfFlags)))
|
|
{
|
|
dwStyle = _SetStyle(dwStyle); // initializes new _style and returns old one
|
|
if ((dwStyle ^ _style) & ~WS_VISIBLE) // don't care if only visible changed
|
|
{
|
|
DWORD dwMask = (_style | dwStyle) & ~WS_VISIBLE; // don't want to change visible style
|
|
SetWindowBits(_hwndTree, GWL_STYLE, dwMask, _style);
|
|
}
|
|
|
|
dwExStyle = _SetExStyle(dwExStyle);
|
|
if (dwExStyle != _dwExStyle)
|
|
TreeView_SetExtendedStyle(_hwndTree, _dwExStyle, dwExStyle | _dwExStyle);
|
|
}
|
|
}
|
|
|
|
if (MODE_NORMAL == _mode)
|
|
{
|
|
BOOL fOrdered;
|
|
_fUpdate = TRUE;
|
|
_StartBackgroundEnum(TreeView_GetChild(_hwndTree, TVI_ROOT), _pidlRoot, &fOrdered, TRUE);
|
|
_fUpdate = FALSE;
|
|
}
|
|
else
|
|
{
|
|
LPITEMIDLIST pidlRoot;
|
|
hr = SHILClone(_pidlRoot, &pidlRoot); // Need to do this because it's freed
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
HTREEITEM htiSelected = TreeView_GetSelection(_hwndTree);
|
|
TV_ITEM tvi;
|
|
tvi.mask = TVIF_HANDLE | TVIF_STATE;
|
|
tvi.stateMask = TVIS_EXPANDED;
|
|
tvi.hItem = (HTREEITEM)htiSelected;
|
|
BOOL fExpanded = (TreeView_GetItem(_hwndTree, &tvi) && (tvi.state & TVIS_EXPANDED));
|
|
|
|
LPITEMIDLIST pidlSelect;
|
|
GetSelectedItem(&pidlSelect, 0);
|
|
|
|
_ChangePidlRoot(pidlRoot);
|
|
if (pidlSelect)
|
|
{
|
|
_Expand(pidlSelect, fExpanded ? 1 : 0);
|
|
ILFree(pidlSelect);
|
|
}
|
|
|
|
ILFree(pidlRoot);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
void CNscTree::_CacheDetails()
|
|
{
|
|
if (_ulDisplayCol == (ULONG)-1)
|
|
{
|
|
_ulSortCol = _ulDisplayCol = 0;
|
|
|
|
if (_psf2Cache)
|
|
{
|
|
_psf2Cache->GetDefaultColumn(0, &_ulSortCol, &_ulDisplayCol);
|
|
}
|
|
}
|
|
}
|
|
|
|
HRESULT CNscTree::_GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD uFlags,
|
|
LPSHELLDETAILS pdetails)
|
|
{
|
|
ASSERT(_psfCache);
|
|
_CacheDetails();
|
|
if (_ulDisplayCol)
|
|
return _psf2Cache->GetDetailsOf(pidl, _ulDisplayCol, pdetails);
|
|
return _psfCache->GetDisplayNameOf(pidl, uFlags, &pdetails->str);
|
|
}
|
|
|
|
// if fSort, then compare for sort, else compare for existence.
|
|
HRESULT CNscTree::_CompareIDs(IShellFolder *psf, LPITEMIDLIST pidl1, LPITEMIDLIST pidl2)
|
|
{
|
|
_CacheDetails();
|
|
|
|
return psf->CompareIDs(_ulSortCol, pidl1, pidl2);
|
|
}
|
|
|
|
HRESULT CNscTree::_ParentFromItem(LPCITEMIDLIST pidl, IShellFolder** ppsfParent, LPCITEMIDLIST *ppidlChild)
|
|
{
|
|
return IEBindToParentFolder(pidl, ppsfParent, ppidlChild);
|
|
}
|
|
|
|
COLORREF CNscTree::_GetRegColor(COLORREF clrDefault, LPCTSTR pszName)
|
|
{
|
|
// Fetch the specified alternate color
|
|
|
|
COLORREF clrValue;
|
|
DWORD cbData = sizeof(clrValue);
|
|
if (FAILED(SKGetValue(SHELLKEY_HKCU_EXPLORER, NULL, pszName, NULL, &clrValue, &cbData)))
|
|
{
|
|
return clrDefault;
|
|
}
|
|
return clrValue;
|
|
}
|
|
|
|
LRESULT CNscTree::_OnCDNotify(LPNMCUSTOMDRAW pnm)
|
|
{
|
|
LRESULT lres = CDRF_DODEFAULT;
|
|
|
|
ASSERT(pnm->hdr.idFrom == ID_CONTROL);
|
|
|
|
if (_dwFlags & NSS_NORMALTREEVIEW)
|
|
{
|
|
LPNMTVCUSTOMDRAW pnmTVCustomDraw = (LPNMTVCUSTOMDRAW) pnm;
|
|
if (pnmTVCustomDraw->nmcd.dwDrawStage == CDDS_PREPAINT)
|
|
{
|
|
if (_fShowCompColor)
|
|
{
|
|
return CDRF_NOTIFYITEMDRAW;
|
|
}
|
|
else
|
|
{
|
|
return lres;
|
|
}
|
|
}
|
|
|
|
if (pnmTVCustomDraw->nmcd.dwDrawStage == CDDS_ITEMPREPAINT)
|
|
{
|
|
PORDERITEM pOrderItem = GetPoi(pnmTVCustomDraw->nmcd.lItemlParam);
|
|
if (pOrderItem && pOrderItem->pidl)
|
|
{
|
|
LPCITEMIDLIST pidl = _CacheParentShellFolder((HTREEITEM)pnmTVCustomDraw->nmcd.dwItemSpec, pOrderItem->pidl);
|
|
if (pidl)
|
|
{
|
|
DWORD dwAttribs = SHGetAttributes(_psfCache, pidl, SFGAO_COMPRESSED | SFGAO_ENCRYPTED);
|
|
// either compressed, or encrypted, can never be both
|
|
if (dwAttribs & SFGAO_COMPRESSED)
|
|
{
|
|
// If it is the item is hi-lited (selected, and has focus), blue text is not visible with the hi-lite...
|
|
if ((pnmTVCustomDraw->nmcd.uItemState & CDIS_SELECTED) && (pnmTVCustomDraw->nmcd.uItemState & CDIS_FOCUS))
|
|
pnmTVCustomDraw->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
|
|
else
|
|
pnmTVCustomDraw->clrText = _GetRegColor(RGB(0, 0, 255), TEXT("AltColor")); // default Blue
|
|
}
|
|
else if (dwAttribs & SFGAO_ENCRYPTED)
|
|
{
|
|
if ((pnmTVCustomDraw->nmcd.uItemState & CDIS_SELECTED) && (pnmTVCustomDraw->nmcd.uItemState & CDIS_FOCUS))
|
|
pnmTVCustomDraw->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
|
|
else
|
|
pnmTVCustomDraw->clrText = _GetRegColor(RGB(19, 146, 13), TEXT("AltEncryptionColor")); // default Luna Mid Green
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return lres;
|
|
}
|
|
|
|
switch (pnm->dwDrawStage)
|
|
{
|
|
case CDDS_PREPAINT:
|
|
if (NSS_BROWSERSELECT & _dwFlags)
|
|
lres = CDRF_NOTIFYITEMDRAW;
|
|
break;
|
|
|
|
case CDDS_ITEMPREPAINT:
|
|
{
|
|
//APPCOMPAT davemi: why is comctl giving us empty rects?
|
|
if (IsRectEmpty(&(pnm->rc)))
|
|
break;
|
|
PORDERITEM poi = GetPoi(pnm->lItemlParam);
|
|
DWORD dwFlags = 0;
|
|
COLORREF clrBk, clrText;
|
|
LPNMTVCUSTOMDRAW pnmtv = (LPNMTVCUSTOMDRAW)pnm;
|
|
TV_ITEM tvi;
|
|
TCHAR sz[MAX_URL_STRING];
|
|
tvi.mask = TVIF_TEXT | TVIF_HANDLE | TVIF_STATE;
|
|
tvi.stateMask = TVIS_EXPANDED | TVIS_STATEIMAGEMASK | TVIS_DROPHILITED;
|
|
tvi.pszText = sz;
|
|
tvi.cchTextMax = ARRAYSIZE(sz);
|
|
tvi.hItem = (HTREEITEM)pnm->dwItemSpec;
|
|
if (!TreeView_GetItem(_hwndTree, &tvi))
|
|
break;
|
|
//
|
|
// See if we have fetched greyed/pinned information for this item yet
|
|
//
|
|
ITEMINFO * pii = GetPii(pnm->lItemlParam);
|
|
pii->fFetched = TRUE;
|
|
|
|
if (pii->fGreyed && !(_mode & MODE_CONTROL))
|
|
dwFlags |= DIGREYED;
|
|
if (pii->fPinned)
|
|
dwFlags |= DIPINNED;
|
|
|
|
if (!pii->fNavigable)
|
|
dwFlags |= DIFOLDER;
|
|
|
|
dwFlags |= DIICON;
|
|
|
|
if (_style & TVS_RTLREADING)
|
|
dwFlags |= DIRTLREADING;
|
|
|
|
clrBk = TreeView_GetBkColor(_hwndTree);
|
|
clrText = GetSysColor(COLOR_WINDOWTEXT);
|
|
|
|
//if we're renaming an item, don't draw any text for it (otherwise it shows behind the item)
|
|
if (tvi.hItem == _htiRenaming)
|
|
sz[0] = 0;
|
|
|
|
if (tvi.state & TVIS_EXPANDED)
|
|
dwFlags |= DIFOLDEROPEN;
|
|
|
|
if (!(_dwFlags & NSS_MULTISELECT) && ((pnm->uItemState & CDIS_SELECTED) || (tvi.state & TVIS_DROPHILITED)))
|
|
{
|
|
if (_fHasFocus || tvi.state & TVIS_DROPHILITED)
|
|
{
|
|
clrBk = GetSysColor(COLOR_HIGHLIGHT);
|
|
clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
|
|
}
|
|
else
|
|
{
|
|
clrBk = GetSysColor(COLOR_BTNFACE);
|
|
}
|
|
// dwFlags |= DIFOCUSRECT;
|
|
}
|
|
|
|
if (pnm->uItemState & CDIS_HOT)
|
|
{
|
|
if (!(_mode & MODE_CONTROL))
|
|
dwFlags |= DIHOT;
|
|
clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
|
|
|
|
if (clrText == clrBk)
|
|
clrText = GetSysColor(COLOR_HIGHLIGHT);
|
|
}
|
|
|
|
if ((_dwFlags & NSS_MULTISELECT) && (pnm->uItemState & CDIS_SELECTED))
|
|
dwFlags |= DIACTIVEBORDER | DISUBFIRST | DISUBLAST;
|
|
|
|
if (tvi.state & NSC_TVIS_MARKED)
|
|
{
|
|
if (_dwFlags & NSS_MULTISELECT)
|
|
{
|
|
if (_fHasFocus)
|
|
{
|
|
clrBk = GetSysColor(COLOR_HIGHLIGHT);
|
|
clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
|
|
}
|
|
else
|
|
{
|
|
clrBk = GetSysColor(COLOR_BTNFACE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwFlags |= DIACTIVEBORDER;
|
|
//top level item
|
|
if (_IsTopParentItem((HTREEITEM)pnm->dwItemSpec))
|
|
{
|
|
dwFlags |= DISUBFIRST;
|
|
if (!(tvi.state & TVIS_EXPANDED))
|
|
dwFlags |= DISUBLAST;
|
|
}
|
|
else // lower level items
|
|
{
|
|
dwFlags |= DISUBITEM;
|
|
if (((HTREEITEM)pnm->dwItemSpec) == _htiActiveBorder)
|
|
dwFlags |= DISUBFIRST;
|
|
|
|
HTREEITEM hti = TreeView_GetNextVisible(_hwndTree, (HTREEITEM)pnm->dwItemSpec);
|
|
if ((hti && !_IsMarked(hti)) || (hti == NULL))
|
|
dwFlags |= DISUBLAST;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((_dwFlags & NSS_HEADER) && _hwndHdr &&
|
|
_CacheParentShellFolder((HTREEITEM)pnm->dwItemSpec, poi->pidl) &&
|
|
_psf2Cache)
|
|
{
|
|
// with header we don't draw active order because it looks ugly,
|
|
// but with multiselect we do because that's how we differentiate selected items
|
|
if (!(_dwFlags & NSS_MULTISELECT))
|
|
dwFlags &= ~DIACTIVEBORDER;
|
|
|
|
RECT rc;
|
|
|
|
CopyRect(&rc, &(pnm->rc));
|
|
for (int i=0; i<DPA_GetPtrCount(_hdpaColumns); i++)
|
|
{
|
|
RECT rcHeader;
|
|
int iLevel = 0;
|
|
HEADERINFO *phinfo = (HEADERINFO *)DPA_GetPtr(_hdpaColumns, i);
|
|
|
|
ASSERT(phinfo);
|
|
Header_GetItemRect(_hwndHdr, i, &rcHeader);
|
|
rc.left = rcHeader.left;
|
|
rc.right = rcHeader.right;
|
|
if (i == 0) //it is name column
|
|
{
|
|
iLevel = pnmtv->iLevel;
|
|
//use sz set above in the function
|
|
}
|
|
else
|
|
{
|
|
// in multiselect draw border only around the name
|
|
dwFlags &= ~DIACTIVEBORDER;
|
|
dwFlags = 0;
|
|
if (phinfo->fmt & LVCFMT_RIGHT)
|
|
dwFlags |= DIRIGHT;
|
|
clrBk = TreeView_GetBkColor(_hwndTree);
|
|
clrText = GetSysColor(COLOR_WINDOWTEXT);
|
|
|
|
sz[0] = 0;
|
|
|
|
VARIANT var;
|
|
if (SUCCEEDED(_psf2Cache->GetDetailsEx(poi->pidl, phinfo->pscid, &var)))
|
|
{
|
|
VariantToStr(&var, sz, ARRAYSIZE(sz));
|
|
}
|
|
}
|
|
_DrawItem((HTREEITEM)pnm->dwItemSpec, sz, pnm->hdc, &rc, dwFlags, iLevel, clrBk, clrText);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_DrawItem((HTREEITEM)pnm->dwItemSpec, sz, pnm->hdc, &(pnm->rc), dwFlags, pnmtv->iLevel, clrBk, clrText);
|
|
}
|
|
lres = CDRF_SKIPDEFAULT;
|
|
break;
|
|
}
|
|
case CDDS_POSTPAINT:
|
|
break;
|
|
}
|
|
|
|
return lres;
|
|
}
|
|
|
|
// *******droptarget implementation.
|
|
void CNscTree::_DtRevoke()
|
|
{
|
|
if (_fDTRegistered)
|
|
{
|
|
RevokeDragDrop(_hwndTree);
|
|
_fDTRegistered = FALSE;
|
|
}
|
|
}
|
|
|
|
void CNscTree::_DtRegister()
|
|
{
|
|
if (!_fDTRegistered && (_dwFlags & NSS_DROPTARGET))
|
|
{
|
|
if (::IsWindow(_hwndTree))
|
|
{
|
|
HRESULT hr = THR(RegisterDragDrop(_hwndTree, SAFECAST(this, IDropTarget*)));
|
|
_fDTRegistered = BOOLIFY(SUCCEEDED(hr));
|
|
}
|
|
else
|
|
ASSERT(FALSE);
|
|
}
|
|
}
|
|
|
|
HRESULT CNscTree::GetWindowsDDT(HWND * phwndLock, HWND * phwndScroll)
|
|
{
|
|
if (!::IsWindow(_hwndTree))
|
|
{
|
|
ASSERT(FALSE);
|
|
return S_FALSE;
|
|
}
|
|
*phwndLock = /*_hwndDD*/_hwndTree;
|
|
*phwndScroll = _hwndTree;
|
|
return S_OK;
|
|
}
|
|
const int iInsertThresh = 6;
|
|
|
|
// We use this as the sentinal "This is where you started"
|
|
#define DDT_SENTINEL ((DWORD_PTR)(INT_PTR)-1)
|
|
|
|
HRESULT CNscTree::HitTestDDT(UINT nEvent, LPPOINT ppt, DWORD_PTR *pdwId, DWORD * pdwDropEffect)
|
|
{
|
|
switch (nEvent)
|
|
{
|
|
case HTDDT_ENTER:
|
|
break;
|
|
|
|
case HTDDT_LEAVE:
|
|
{
|
|
_fDragging = FALSE;
|
|
_fDropping = FALSE;
|
|
DAD_ShowDragImage(FALSE);
|
|
TreeView_SetInsertMark(_hwndTree, NULL, !_fInsertBefore);
|
|
TreeView_SelectDropTarget(_hwndTree, NULL);
|
|
DAD_ShowDragImage(TRUE);
|
|
break;
|
|
}
|
|
|
|
case HTDDT_OVER:
|
|
{
|
|
// review chrisny: make function TreeView_InsertMarkHittest!!!!!
|
|
RECT rc;
|
|
TV_HITTESTINFO tvht;
|
|
HTREEITEM htiOver; // item to insert before or after.
|
|
BOOL fWasInserting = BOOLIFY(_fInserting);
|
|
BOOL fOldInsertBefore = BOOLIFY(_fInsertBefore);
|
|
TV_ITEM tvi;
|
|
PORDERITEM poi = NULL;
|
|
IDropTarget *pdtgt = NULL;
|
|
HRESULT hr;
|
|
LPITEMIDLIST pidl;
|
|
|
|
_fDragging = TRUE;
|
|
*pdwDropEffect = DROPEFFECT_NONE; // dropping from without.
|
|
tvht.pt = *ppt;
|
|
htiOver = TreeView_HitTest(_hwndTree, &tvht);
|
|
// if no hittest assume we are dropping on the evil root.
|
|
if (htiOver != NULL)
|
|
{
|
|
TreeView_GetItemRect(_hwndTree, (HTREEITEM)htiOver, &rc, TRUE);
|
|
tvi.mask = TVIF_STATE | TVIF_PARAM | TVIF_HANDLE;
|
|
tvi.stateMask = TVIS_EXPANDED;
|
|
tvi.hItem = (HTREEITEM)htiOver;
|
|
if (TreeView_GetItem(_hwndTree, &tvi))
|
|
poi = GetPoi(tvi.lParam);
|
|
if (poi == NULL)
|
|
{
|
|
ASSERT(FALSE);
|
|
return S_FALSE;
|
|
}
|
|
}
|
|
else if (_mode != MODE_NORMAL) //need parity with win2k Explorer band
|
|
{
|
|
htiOver = TVI_ROOT;
|
|
}
|
|
|
|
// NO DROPPY ON HISTORY
|
|
if (_mode & MODE_HISTORY)
|
|
{
|
|
*pdwId = (DWORD_PTR)(htiOver);
|
|
*pdwDropEffect = DROPEFFECT_NONE; // dropping from without.
|
|
return S_OK;
|
|
}
|
|
|
|
pidl = (poi == NULL) ? _pidlRoot : poi->pidl;
|
|
pidl = _CacheParentShellFolder(htiOver, pidl);
|
|
if (pidl)
|
|
{
|
|
// Is this the desktop pidl?
|
|
if (ILIsEmpty(pidl))
|
|
{
|
|
// Desktop's GetUIObject does not support the Empty pidl, so
|
|
// create the view object.
|
|
hr = _psfCache->CreateViewObject(_hwndTree, IID_PPV_ARG(IDropTarget, &pdtgt));
|
|
}
|
|
else
|
|
hr = _psfCache->GetUIObjectOf(_hwndTree, 1, (LPCITEMIDLIST *)&pidl, IID_PPV_ARG_NULL(IDropTarget, &pdtgt));
|
|
}
|
|
|
|
_fInserting = ((htiOver != TVI_ROOT) && ((ppt->y < (rc.top + iInsertThresh)
|
|
|| (ppt->y > (rc.bottom - iInsertThresh))) || !pdtgt));
|
|
// review chrisny: do I need folderstart == folder over?
|
|
// If in normal mode, we never want to insert before, always _ON_...
|
|
if (_mode != MODE_NORMAL && _fInserting)
|
|
{
|
|
ASSERT(poi);
|
|
_iDragDest = poi->nOrder; // index of item within folder pdwId
|
|
if ((ppt->y < (rc.top + iInsertThresh)) || !pdtgt)
|
|
_fInsertBefore = TRUE;
|
|
else
|
|
{
|
|
ASSERT (ppt->y > (rc.bottom - iInsertThresh));
|
|
_fInsertBefore = FALSE;
|
|
}
|
|
if (_iDragSrc != -1)
|
|
*pdwDropEffect = DROPEFFECT_MOVE; // moving from within.
|
|
else
|
|
*pdwDropEffect = DROPEFFECT_NONE; // dropping from without.
|
|
// inserting, drop target is actually parent folder of this item
|
|
if (_fInsertBefore || ((htiOver != TVI_ROOT) && !(tvi.state & TVIS_EXPANDED)))
|
|
{
|
|
_htiDropInsert = TreeView_GetParent(_hwndTree, (HTREEITEM)htiOver);
|
|
}
|
|
else
|
|
_htiDropInsert = htiOver;
|
|
if (_htiDropInsert == NULL)
|
|
_htiDropInsert = TVI_ROOT;
|
|
*pdwId = (DWORD_PTR)(_htiDropInsert);
|
|
}
|
|
else
|
|
{
|
|
_htiDropInsert = htiOver;
|
|
*pdwId = (DWORD_PTR)(htiOver);
|
|
_iDragDest = -1; // no insertion point.
|
|
*pdwDropEffect = DROPEFFECT_NONE;
|
|
}
|
|
|
|
// if we're over the item we're dragging, don't allow drop here
|
|
if ((_htiDragging == htiOver) || (IsParentOfItem(_hwndTree, _htiDragging, htiOver)))
|
|
{
|
|
*pdwDropEffect = DROPEFFECT_NONE;
|
|
*pdwId = DDT_SENTINEL;
|
|
_fInserting = FALSE;
|
|
ATOMICRELEASE(pdtgt);
|
|
}
|
|
|
|
// update UI
|
|
if (_htiCur != (HTREEITEM)htiOver || fWasInserting != BOOLIFY(_fInserting) || fOldInsertBefore != BOOLIFY(_fInsertBefore))
|
|
{
|
|
// change in target
|
|
_dwLastTime = GetTickCount(); // keep track for auto-expanding the tree
|
|
DAD_ShowDragImage(FALSE);
|
|
if (_fInserting)
|
|
{
|
|
TraceMsg(TF_NSC, "NSCBand: drop insert now");
|
|
if (htiOver != TVI_ROOT)
|
|
{
|
|
if (_mode != MODE_NORMAL)
|
|
{
|
|
TreeView_SelectDropTarget(_hwndTree, NULL);
|
|
TreeView_SetInsertMark(_hwndTree, htiOver, !_fInsertBefore);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(TF_NSC, "NSCBand: drop select now");
|
|
if (_mode != MODE_NORMAL)
|
|
TreeView_SetInsertMark(_hwndTree, NULL, !_fInsertBefore);
|
|
|
|
if (htiOver != TVI_ROOT)
|
|
{
|
|
if (pdtgt)
|
|
{
|
|
TreeView_SelectDropTarget(_hwndTree, htiOver);
|
|
}
|
|
else if (_mode != MODE_NORMAL)
|
|
{
|
|
// We do not want to select the drop target in normal mode
|
|
// because it causes a weird flashing of some item unrelated
|
|
// to the drag and drop when the drop is not supported.
|
|
TreeView_SelectDropTarget(_hwndTree, NULL);
|
|
}
|
|
}
|
|
}
|
|
::UpdateWindow(_hwndTree);
|
|
DAD_ShowDragImage(TRUE);
|
|
}
|
|
else
|
|
{
|
|
// No target change
|
|
// auto expand the tree
|
|
if (_htiCur)
|
|
{
|
|
DWORD dwNow = GetTickCount();
|
|
if ((dwNow - _dwLastTime) >= 1000)
|
|
{
|
|
_dwLastTime = dwNow;
|
|
DAD_ShowDragImage(FALSE);
|
|
_fAutoExpanding = TRUE;
|
|
if (_htiCur != TVI_ROOT)
|
|
TreeView_Expand(_hwndTree, _htiCur, TVE_EXPAND);
|
|
_fAutoExpanding = FALSE;
|
|
::UpdateWindow(_hwndTree);
|
|
DAD_ShowDragImage(TRUE);
|
|
}
|
|
}
|
|
}
|
|
_htiCur = (HTREEITEM)htiOver;
|
|
ATOMICRELEASE(pdtgt);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNscTree::GetObjectDDT(DWORD_PTR dwId, REFIID riid, void **ppv)
|
|
{
|
|
HRESULT hr = S_FALSE;
|
|
|
|
if (dwId != DDT_SENTINEL)
|
|
{
|
|
LPCITEMIDLIST pidl = _CacheParentShellFolder((HTREEITEM)dwId, NULL);
|
|
if (pidl)
|
|
{
|
|
if (ILIsEmpty(pidl))
|
|
hr = _psfCache->CreateViewObject(_hwndTree, riid, ppv);
|
|
else
|
|
hr = _psfCache->GetUIObjectOf(_hwndTree, 1, &pidl, riid, NULL, ppv);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNscTree::OnDropDDT(IDropTarget *pdt, IDataObject *pdtobj, DWORD * pgrfKeyState, POINTL pt, DWORD *pdwEffect)
|
|
{
|
|
HRESULT hr;
|
|
|
|
_fAsyncDrop = FALSE; //ASSUME
|
|
_fDropping = TRUE;
|
|
|
|
// move within same folder, else let Drop() handle it.
|
|
if (_iDragSrc >= 0)
|
|
{
|
|
if (_htiFolderStart == _htiDropInsert && _mode != MODE_NORMAL)
|
|
{
|
|
if (_iDragSrc != _iDragDest) // no moving needed
|
|
{
|
|
int iNewPos = _fInsertBefore ? (_iDragDest - 1) : _iDragDest;
|
|
if (_MoveNode(_iDragSrc, iNewPos, _pidlDrag))
|
|
{
|
|
TraceMsg(TF_NSC, "NSCBand: Reordering");
|
|
_fDropping = TRUE;
|
|
_Dropped();
|
|
// Remove this notify message immediately (so _fDropping is set
|
|
// and we'll ignore this event in above OnChange method)
|
|
//
|
|
_FlushNotifyMessages(_hwndTree);
|
|
_fDropping = FALSE;
|
|
}
|
|
Pidl_Set(&_pidlDrag, NULL);
|
|
}
|
|
DragLeave();
|
|
_htiCur = _htiFolderStart = NULL;
|
|
_htiDropInsert = (HTREEITEM)-1;
|
|
_fDragging = _fInserting = _fDropping = FALSE;
|
|
_iDragDest = -1;
|
|
hr = S_FALSE; // handled
|
|
}
|
|
else
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the item will get created in SHNotifyCreate()
|
|
TraceMsg(TF_NSC, "NSCBand: Dropped and External Item");
|
|
|
|
BOOL fSafe = TRUE;
|
|
LPITEMIDLIST pidl;
|
|
|
|
if (SUCCEEDED(SHPidlFromDataObject(pdtobj, &pidl, NULL, 0)))
|
|
{
|
|
fSafe = IEIsLinkSafe(_hwndParent, pidl, ILS_ADDTOFAV);
|
|
ILFree(pidl);
|
|
}
|
|
|
|
if (fSafe)
|
|
{
|
|
_fAsyncDrop = TRUE;
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
hr = S_FALSE;
|
|
}
|
|
}
|
|
|
|
TreeView_SetInsertMark(_hwndTree, NULL, !_fInsertBefore);
|
|
TreeView_SelectDropTarget(_hwndTree, NULL);
|
|
|
|
ILFree(_pidlDrag);
|
|
_pidlDrag = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
IStream * CNscTree::GetOrderStream(LPCITEMIDLIST pidl, DWORD grfMode)
|
|
{
|
|
// only do this for favorites
|
|
if (!ILIsEmpty(pidl) && (_mode & MODE_FAVORITES))
|
|
return OpenPidlOrderStream((LPCITEMIDLIST)CSIDL_FAVORITES, pidl, REG_SUBKEY_FAVORITESA, grfMode);
|
|
return NULL;
|
|
}
|
|
|
|
BOOL CNscTree::_MoveNode(int iDragSrc, int iNewPos, LPITEMIDLIST pidl)
|
|
{
|
|
HTREEITEM hti, htiAfter = TVI_LAST, htiDel = NULL;
|
|
|
|
// if we are not moving and not dropping directly on a folder with no insert.
|
|
if ((iDragSrc == iNewPos) && (iNewPos != -1))
|
|
return FALSE; // no need to move
|
|
|
|
int i = 0;
|
|
for (hti = TreeView_GetChild(_hwndTree, _htiDropInsert); hti; hti = TreeView_GetNextSibling(_hwndTree, hti), i++)
|
|
{
|
|
if (i == iDragSrc)
|
|
htiDel = hti; // save node to be deleted, can't deelete it while enumerating
|
|
// cuz the treeview will go down the tubes.
|
|
if (i == iNewPos)
|
|
htiAfter = hti;
|
|
}
|
|
|
|
if (iNewPos == -1) // must be the first item
|
|
htiAfter = TVI_FIRST;
|
|
// add before delete to handle add after deleteable item case.
|
|
_AddItemToTree(_htiDropInsert, pidl, I_CHILDRENCALLBACK, _iDragDest, htiAfter, FALSE);
|
|
if (htiDel)
|
|
TreeView_DeleteItem(_hwndTree, htiDel);
|
|
|
|
_PopulateOrderList(_htiDropInsert);
|
|
|
|
_fWeChangedOrder = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
void CNscTree::_Dropped(void)
|
|
{
|
|
// Persist the new order out to the registry
|
|
LPITEMIDLIST pidl = _GetFullIDList(_htiDropInsert);
|
|
if (pidl)
|
|
{
|
|
IStream* pstm = GetOrderStream(pidl, STGM_WRITE | STGM_CREATE);
|
|
if (pstm)
|
|
{
|
|
if (_CacheShellFolder(_htiDropInsert))
|
|
{
|
|
#ifdef DEBUG
|
|
if (_hdpaOrd)
|
|
{
|
|
for (int i=0; i<DPA_GetPtrCount(_hdpaOrd); i++)
|
|
{
|
|
PORDERITEM poi = (PORDERITEM)DPA_GetPtr(_hdpaOrd, i);
|
|
if (poi)
|
|
{
|
|
ASSERTMSG(poi->nOrder >= 0, "nsc saving bogus order list nOrder (%d), get reljai", poi->nOrder);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
OrderList_SaveToStream(pstm, _hdpaOrd, _psfCache);
|
|
// remember we are now ordered.
|
|
if (_htiDropInsert == TVI_ROOT)
|
|
{
|
|
_fOrdered = TRUE;
|
|
}
|
|
else
|
|
{
|
|
PORDERITEM poi = _GetTreeOrderItem(_htiDropInsert);
|
|
if (poi)
|
|
{
|
|
poi->lParam = (DWORD)FALSE;
|
|
}
|
|
}
|
|
// Notify everyone that the order changed
|
|
SHSendChangeMenuNotify(this, SHCNEE_ORDERCHANGED, 0, pidl);
|
|
_dwOrderSig++;
|
|
}
|
|
pstm->Release();
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
|
|
DPA_Destroy(_hdpaOrd);
|
|
_hdpaOrd = NULL;
|
|
|
|
_UpdateActiveBorder(_htiDropInsert);
|
|
}
|
|
|
|
CNscTree::CSelectionContextMenu::~CSelectionContextMenu()
|
|
{
|
|
ATOMICRELEASE(_pcmSelection);
|
|
ATOMICRELEASE(_pcm2Selection);
|
|
}
|
|
|
|
HRESULT CNscTree::CSelectionContextMenu::QueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
static const QITAB qit[] = {
|
|
QITABENT(CNscTree::CSelectionContextMenu, IContextMenu2), // IID_IContextMenu2
|
|
QITABENTMULTI(CNscTree::CSelectionContextMenu, IContextMenu, IContextMenu2), // IID_IContextMenu
|
|
{ 0 },
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
|
|
|
|
ULONG CNscTree::CSelectionContextMenu::AddRef(void)
|
|
{
|
|
CComObject<CNscTree> *pnsc = IToClass(CComObject<CNscTree>, _scm, this);
|
|
_ulRefs++;
|
|
return pnsc->AddRef();
|
|
}
|
|
|
|
ULONG CNscTree::CSelectionContextMenu::Release(void)
|
|
{
|
|
CComObject<CNscTree> *pnsc = IToClass(CComObject<CNscTree>, _scm, this);
|
|
ASSERT(_ulRefs > 0);
|
|
_ulRefs--;
|
|
if (0 == _ulRefs)
|
|
{
|
|
ATOMICRELEASE(_pcmSelection);
|
|
ATOMICRELEASE(_pcm2Selection);
|
|
}
|
|
return pnsc->Release();
|
|
}
|
|
|
|
HRESULT CNscTree::CSelectionContextMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu,
|
|
UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
|
|
{
|
|
if (NULL == _pcmSelection)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
else
|
|
{
|
|
return _pcmSelection->QueryContextMenu(hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
|
|
}
|
|
}
|
|
|
|
HRESULT CNscTree::CSelectionContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
|
|
{
|
|
HTREEITEM hti;
|
|
CNscTree* pnsc = IToClass(CNscTree, _scm, this);
|
|
UINT idCmd;
|
|
|
|
if (!HIWORD(pici->lpVerb))
|
|
{
|
|
idCmd = LOWORD(pici->lpVerb);
|
|
}
|
|
else
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
HRESULT hr = pnsc->_QuerySelection(NULL, &hti);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
pnsc->_ApplyCmd(hti, _pcmSelection, idCmd);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNscTree::CSelectionContextMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
if (_pcm2Selection)
|
|
{
|
|
hr = _pcm2Selection->HandleMenuMsg(uMsg,wParam,lParam);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
LRESULT CALLBACK CNscTree::s_SubClassTreeWndProc(
|
|
HWND hwnd, UINT uMsg,
|
|
WPARAM wParam, LPARAM lParam,
|
|
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
|
|
CNscTree* pns = (CNscTree*)dwRefData;
|
|
ASSERT(pns);
|
|
if (pns == NULL)
|
|
return 0;
|
|
|
|
ULONG_PTR cookie = 0;
|
|
// FUSION: When nsc calls 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.
|
|
// Future enhancements to this codepath may include using the fusion manifest
|
|
// tab <noinherit> which basically surplants the activat(null) in the following
|
|
// codepath. This disables the automatic activation from user32 for the duration
|
|
// of this wndproc, essentially doing this null push.
|
|
NT5_ActivateActCtx(NULL, &cookie);
|
|
LRESULT lres = pns->_SubClassTreeWndProc(hwnd, uMsg, wParam, lParam);
|
|
if (cookie != 0)
|
|
NT5_DeactivateActCtx(cookie);
|
|
|
|
return lres;
|
|
}
|
|
|
|
LRESULT CNscTree::_SubClassTreeWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LRESULT lres = 0;
|
|
BOOL fCallDefWndProc = TRUE;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_COMMAND:
|
|
lres = _OnCommand(wParam, lParam);
|
|
break;
|
|
|
|
case WM_SIZE:
|
|
// if the width changes, we need to invalidate to redraw the ...'s at the end of the lines
|
|
if (GET_X_LPARAM(lParam) != _cxOldWidth) {
|
|
//FEATURE: be a bit more clever and only inval the right part where the ... can be
|
|
::InvalidateRect(_hwndTree, NULL, FALSE);
|
|
_cxOldWidth = GET_X_LPARAM(lParam);
|
|
}
|
|
break;
|
|
|
|
case WM_CONTEXTMENU:
|
|
_OnContextMenu(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
|
return TRUE;
|
|
break;
|
|
|
|
case WM_INITMENUPOPUP:
|
|
case WM_MEASUREITEM:
|
|
case WM_DRAWITEM:
|
|
if (_pcmSendTo)
|
|
{
|
|
_pcmSendTo->HandleMenuMsg(uMsg, wParam, lParam);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case WM_NSCUPDATEICONOVERLAY:
|
|
{
|
|
NSC_OVERLAYCALLBACKINFO noci = {(DWORD) (lParam & 0x0FFFFFFF),
|
|
(DWORD) ((lParam & 0xF0000000) >> 28) };
|
|
// make sure the magic numbers match
|
|
if (noci.nMagic == _bSynchId)
|
|
{
|
|
TVITEM tvi;
|
|
tvi.mask = TVIF_STATE;
|
|
tvi.stateMask = TVIS_OVERLAYMASK;
|
|
tvi.state = 0;
|
|
tvi.hItem = (HTREEITEM)wParam;
|
|
// This can fail if the item was moved before the async icon
|
|
// extraction finished for that item.
|
|
if (TreeView_GetItem(_hwndTree, &tvi))
|
|
{
|
|
tvi.state = INDEXTOOVERLAYMASK(noci.iOverlayIndex);
|
|
TreeView_SetItem(_hwndTree, &tvi);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_NSCUPDATEICONINFO:
|
|
{
|
|
NSC_ICONCALLBACKINFO nici = {(DWORD) (lParam&0x00000FFF),
|
|
(DWORD) ((lParam&0x00FFF000) >> 12),
|
|
(DWORD) ((lParam&0x0F000000) >> 24),
|
|
(DWORD) ((lParam&0xF0000000) >> 28) };
|
|
// make sure the magic numbers match
|
|
if (nici.nMagic == _bSynchId)
|
|
{
|
|
TVITEM tvi;
|
|
tvi.mask = TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
|
|
tvi.hItem = (HTREEITEM)wParam;
|
|
|
|
// This can fail if the item was moved before the async icon
|
|
// extraction finished for that item.
|
|
if (TreeView_GetItem(_hwndTree, &tvi))
|
|
{
|
|
ITEMINFO* pii = GetPii(tvi.lParam);
|
|
|
|
pii->fGreyed = BOOLIFY(nici.nFlags & NSCICON_GREYED);
|
|
pii->fPinned = BOOLIFY(nici.nFlags & NSCICON_PINNED);
|
|
pii->fDontRefetch = BOOLIFY(nici.nFlags & NSCICON_DONTREFETCH);
|
|
|
|
tvi.iImage = nici.iIcon;
|
|
tvi.iSelectedImage = nici.iOpenIcon;
|
|
|
|
TreeView_SetItem(_hwndTree, &tvi);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_NSCBACKGROUNDENUMDONE:
|
|
{
|
|
if (_fShouldShowAppStartCursor)
|
|
{
|
|
// Restore cursor now
|
|
_fShouldShowAppStartCursor = FALSE;
|
|
SetCursor(LoadCursor(NULL, IDC_ARROW));
|
|
}
|
|
NSC_BKGDENUMDONEDATA * pbedd;
|
|
do
|
|
{
|
|
EnterCriticalSection(&_csBackgroundData);
|
|
// Extract the first element of the list
|
|
pbedd = _pbeddList;
|
|
if (pbedd)
|
|
{
|
|
_pbeddList = pbedd->pNext;
|
|
}
|
|
LeaveCriticalSection(&_csBackgroundData);
|
|
if (pbedd)
|
|
{
|
|
pbedd->pNext = NULL;
|
|
_EnumBackgroundDone(pbedd);
|
|
}
|
|
} while (pbedd);
|
|
}
|
|
break;
|
|
|
|
|
|
// UGLY: Win95/NT4 shell DefView code sends this msg and does not deal
|
|
// with the failure case. other ISVs do the same so this needs to stay forever
|
|
case CWM_GETISHELLBROWSER:
|
|
return (LRESULT)SAFECAST(this, IShellBrowser*); // not ref counted!
|
|
|
|
case WM_TIMER:
|
|
if (wParam == IDT_SELECTION)
|
|
{
|
|
::KillTimer(_hwndTree, IDT_SELECTION);
|
|
_OnSetSelection();
|
|
}
|
|
break;
|
|
|
|
case WM_HELP:
|
|
{
|
|
// Let controls provide thier own help (organize favorites). The default help
|
|
// also doesn't make sence for history (really need separate help id for history)
|
|
if (!(_mode & (MODE_CONTROL | MODE_HISTORY)))
|
|
{
|
|
if (_mode & MODE_FAVORITES)
|
|
{
|
|
const static DWORD aBrowseHelpIDs[] =
|
|
{ // Context Help IDs
|
|
ID_CONTROL, IDH_ORGFAVS_LIST,
|
|
0, 0
|
|
};
|
|
::WinHelp((HWND)((LPHELPINFO) lParam)->hItemHandle, c_szHelpFile, HELP_WM_HELP, (ULONG_PTR)(LPTSTR) aBrowseHelpIDs);
|
|
}
|
|
else
|
|
{
|
|
// default help
|
|
const static DWORD aBrowseHelpIDs[] =
|
|
{ // Context Help IDs
|
|
ID_CONTROL, IDH_BROWSELIST,
|
|
0, 0
|
|
};
|
|
::WinHelp((HWND)((LPHELPINFO) lParam)->hItemHandle, NULL, HELP_WM_HELP, (ULONG_PTR)(LPTSTR) aBrowseHelpIDs);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_SYSCOLORCHANGE:
|
|
case WM_WININICHANGE:
|
|
// _HandleWinIniChange does an item height calculation that
|
|
// depends on treeview having computed the default item height
|
|
// already. So we need to let treeview handle the settings
|
|
// change before calling _HandleWinIniChange. Also, we need
|
|
// to reset the height to default so that treeview will
|
|
// calculate a new default.
|
|
TreeView_SetItemHeight(hwnd, -1);
|
|
lres = DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
_HandleWinIniChange();
|
|
break;
|
|
|
|
case WM_KEYDOWN:
|
|
// Only do this when the CTRL key is not down
|
|
if (GetKeyState(VK_CONTROL) >= 0)
|
|
{
|
|
if (wParam == VK_MULTIPLY)
|
|
{
|
|
// We set _pidlNavigatingTo to NULL here to ensure that we will be doing full expands.
|
|
// When _pidlNavigatingTo is non null, we are doing partial expands by default, which is not
|
|
// what we want here.
|
|
Pidl_Set(&_pidlNavigatingTo, NULL);
|
|
|
|
_uDepth = (UINT)-1; // to recursive expand all the way to the end
|
|
lres = DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
_uDepth = 0;
|
|
fCallDefWndProc = FALSE; // Don't call DefSubclassProc again.
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (fCallDefWndProc && lres == 0)
|
|
lres = DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
|
|
return lres;
|
|
}
|
|
|
|
HRESULT CNscTree::_OnPaletteChanged(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
// forward this to our child view by invalidating their window (they should never realize their palette
|
|
// in the foreground so they don't need the message parameters.) ...
|
|
RECT rc;
|
|
::GetClientRect(_hwndTree, &rc);
|
|
::InvalidateRect(_hwndTree, &rc, FALSE);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
void CNscTree::_InvalidateImageIndex(HTREEITEM hItem, int iImage)
|
|
{
|
|
HTREEITEM hChild;
|
|
TV_ITEM tvi;
|
|
|
|
if (hItem)
|
|
{
|
|
tvi.mask = TVIF_SELECTEDIMAGE | TVIF_IMAGE;
|
|
tvi.hItem = hItem;
|
|
|
|
TreeView_GetItem(_hwndTree, &tvi);
|
|
if (iImage == -1 || tvi.iImage == iImage || tvi.iSelectedImage == iImage)
|
|
_TreeInvalidateItemInfo(hItem, 0);
|
|
}
|
|
|
|
hChild = TreeView_GetChild(_hwndTree, hItem);
|
|
if (!hChild)
|
|
return;
|
|
|
|
for (; hChild; hChild = TreeView_GetNextSibling(_hwndTree, hChild))
|
|
_InvalidateImageIndex(hChild, iImage);
|
|
}
|
|
|
|
void CNscTree::_TreeInvalidateItemInfo(HTREEITEM hItem, UINT mask)
|
|
{
|
|
TV_ITEM tvi;
|
|
|
|
tvi.mask = mask | TVIF_CHILDREN | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
|
|
tvi.stateMask = TVIS_OVERLAYMASK;
|
|
tvi.state = 0;
|
|
tvi.hItem = hItem;
|
|
tvi.cChildren = I_CHILDRENCALLBACK;
|
|
tvi.iImage = I_IMAGECALLBACK;
|
|
tvi.iSelectedImage = I_IMAGECALLBACK;
|
|
tvi.pszText = LPSTR_TEXTCALLBACK;
|
|
TreeView_SetItem(_hwndTree, &tvi);
|
|
}
|
|
|
|
#define DXLEFT 8
|
|
#define MAGICINDENT 3
|
|
void CNscTree::_DrawIcon(HTREEITEM hti, HDC hdc, int iLevel, RECT *prc, DWORD dwFlags)
|
|
{
|
|
HIMAGELIST himl = TreeView_GetImageList(_hwndTree, TVSIL_NORMAL);
|
|
TV_ITEM tvi;
|
|
int dx, dy, x, y;
|
|
|
|
tvi.mask = TVIF_SELECTEDIMAGE | TVIF_IMAGE | TVIF_HANDLE;
|
|
tvi.hItem = hti;
|
|
if (TreeView_GetItem(_hwndTree, &tvi))
|
|
{
|
|
ImageList_GetIconSize(himl, &dx, &dy);
|
|
if (!_fStartingDrag)
|
|
x = DXLEFT;
|
|
else
|
|
x = 0;
|
|
x += (iLevel * TreeView_GetIndent(_hwndTree)); // - ((dwFlags & DIFOLDEROPEN) ? 1 : 0);
|
|
y = prc->top + (((prc->bottom - prc->top) - dy) >> 1);
|
|
int iImage = (dwFlags & DIFOLDEROPEN) ? tvi.iSelectedImage : tvi.iImage;
|
|
ImageList_DrawEx(himl, iImage, hdc, x, y, 0, 0, CLR_NONE, GetSysColor(COLOR_WINDOW), (dwFlags & DIGREYED) ? ILD_BLEND50 : ILD_TRANSPARENT);
|
|
|
|
if (dwFlags & DIPINNED)
|
|
{
|
|
ASSERT(_hicoPinned);
|
|
DrawIconEx(hdc, x, y, _hicoPinned, 16, 16, 0, NULL, DI_NORMAL);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
#define TreeView_GetFont(hwnd) (HFONT)::SendMessage(hwnd, WM_GETFONT, 0, 0)
|
|
|
|
void CNscTree::_DrawItem(HTREEITEM hti, TCHAR * psz, HDC hdc
|
|
, LPRECT prc, DWORD dwFlags, int iLevel, COLORREF clrbk, COLORREF clrtxt)
|
|
{
|
|
SIZE size;
|
|
HIMAGELIST himl = TreeView_GetImageList(_hwndTree, TVSIL_NORMAL);
|
|
HFONT hfont = NULL;
|
|
HFONT hfontOld = NULL;
|
|
int x, y, dx, dy;
|
|
LOGFONT lf;
|
|
|
|
COLORREF clrGreyed = GetSysColor(COLOR_BTNSHADOW);
|
|
if ((dwFlags & DIGREYED) && (clrbk != clrGreyed))
|
|
{
|
|
clrtxt = clrGreyed;
|
|
}
|
|
|
|
// For the history and favorites bars, we use the default
|
|
// font (for UI consistency with the folders bar).
|
|
|
|
if (_mode != MODE_FAVORITES && _mode != MODE_HISTORY)
|
|
hfont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
|
|
|
|
if ((dwFlags & DIHOT) && !(dwFlags & DIFOLDER))
|
|
{
|
|
if (!hfont)
|
|
hfont = TreeView_GetFont(_hwndTree);
|
|
|
|
// create the underline font
|
|
GetObject(hfont, sizeof(lf), &lf);
|
|
lf.lfUnderline = TRUE;
|
|
hfont = CreateFontIndirect(&lf);
|
|
}
|
|
|
|
if (hfont)
|
|
hfontOld = (HFONT)SelectObject(hdc, hfont);
|
|
GetTextExtentPoint32(hdc, psz, lstrlen(psz), &size);
|
|
if (himl)
|
|
ImageList_GetIconSize(himl, &dx, &dy);
|
|
else
|
|
{
|
|
dx = 0;
|
|
dy = 0;
|
|
}
|
|
x = prc->left + ((dwFlags & DIICON) ? (iLevel * TreeView_GetIndent(_hwndTree) + dx + DXLEFT + MAGICINDENT) : DXLEFT);
|
|
if (_fStartingDrag)
|
|
x -= DXLEFT;
|
|
y = prc->top + (((prc->bottom - prc->top) - size.cy) >> 1);
|
|
|
|
UINT eto = ETO_CLIPPED;
|
|
RECT rc;
|
|
rc.left = prc->left + 2;
|
|
rc.top = prc->top;
|
|
rc.bottom = prc->bottom;
|
|
rc.right = prc->right - 2;
|
|
|
|
SetBkColor(hdc, clrbk);
|
|
eto |= ETO_OPAQUE;
|
|
ExtTextOut(hdc, 0, 0, eto, &rc, NULL, 0, NULL);
|
|
|
|
SetTextColor(hdc, clrtxt);
|
|
rc.left = x;
|
|
rc.top = y;
|
|
rc.bottom = rc.top + size.cy;
|
|
|
|
UINT uFormat = DT_VCENTER | DT_END_ELLIPSIS | DT_NOPREFIX;
|
|
if (dwFlags & DIRIGHT)
|
|
uFormat |= DT_RIGHT;
|
|
if (dwFlags & DIRTLREADING)
|
|
uFormat |= DT_RTLREADING;
|
|
DrawTextWrap(hdc, psz, lstrlen(psz), &rc, uFormat);
|
|
|
|
if (dwFlags & DIICON)
|
|
_DrawIcon(hti, hdc, iLevel, prc, dwFlags);
|
|
if (hfontOld)
|
|
SelectObject(hdc, hfontOld);
|
|
|
|
if (dwFlags & DIACTIVEBORDER)
|
|
{
|
|
if (dwFlags & DIFIRST)
|
|
{
|
|
rc = *prc;
|
|
rc.left += 2;
|
|
rc.bottom = rc.top + 1;
|
|
rc.right -= 2;
|
|
SHFillRectClr(hdc, &rc, GetSysColor(COLOR_BTNSHADOW));
|
|
}
|
|
if (dwFlags & DISUBITEM)
|
|
{
|
|
rc = *prc;
|
|
rc.left += 2;
|
|
rc.right = rc.left + 1;
|
|
SHFillRectClr(hdc, &rc, GetSysColor(COLOR_BTNSHADOW));
|
|
rc.right = prc->right - 2;
|
|
rc.left = rc.right - 1;
|
|
SHFillRectClr(hdc, &rc, GetSysColor(COLOR_BTNSHADOW));
|
|
}
|
|
if (dwFlags & DILAST)
|
|
{
|
|
rc = *prc;
|
|
rc.left += 2;
|
|
rc.top = rc.bottom - 1;
|
|
rc.right -= 2;
|
|
SHFillRectClr(hdc, &rc, GetSysColor(COLOR_BTNSHADOW));
|
|
}
|
|
}
|
|
|
|
if (hfont)
|
|
DeleteObject(hfont);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// If going online, ungreys all items that were unavailable. If going
|
|
// offline, refreshes all items to see if they are still available.
|
|
//--------------------------------------------------------------------------
|
|
void CNscTree::_OnSHNotifyOnlineChange(HTREEITEM htiRoot, BOOL fGoingOnline)
|
|
{
|
|
HTREEITEM hItem;
|
|
|
|
for (hItem = TreeView_GetChild(_hwndTree, htiRoot); hItem
|
|
; hItem = TreeView_GetNextSibling(_hwndTree, hItem))
|
|
{
|
|
ITEMINFO *pii = _GetTreeItemInfo(hItem);
|
|
if (pii)
|
|
{
|
|
if (fGoingOnline)
|
|
{
|
|
// Going online, if previously greyed then ungrey it
|
|
if (pii->fGreyed)
|
|
{
|
|
pii->fGreyed = FALSE;
|
|
_UpdateItemDisplayInfo(hItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Recheck each item to see if they should be greyed
|
|
if (pii->fFetched && !pii->fDontRefetch)
|
|
{
|
|
pii->fFetched = FALSE;
|
|
_UpdateItemDisplayInfo(hItem);
|
|
}
|
|
}
|
|
}
|
|
// Inform children too
|
|
_OnSHNotifyOnlineChange(hItem, fGoingOnline);
|
|
}
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Force items to recheck to see if the should be pinned or greyed
|
|
//--------------------------------------------------------------------------
|
|
void CNscTree::_OnSHNotifyCacheChange
|
|
(
|
|
HTREEITEM htiRoot, // recurse through all children
|
|
DWORD_PTR dwFlags // CACHE_NOTIFY_* flags
|
|
)
|
|
{
|
|
HTREEITEM hItem;
|
|
|
|
for (hItem = TreeView_GetChild(_hwndTree, htiRoot); hItem
|
|
; hItem = TreeView_GetNextSibling(_hwndTree, hItem))
|
|
{
|
|
ITEMINFO *pii = _GetTreeItemInfo(hItem);
|
|
if (pii)
|
|
{
|
|
// If we have cached info for this item, refresh it if it's state may have toggled
|
|
if ((pii->fFetched && !pii->fDontRefetch) &&
|
|
((pii->fGreyed && (dwFlags & CACHE_NOTIFY_ADD_URL)) ||
|
|
|
|
// We only need to check ungreyed items for changes to the
|
|
// stickey bit in the cache!
|
|
(!pii->fGreyed &&
|
|
((dwFlags & (CACHE_NOTIFY_DELETE_URL | CACHE_NOTIFY_DELETE_ALL))) ||
|
|
(!pii->fPinned && (dwFlags & CACHE_NOTIFY_URL_SET_STICKY)) ||
|
|
(pii->fPinned && (dwFlags & CACHE_NOTIFY_URL_UNSET_STICKY))
|
|
)
|
|
))
|
|
{
|
|
pii->fFetched = FALSE;
|
|
_UpdateItemDisplayInfo(hItem);
|
|
}
|
|
}
|
|
|
|
// Do it's children too
|
|
_OnSHNotifyCacheChange(hItem, dwFlags);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Calls the appropriate routine in shdocvw to favorites import or export on
|
|
// the currently selected item
|
|
//
|
|
HRESULT CNscTree::DoImportOrExport(BOOL fImport)
|
|
{
|
|
HTREEITEM htiSelected = TreeView_GetSelection(_hwndTree);
|
|
LPITEMIDLIST pidl = _GetFullIDList(htiSelected);
|
|
if (pidl)
|
|
{
|
|
//
|
|
// If current selection is not a folder get the parent pidl
|
|
//
|
|
if (!ILIsFileSysFolder(pidl))
|
|
ILRemoveLastID(pidl);
|
|
|
|
//
|
|
// Create the actual routine in shdocvw to do the import/export work
|
|
//
|
|
IShellUIHelper *pShellUIHelper;
|
|
HRESULT hr = CoCreateInstance(CLSID_ShellUIHelper, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellUIHelper, &pShellUIHelper));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
VARIANT_BOOL vbImport = fImport ? VARIANT_TRUE : VARIANT_FALSE;
|
|
WCHAR wszPath[MAX_PATH];
|
|
|
|
SHGetPathFromIDListW(pidl, wszPath);
|
|
|
|
hr = pShellUIHelper->ImportExportFavorites(vbImport, wszPath);
|
|
if (SUCCEEDED(hr) && fImport)
|
|
{
|
|
//
|
|
// Successfully imported favorites so need to update view
|
|
// FEATURE ie5 24973 - flicker alert, should optimize to just redraw selected
|
|
//
|
|
Refresh();
|
|
//TreeView_SelectItem(_hwndTree, htiSelected);
|
|
}
|
|
|
|
pShellUIHelper->Release();
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::GetSelectedItemName(LPWSTR pszName, DWORD cchName)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
TV_ITEM tvi = {0};
|
|
|
|
tvi.hItem = TreeView_GetSelection(_hwndTree);
|
|
if (tvi.hItem != NULL)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
|
|
tvi.mask = TVIF_HANDLE | TVIF_TEXT;
|
|
tvi.pszText = szPath;
|
|
tvi.cchTextMax = ARRAYSIZE(szPath);
|
|
|
|
if (TreeView_GetItem(_hwndTree, &tvi))
|
|
{
|
|
SHTCharToUnicode(szPath, pszName, cchName);
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNscTree::BindToSelectedItemParent(REFIID riid, void **ppv, LPITEMIDLIST *ppidl)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
if (!_fClosing)
|
|
{
|
|
LPCITEMIDLIST pidlItem = _CacheParentShellFolder(TreeView_GetSelection(_hwndTree), NULL);
|
|
if (pidlItem)
|
|
{
|
|
hr = _psfCache->QueryInterface(riid, ppv);
|
|
if (SUCCEEDED(hr) && ppidl)
|
|
{
|
|
*ppidl = ILClone(pidlItem);
|
|
if (*ppidl == NULL)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
((IUnknown *)*ppv)->Release();
|
|
*ppv = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// takes ownership of pidl
|
|
HRESULT CNscTree::RightPaneNavigationStarted(LPITEMIDLIST pidl)
|
|
{
|
|
_fExpandNavigateTo = FALSE;
|
|
_fNavigationFinished = FALSE;
|
|
|
|
Pidl_Set(&_pidlNavigatingTo, pidl);
|
|
return S_OK;
|
|
}
|
|
|
|
// takes ownership of pidl
|
|
HRESULT CNscTree::RightPaneNavigationFinished(LPITEMIDLIST pidl)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
_fNavigationFinished = TRUE;
|
|
if (_fExpandNavigateTo)
|
|
{
|
|
_fExpandNavigateTo = FALSE; // only do this once
|
|
|
|
hr = E_OUTOFMEMORY;
|
|
HDPA hdpa = DPA_Create(2);
|
|
if (hdpa)
|
|
{
|
|
IDVGetEnum *pdvge; // private defview interface
|
|
hr = IUnknown_QueryService(_punkSite, SID_SFolderView, IID_PPV_ARG(IDVGetEnum, &pdvge));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
HTREEITEM hti = _FindFromRoot(NULL, pidl);
|
|
// Try to find the tree item using the pidl
|
|
if (hti)
|
|
{
|
|
IShellFolder* psf;
|
|
hr = IEBindToObject(pidl, &psf);
|
|
if (S_OK == hr)
|
|
{
|
|
ITEMINFO *pii = _GetTreeItemInfo(hti);
|
|
DWORD grfFlags;
|
|
_GetEnumFlags(psf, pidl, &grfFlags, NULL);
|
|
IEnumIDList *penum;
|
|
hr = pdvge->CreateEnumIDListFromContents(pidl, grfFlags, &penum);
|
|
if (S_OK == hr)
|
|
{
|
|
ULONG celt;
|
|
LPITEMIDLIST pidlTemp;
|
|
while (S_OK == penum->Next(1, &pidlTemp, &celt))
|
|
{
|
|
if (!OrderList_Append(hdpa, pidlTemp, -1))
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
ILFree(pidlTemp);
|
|
break;
|
|
}
|
|
}
|
|
penum->Release();
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
ORDERINFO oinfo;
|
|
oinfo.psf = psf;
|
|
oinfo.dwSortBy = OI_SORTBYNAME; // merge depends on by name.
|
|
|
|
DPA_Sort(hdpa, OrderItem_Compare, (LPARAM)&oinfo);
|
|
OrderList_Reorder(hdpa);
|
|
|
|
LPITEMIDLIST pidlExpClone = ILClone(_pidlExpandingTo); // NULL is OK
|
|
|
|
s_NscEnumCallback(this, pidl, (UINT_PTR)hti, pii->dwSig, hdpa, pidlExpClone, _dwOrderSig, 0, FALSE, FALSE);
|
|
hdpa = NULL;
|
|
pidl = NULL;
|
|
}
|
|
psf->Release();
|
|
}
|
|
}
|
|
pdvge->Release();
|
|
}
|
|
}
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
if (hdpa)
|
|
OrderList_Destroy(&hdpa, TRUE); // calls DPA_Destroy(hdpa)
|
|
|
|
if (pidl)
|
|
{
|
|
HTREEITEM hti = _FindFromRoot(NULL, pidl);
|
|
if (hti)
|
|
{
|
|
BOOL fOrdered;
|
|
hr = _StartBackgroundEnum(hti, pidl, &fOrdered, FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ILFree(pidl);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNscTree::MoveSelectionTo(void)
|
|
{
|
|
return MoveItemsIntoFolder(::GetParent(_hwndParent)) ? S_OK : S_FALSE;
|
|
}
|
|
|
|
BOOL CNscTree::MoveItemsIntoFolder(HWND hwndParent)
|
|
{
|
|
BOOL fSuccess = FALSE;
|
|
BROWSEINFO browse = {0};
|
|
TCHAR szDisplayName[MAX_PATH];
|
|
TCHAR szInstructionString[MAX_PATH];
|
|
LPITEMIDLIST pidlDest = NULL, pidlSelected = NULL;
|
|
HTREEITEM htiSelected = NULL;
|
|
|
|
//Initialize the BROWSEINFO struct.
|
|
browse.pidlRoot = ILClone(_pidlRoot);
|
|
if (!browse.pidlRoot)
|
|
return FALSE;
|
|
|
|
htiSelected = TreeView_GetSelection(_hwndTree);
|
|
pidlSelected = _GetFullIDList(htiSelected);
|
|
if (!pidlSelected)
|
|
{
|
|
ILFree((LPITEMIDLIST)browse.pidlRoot);
|
|
return FALSE;
|
|
}
|
|
|
|
MLLoadShellLangString(IDS_FAVORITEBROWSE, szInstructionString, ARRAYSIZE(szInstructionString));
|
|
|
|
browse.pszDisplayName = szDisplayName;
|
|
browse.hwndOwner = hwndParent;
|
|
browse.lpszTitle = szInstructionString;
|
|
browse.ulFlags = BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
|
|
browse.lpfn = NULL;
|
|
browse.lParam = 0;
|
|
browse.iImage = 0;
|
|
|
|
pidlDest = SHBrowseForFolder(&browse);
|
|
if (pidlDest)
|
|
{
|
|
TCHAR szFrom[MAX_PATH+1]; // +1 for double null
|
|
TCHAR szDest[MAX_PATH+1];
|
|
SHGetPathFromIDList(pidlDest, szDest);
|
|
SHGetPathFromIDList(pidlSelected, szFrom);
|
|
|
|
ASSERT(szDest[0]); // must be a file system thing...
|
|
ASSERT(szFrom[0]);
|
|
|
|
szDest[lstrlen(szDest) + 1] = 0; // double null
|
|
szFrom[lstrlen(szFrom) + 1] = 0; // double null
|
|
|
|
|
|
SHFILEOPSTRUCT shop = {hwndParent, FO_MOVE, szFrom, szDest, 0, };
|
|
SHFileOperation(&shop);
|
|
|
|
fSuccess = TRUE;
|
|
|
|
ILFree(pidlDest);
|
|
}
|
|
ILFree((LPITEMIDLIST)browse.pidlRoot);
|
|
ILFree(pidlSelected);
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
// the following guid goo and IsChannelFolder are mostly lifted from cdfview
|
|
#define GUID_STR_LEN 80
|
|
const GUID CLSID_CDFINI = {0xf3aa0dc0, 0x9cc8, 0x11d0, {0xa5, 0x99, 0x0, 0xc0, 0x4f, 0xd6, 0x44, 0x34}};
|
|
// {f3aa0dc0-9cc8-11d0-a599-00c04fd64434}
|
|
|
|
// REARCHITECT: total hack. looks into the desktop.ini for this guy
|
|
//
|
|
// pwzChannelURL is assumed to be INTERNET_MAX_URL_LENGTH
|
|
BOOL IsChannelFolder(LPCWSTR pwzPath, LPWSTR pwzChannelURL)
|
|
{
|
|
ASSERT(pwzPath);
|
|
|
|
BOOL fRet = FALSE;
|
|
|
|
WCHAR wzFolderGUID[GUID_STR_LEN];
|
|
WCHAR wzIniFile[MAX_PATH];
|
|
|
|
if (!PathCombineW(wzIniFile, pwzPath, L"desktop.ini"))
|
|
return FALSE;
|
|
|
|
if (GetPrivateProfileString(L".ShellClassInfo", L"CLSID", L"", wzFolderGUID, ARRAYSIZE(wzFolderGUID), wzIniFile))
|
|
{
|
|
WCHAR wzChannelGUID[GUID_STR_LEN];
|
|
|
|
//it's only a channel if it's got the right guid and an url
|
|
if (SHStringFromGUID(CLSID_CDFINI, wzChannelGUID, ARRAYSIZE(wzChannelGUID)))
|
|
{
|
|
fRet = (StrCmpN(wzFolderGUID, wzChannelGUID, ARRAYSIZE(wzChannelGUID)) == 0);
|
|
if (fRet && pwzChannelURL)
|
|
{
|
|
fRet = (SHGetIniStringW(L"Channel", L"CDFURL", pwzChannelURL, INTERNET_MAX_URL_LENGTH, wzIniFile) != 0);
|
|
}
|
|
}
|
|
}
|
|
return fRet;
|
|
}
|
|
|
|
BOOL CNscTree::_IsChannelFolder(HTREEITEM hti)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
|
|
LPITEMIDLIST pidl = _GetFullIDList(hti);
|
|
if (pidl)
|
|
{
|
|
WCHAR szPath[MAX_PATH];
|
|
if (SUCCEEDED(IEGetNameAndFlags(pidl, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), NULL)))
|
|
{
|
|
fRet = IsChannelFolder(szPath, NULL);
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
return fRet;
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::CreateSubscriptionForSelection(/*[out, retval]*/ VARIANT_BOOL *pBool)
|
|
{
|
|
HRESULT hr = DoSubscriptionForSelection(TRUE);
|
|
|
|
if (pBool)
|
|
*pBool = (SUCCEEDED(hr) ? TRUE : FALSE);
|
|
|
|
return FIX_SCRIPTING_ERRORS(hr);
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::DeleteSubscriptionForSelection(/*[out, retval]*/ VARIANT_BOOL *pBool)
|
|
{
|
|
HRESULT hr = DoSubscriptionForSelection(FALSE);
|
|
|
|
if (pBool)
|
|
*pBool = (SUCCEEDED(hr) ? TRUE : FALSE);
|
|
|
|
return FIX_SCRIPTING_ERRORS(hr);
|
|
}
|
|
|
|
|
|
//
|
|
// 1. get the selected item
|
|
// 2. get it's name
|
|
// 3. get it's url
|
|
// 4. create a Subscription manager and do the right thing for channels
|
|
// 5. return Subscription manager's result
|
|
HRESULT CNscTree::DoSubscriptionForSelection(BOOL fCreate)
|
|
{
|
|
#ifndef DISABLE_SUBSCRIPTIONS
|
|
|
|
HRESULT hr = E_FAIL;
|
|
WCHAR wzUrl[MAX_URL_STRING];
|
|
WCHAR wzName[MAX_PATH];
|
|
HTREEITEM htiSelected = TreeView_GetSelection(_hwndTree);
|
|
if (htiSelected == NULL)
|
|
return E_FAIL;
|
|
|
|
TV_ITEM tvi;
|
|
|
|
tvi.mask = TVIF_HANDLE | TVIF_TEXT;
|
|
tvi.hItem = htiSelected;
|
|
tvi.pszText = wzName;
|
|
tvi.cchTextMax = ARRAYSIZE(wzName);
|
|
|
|
TreeView_GetItem(_hwndTree, &tvi);
|
|
|
|
WCHAR wzPath[MAX_PATH];
|
|
|
|
LPITEMIDLIST pidlItem = _CacheParentShellFolder(htiSelected, NULL);
|
|
if (pidlItem)
|
|
{
|
|
GetPathForItem(_psfCache, pidlItem, wzPath, NULL);
|
|
hr = GetNavTargetName(_psfCache, pidlItem, wzUrl, ARRAYSIZE(wzUrl));
|
|
}
|
|
|
|
if (FAILED(hr)) //if we couldn't get an url, not much to do
|
|
return hr;
|
|
|
|
ISubscriptionMgr *psm;
|
|
|
|
hr = JITCoCreateInstance(CLSID_SubscriptionMgr, NULL,
|
|
CLSCTX_INPROC_SERVER, IID_PPV_ARG(ISubscriptionMgr, &psm),
|
|
_hwndTree, FIEF_FLAG_FORCE_JITUI);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
HCURSOR hCursorOld = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
//IsChannelFolder will fixup wzUrl if it's a channel
|
|
BOOL fChannel = IsChannelFolder(wzPath, wzUrl);
|
|
|
|
if (fCreate)
|
|
{
|
|
SUBSCRIPTIONINFO si = { sizeof(SUBSCRIPTIONINFO) };
|
|
TASK_TRIGGER tt;
|
|
BOOL bIsSoftware = FALSE;
|
|
|
|
if (fChannel)
|
|
{
|
|
IChannelMgrPriv *pChannelMgrPriv;
|
|
|
|
hr = JITCoCreateInstance(CLSID_ChannelMgr, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARG(IChannelMgrPriv, &pChannelMgrPriv),
|
|
_hwndTree, FIEF_FLAG_PEEK);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
WCHAR wszTitle[MAX_PATH];
|
|
|
|
si.fUpdateFlags |= SUBSINFO_SCHEDULE;
|
|
si.schedule = SUBSSCHED_AUTO;
|
|
si.pTrigger = (void *)&tt;
|
|
|
|
hr = pChannelMgrPriv->DownloadMinCDF(_hwndTree, wzUrl,
|
|
wszTitle, ARRAYSIZE(wszTitle),
|
|
&si, &bIsSoftware);
|
|
pChannelMgrPriv->Release();
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DWORD dwFlags = CREATESUBS_NOUI | CREATESUBS_FROMFAVORITES |
|
|
((!bIsSoftware) ? 0 : CREATESUBS_SOFTWAREUPDATE);
|
|
|
|
hr = psm->CreateSubscription(_hwndTree, wzUrl, wzName, dwFlags,
|
|
(fChannel ? SUBSTYPE_CHANNEL : SUBSTYPE_URL),
|
|
&si);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = psm->DeleteSubscription(wzUrl, NULL);
|
|
}
|
|
|
|
// This is in case subscribing or unsubscribing return a failed result even
|
|
// though the action succeeded from our standpoint (ie. item was subscribed
|
|
// successfully but creating a schedule failed or the item was unsubscribed
|
|
// successfully but we couldn't abort a running download in syncmgr).
|
|
|
|
BOOL bSubscribed;
|
|
psm->IsSubscribed(wzUrl, &bSubscribed);
|
|
|
|
hr = ((fCreate && bSubscribed) || (!fCreate && !bSubscribed)) ? S_OK : E_FAIL;
|
|
|
|
psm->Release();
|
|
|
|
SetCursor(hCursorOld);
|
|
}
|
|
|
|
return hr;
|
|
|
|
#else /* !DISABLE_SUBSCRIPTIONS */
|
|
|
|
return E_FAIL;
|
|
|
|
#endif /* !DISABLE_SUBSCRIPTIONS */
|
|
|
|
}
|
|
|
|
|
|
// Causes NSC to re-root on a different pidl --
|
|
HRESULT CNscTree::_ChangePidlRoot(LPCITEMIDLIST pidl)
|
|
{
|
|
_fClosing = TRUE;
|
|
::SendMessage(_hwndTree, WM_SETREDRAW, FALSE, 0);
|
|
_bSynchId++;
|
|
if (_bSynchId >= 16)
|
|
_bSynchId = 0;
|
|
TreeView_DeleteAllItemsQuickly(_hwndTree);
|
|
_htiActiveBorder = NULL;
|
|
_fClosing = FALSE;
|
|
if (_psfCache)
|
|
_ReleaseCachedShellFolder();
|
|
|
|
// We do this even for (NULL == pidl) because (CSIDL_DESKTOP == NULL)
|
|
if ((LPCITEMIDLIST)INVALID_HANDLE_VALUE != pidl)
|
|
{
|
|
_UnSubClass();
|
|
_SetRoot(pidl, 3/*random*/, NULL, NSSR_CREATEPIDL);
|
|
_SubClass(pidl);
|
|
}
|
|
::SendMessage(_hwndTree, WM_SETREDRAW, TRUE, 0);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
BOOL CNscTree::_IsMarked(HTREEITEM hti)
|
|
{
|
|
if ((hti == NULL) || (hti == TVI_ROOT))
|
|
return FALSE;
|
|
|
|
TVITEM tvi;
|
|
tvi.mask = TVIF_HANDLE | TVIF_STATE;
|
|
tvi.stateMask = TVIS_STATEIMAGEMASK;
|
|
tvi.state = 0;
|
|
tvi.hItem = hti;
|
|
TreeView_GetItem(_hwndTree, &tvi);
|
|
|
|
return BOOLIFY(tvi.state & NSC_TVIS_MARKED);
|
|
}
|
|
|
|
void CNscTree::_MarkChildren(HTREEITEM htiParent, BOOL fOn)
|
|
{
|
|
TVITEM tvi;
|
|
tvi.mask = TVIF_HANDLE | TVIF_STATE;
|
|
tvi.stateMask = TVIS_STATEIMAGEMASK;
|
|
tvi.state = (fOn ? NSC_TVIS_MARKED : 0);
|
|
tvi.hItem = htiParent;
|
|
TreeView_SetItem(_hwndTree, &tvi);
|
|
|
|
for (HTREEITEM htiTemp = TreeView_GetChild(_hwndTree, htiParent); htiTemp; htiTemp = TreeView_GetNextSibling(_hwndTree, htiTemp))
|
|
{
|
|
tvi.hItem = htiTemp;
|
|
TreeView_SetItem(_hwndTree, &tvi);
|
|
|
|
_MarkChildren(htiTemp, fOn);
|
|
}
|
|
}
|
|
|
|
//Updates the tree and internal state for the active border (the 1 pixel line)
|
|
// htiSelected is the item that was just clicked on/selected
|
|
void CNscTree::_UpdateActiveBorder(HTREEITEM htiSelected)
|
|
{
|
|
HTREEITEM htiNewBorder;
|
|
if (MODE_NORMAL == _mode)
|
|
return;
|
|
|
|
//if an item is a folder, then it should have the border
|
|
if (htiSelected != TVI_ROOT)
|
|
{
|
|
if (TreeView_GetChild(_hwndTree, htiSelected))
|
|
htiNewBorder = htiSelected;
|
|
else
|
|
htiNewBorder = TreeView_GetParent(_hwndTree, htiSelected);
|
|
}
|
|
else
|
|
htiNewBorder = NULL;
|
|
|
|
//clear the old state
|
|
// in multiselect mode we don't unselect the previously selected folder
|
|
if ((!(_dwFlags & NSS_MULTISELECT)) && (_htiActiveBorder != TVI_ROOT) && (_htiActiveBorder != NULL)
|
|
&& (htiNewBorder != _htiActiveBorder))
|
|
_MarkChildren(_htiActiveBorder, FALSE);
|
|
|
|
//set the new state
|
|
BOOL bMark = TRUE;
|
|
if (_dwFlags & NSS_MULTISELECT)
|
|
{
|
|
bMark = TreeView_GetItemState(_hwndTree, htiNewBorder, NSC_TVIS_MARKED) & NSC_TVIS_MARKED;
|
|
}
|
|
|
|
if (bMark && (htiNewBorder != TVI_ROOT) && (htiNewBorder != NULL))
|
|
_MarkChildren(htiNewBorder, TRUE);
|
|
|
|
//treeview knows to invalidate itself
|
|
|
|
_htiActiveBorder = htiNewBorder;
|
|
}
|
|
|
|
void CNscTree::_UpdateItemDisplayInfo(HTREEITEM hti)
|
|
{
|
|
if (_GetTreeItemInfo(hti) && _pTaskScheduler)
|
|
{
|
|
LPITEMIDLIST pidl = _GetFullIDList(hti);
|
|
if (pidl)
|
|
{
|
|
LPITEMIDLIST pidl2 = _mode == MODE_NORMAL ? ILClone(pidl) : NULL;
|
|
|
|
AddNscIconTask(_pTaskScheduler, pidl, s_NscIconCallback, this, (UINT_PTR) hti, (UINT)_bSynchId);
|
|
if (pidl2)
|
|
{
|
|
AddNscOverlayTask(_pTaskScheduler, pidl2, &s_NscOverlayCallback, this, (UINT_PTR)hti, (UINT)_bSynchId);
|
|
}
|
|
}
|
|
}
|
|
//pidls get freed by CNscIconTask
|
|
}
|
|
|
|
void CNscTree::s_NscOverlayCallback(CNscTree *pns, UINT_PTR uId, int iOverlayIndex, UINT uMagic)
|
|
{
|
|
ASSERT(pns);
|
|
ASSERT(uId);
|
|
|
|
//this function gets called on a background thread, so use PostMessage to do treeview ops
|
|
//on the main thread only.
|
|
|
|
//assert that wacky packing is going to work
|
|
ASSERT(((iOverlayIndex & 0x0fffffff) == iOverlayIndex) && (uMagic < 16));
|
|
|
|
LPARAM lParam = (uMagic << 28) + iOverlayIndex;
|
|
|
|
if (uMagic == pns->_bSynchId && ::IsWindow(pns->_hwndTree))
|
|
::PostMessage(pns->_hwndTree, WM_NSCUPDATEICONOVERLAY, (WPARAM)uId, lParam);
|
|
}
|
|
|
|
void CNscTree::s_NscIconCallback(CNscTree *pns, UINT_PTR uId, int iIcon, int iIconOpen, DWORD dwFlags, UINT uMagic)
|
|
{
|
|
ASSERT(pns);
|
|
ASSERT(uId);
|
|
|
|
//this function gets called on a background thread, so use PostMessage to do treeview ops
|
|
//on the main thread only.
|
|
|
|
//assert that wacky packing is going to work
|
|
ASSERT((iIcon < 4096) && (iIconOpen < 4096) && (dwFlags < 16) && (uMagic < 16));
|
|
|
|
LPARAM lParam = (uMagic << 28) + (dwFlags << 24) + (iIconOpen << 12) + iIcon;
|
|
|
|
if (uMagic == pns->_bSynchId && ::IsWindow(pns->_hwndTree))
|
|
::PostMessage(pns->_hwndTree, WM_NSCUPDATEICONINFO, (WPARAM)uId, lParam);
|
|
}
|
|
|
|
LRESULT CNscTree::_OnCommand(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
UINT idCmd = GET_WM_COMMAND_ID(wParam, lParam);
|
|
|
|
switch(idCmd)
|
|
{
|
|
case FCIDM_MOVE:
|
|
InvokeContextMenuCommand(L"cut");
|
|
break;
|
|
|
|
case FCIDM_COPY:
|
|
InvokeContextMenuCommand(L"copy");
|
|
break;
|
|
|
|
case FCIDM_PASTE:
|
|
InvokeContextMenuCommand(L"paste");
|
|
break;
|
|
|
|
case FCIDM_LINK:
|
|
InvokeContextMenuCommand(L"link");
|
|
break;
|
|
|
|
case FCIDM_DELETE:
|
|
InvokeContextMenuCommand(L"delete");
|
|
if (_hwndTree)
|
|
{
|
|
SHChangeNotifyHandleEvents();
|
|
}
|
|
break;
|
|
|
|
case FCIDM_PROPERTIES:
|
|
InvokeContextMenuCommand(L"properties");
|
|
break;
|
|
|
|
case FCIDM_RENAME:
|
|
{
|
|
// HACKHACK (lamadio): This is to hack around tree view renaming on click and hover
|
|
_fOkToRename = TRUE;
|
|
HTREEITEM hti = TreeView_GetSelection(_hwndTree);
|
|
if (hti)
|
|
TreeView_EditLabel(_hwndTree, hti);
|
|
_fOkToRename = FALSE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CNscTree::_TreeSetItemState(HTREEITEM hti, UINT stateMask, UINT state)
|
|
{
|
|
if (hti)
|
|
{
|
|
TV_ITEM tvi;
|
|
tvi.mask = TVIF_STATE;
|
|
tvi.stateMask = stateMask;
|
|
tvi.hItem = hti;
|
|
tvi.state = state;
|
|
TreeView_SetItem(_hwndTree, &tvi);
|
|
}
|
|
|
|
}
|
|
|
|
void CNscTree::_TreeNukeCutState()
|
|
{
|
|
_TreeSetItemState(_htiCut, TVIS_CUT, 0);
|
|
_htiCut = NULL;
|
|
|
|
::ChangeClipboardChain(_hwndTree, _hwndNextViewer);
|
|
_hwndNextViewer = NULL;
|
|
}
|
|
|
|
// *** IFolderFilterSite methods ***
|
|
HRESULT CNscTree::SetFilter(IUnknown* punk)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
ATOMICRELEASE(_pFilter);
|
|
|
|
if (punk)
|
|
hr = punk->QueryInterface(IID_PPV_ARG(IFolderFilter, &_pFilter));
|
|
|
|
return hr;
|
|
}
|
|
|
|
int DPADeletePidlsCB(void *pItem, void *pData)
|
|
{
|
|
if (pItem)
|
|
ILFree((LPITEMIDLIST)pItem);
|
|
return TRUE;
|
|
}
|
|
|
|
int DPADeleteItemCB(void *pItem, void *pData)
|
|
{
|
|
if (pItem)
|
|
{
|
|
LocalFree(pItem);
|
|
pItem = NULL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
HRESULT CNscTree::get_SubscriptionsEnabled(VARIANT_BOOL * pVal)
|
|
{
|
|
*pVal = BOOLIFY(!SHRestricted2(REST_NoAddingSubscriptions, NULL, 0));
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNscTree::Synchronize()
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNscTree::NewFolder()
|
|
{
|
|
//we should do this activates stuff only in control mode
|
|
//hack to get control to be activated fully
|
|
m_bUIActive = FALSE;
|
|
InPlaceActivate(OLEIVERB_UIACTIVATE);
|
|
|
|
return CreateNewFolder(TreeView_GetSelection(_hwndTree));
|
|
}
|
|
|
|
HRESULT CNscTree::InvokeContextMenuCommand(BSTR strCommand)
|
|
{
|
|
ASSERT(strCommand);
|
|
|
|
if (strCommand)
|
|
{
|
|
//again activate only if in control mode
|
|
//only if renaming, activate control
|
|
if (StrStr(strCommand, L"rename") != NULL)
|
|
{
|
|
//hack to get control to be activated fully
|
|
m_bUIActive = FALSE;
|
|
InPlaceActivate(OLEIVERB_UIACTIVATE);
|
|
}
|
|
|
|
return _InvokeContextMenuCommand(strCommand);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNscTree::get_EnumOptions(LONG *pVal)
|
|
{
|
|
*pVal = _grfFlags;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNscTree::put_EnumOptions(LONG lVal)
|
|
{
|
|
_grfFlags = lVal;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CreateFolderItem(LPCITEMIDLIST pidl, IDispatch **ppItem)
|
|
{
|
|
*ppItem = NULL;
|
|
|
|
IPersistFolder *ppf;
|
|
HRESULT hr = CoCreateInstance(CLSID_FolderItem, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IPersistFolder, &ppf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (S_OK == ppf->Initialize(pidl))
|
|
{
|
|
hr = ppf->QueryInterface(IID_PPV_ARG(IDispatch, ppItem));
|
|
}
|
|
else
|
|
hr = E_FAIL;
|
|
ppf->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNscTree::get_SelectedItem(IDispatch **ppItem)
|
|
{
|
|
*ppItem = NULL;
|
|
|
|
LPITEMIDLIST pidl;
|
|
if (SUCCEEDED(GetSelectedItem(&pidl, 0)) && pidl)
|
|
{
|
|
CreateFolderItem(pidl, ppItem);
|
|
ILFree(pidl);
|
|
}
|
|
return *ppItem ? S_OK : S_FALSE;
|
|
}
|
|
|
|
HRESULT CNscTree::put_SelectedItem(IDispatch *pItem)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
HRESULT CNscTree::get_Root(VARIANT *pvar)
|
|
{
|
|
pvar->vt = VT_EMPTY;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNscTree::put_Root(VARIANT var)
|
|
{
|
|
if (_csidl != -1)
|
|
{
|
|
SetNscMode(MODE_CONTROL);
|
|
_csidl = -1; // unknown
|
|
}
|
|
|
|
return _PutRootVariant(&var);
|
|
}
|
|
|
|
HRESULT CNscTree::_PutRootVariant(VARIANT *pvar)
|
|
{
|
|
BOOL bReady = _pidlRoot != NULL;
|
|
LPITEMIDLIST pidl = VariantToIDList(pvar);
|
|
if (_hdpaViews)
|
|
{
|
|
DPA_DestroyCallback(_hdpaViews, DPADeletePidlsCB, NULL);
|
|
_hdpaViews = NULL;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
if (bReady)
|
|
hr = _ChangePidlRoot(pidl);
|
|
|
|
ILFree(pidl);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNscTree::SetRoot(BSTR bstrFullPath)
|
|
{
|
|
// SetRoot is from IShellFavoritesNamespace so turn on Favorites mode
|
|
_csidl = CSIDL_FAVORITES;
|
|
SetNscMode(MODE_FAVORITES | MODE_CONTROL);
|
|
|
|
CComVariant varPath(bstrFullPath);
|
|
|
|
return FIX_SCRIPTING_ERRORS(_PutRootVariant(&varPath));
|
|
}
|
|
|
|
|
|
HRESULT CNscTree::put_Mode(UINT uMode)
|
|
{
|
|
SetNscMode(uMode);
|
|
_csidl = -1;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNscTree::put_Flags(DWORD dwFlags)
|
|
{
|
|
_dwFlags = dwFlags;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNscTree::get_Columns(BSTR *pbstrColumns)
|
|
{
|
|
*pbstrColumns = SysAllocString(TEXT(""));
|
|
return *pbstrColumns? S_OK: E_FAIL;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
TCHAR szName[20];
|
|
const SHCOLUMNID *pscid;
|
|
} COLUMNS;
|
|
|
|
static COLUMNS s_Columns[] =
|
|
{
|
|
{TEXT("name"), &SCID_NAME},
|
|
{TEXT("attribs"), &SCID_ATTRIBUTES},
|
|
{TEXT("size"), &SCID_SIZE},
|
|
{TEXT("type"), &SCID_TYPE},
|
|
{TEXT("create"), &SCID_CREATETIME},
|
|
};
|
|
|
|
int _SCIDsFromNames(LPTSTR pszNames, int nSize, const SHCOLUMNID *apscid[])
|
|
{
|
|
int cItems = 0;
|
|
|
|
if (!pszNames || !apscid || !nSize)
|
|
return -1;
|
|
|
|
do
|
|
{
|
|
BOOL bInsert = FALSE;
|
|
LPTSTR pszTemp = StrChr(pszNames, TEXT(';'));
|
|
|
|
if (pszTemp)
|
|
{
|
|
*pszTemp = 0;
|
|
pszTemp++;
|
|
}
|
|
|
|
for (int i = 0; i < ARRAYSIZE(s_Columns); i++)
|
|
{
|
|
if (StrCmpI(pszNames, s_Columns[i].szName) == 0)
|
|
{
|
|
bInsert = TRUE;
|
|
#ifdef NO_DUPLICATES
|
|
for (int j = 0; j < cItems; j++)
|
|
{
|
|
if (IsEqualSCID(*(s_Columns[i].pscid), *apscid[j]))
|
|
{
|
|
bInsert = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
if (bInsert)
|
|
{
|
|
apscid[cItems++] = s_Columns[i].pscid;
|
|
if (cItems >= nSize)
|
|
break;
|
|
}
|
|
pszNames = pszTemp;
|
|
}
|
|
while(pszNames);
|
|
|
|
return cItems;
|
|
}
|
|
|
|
HRESULT CNscTree::put_Columns(BSTR bstrColumns)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (_dwFlags & NSS_HEADER)
|
|
{
|
|
if (!_hdpaColumns)
|
|
{
|
|
_hdpaColumns = DPA_Create(3);
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
DPA_EnumCallback(_hdpaColumns, DPADeleteItemCB, NULL);
|
|
DPA_DeleteAllPtrs(_hdpaColumns);
|
|
}
|
|
|
|
if (_hdpaColumns)
|
|
{
|
|
const SHCOLUMNID *apscid[5];
|
|
int cItems = _SCIDsFromNames(bstrColumns, ARRAYSIZE(apscid), apscid);
|
|
|
|
hr = S_OK;
|
|
|
|
for (int i = 0; i < cItems; i++)
|
|
{
|
|
HEADERINFO *phinfo = (HEADERINFO *)LocalAlloc(LPTR, sizeof(HEADERINFO));
|
|
if (phinfo)
|
|
{
|
|
phinfo->pscid = apscid[i];
|
|
phinfo->iFldrCol = -1;
|
|
if (DPA_AppendPtr(_hdpaColumns, (void *)phinfo) == -1)
|
|
{
|
|
hr = E_FAIL;
|
|
LocalFree(phinfo);
|
|
phinfo = NULL;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
break;
|
|
}
|
|
}
|
|
if (DPA_GetPtrCount(_hdpaColumns) > 0)
|
|
_CreateHeader();
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNscTree::get_CountViewTypes(int *piTypes)
|
|
{
|
|
*piTypes = 0;
|
|
|
|
if (_pidlRoot && !_hdpaViews)
|
|
{
|
|
IShellFolder *psf;
|
|
if (SUCCEEDED(IEBindToObject(_pidlRoot, &psf))) //do we have this cached?
|
|
{
|
|
IShellFolderViewType *psfvt;
|
|
if (SUCCEEDED(psf->QueryInterface(IID_PPV_ARG(IShellFolderViewType, &psfvt))))
|
|
{
|
|
IEnumIDList *penum;
|
|
if (SUCCEEDED(psfvt->EnumViews(0, &penum)))
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
ULONG cFetched;
|
|
|
|
_hdpaViews = DPA_Create(4);
|
|
if (_hdpaViews)
|
|
{
|
|
while (penum->Next(1, &pidl, &cFetched) == S_OK && cFetched == 1)
|
|
{
|
|
if (DPA_AppendPtr(_hdpaViews, pidl) == -1)
|
|
{
|
|
ILFree(pidl);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
penum->Release();
|
|
}
|
|
psfvt->Release();
|
|
}
|
|
psf->Release();
|
|
}
|
|
}
|
|
|
|
if (_hdpaViews)
|
|
*piTypes = DPA_GetPtrCount(_hdpaViews);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNscTree::SetViewType(int iType)
|
|
{
|
|
HRESULT hr = S_FALSE;
|
|
|
|
if (_hdpaViews && iType < DPA_GetPtrCount(_hdpaViews)) // allow negative types to reset to _pidlRoot
|
|
{
|
|
LPITEMIDLIST pidl = (LPITEMIDLIST)DPA_GetPtr(_hdpaViews, iType);
|
|
LPITEMIDLIST pidlType;
|
|
|
|
if (pidl)
|
|
pidlType = ILCombine(_pidlRoot, pidl);
|
|
else
|
|
pidlType = _pidlRoot;
|
|
|
|
if (pidlType)
|
|
{
|
|
hr = _ChangePidlRoot(pidlType);
|
|
if (pidlType != _pidlRoot)
|
|
ILFree(pidlType);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CreateFolderItemsFDF(LPCITEMIDLIST pidl, IDispatch **ppItems)
|
|
{
|
|
*ppItems = NULL;
|
|
|
|
IPersistFolder *ppf;
|
|
HRESULT hr = CoCreateInstance(CLSID_FolderItemsFDF, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IPersistFolder, &ppf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (S_OK == ppf->Initialize(pidl))
|
|
{
|
|
hr = ppf->QueryInterface(IID_PPV_ARG(IDispatch, ppItems));
|
|
}
|
|
else
|
|
hr = E_FAIL;
|
|
ppf->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
void CNscTree::_InsertMarkedChildren(HTREEITEM htiParent, LPCITEMIDLIST pidlParent, IInsertItem *pii)
|
|
{
|
|
TV_ITEM tvi = {0};
|
|
|
|
tvi.mask = TVIF_PARAM | TVIF_HANDLE;
|
|
for (HTREEITEM htiTemp = TreeView_GetChild(_hwndTree, htiParent); htiTemp; htiTemp = TreeView_GetNextSibling(_hwndTree, htiTemp))
|
|
{
|
|
BOOL bMarked = TreeView_GetItemState(_hwndTree, htiTemp, NSC_TVIS_MARKED) & NSC_TVIS_MARKED;
|
|
|
|
tvi.hItem = htiTemp;
|
|
if (TreeView_GetItem(_hwndTree, &tvi))
|
|
{
|
|
if (tvi.lParam)
|
|
{
|
|
PORDERITEM poi = ((ITEMINFO *)tvi.lParam)->poi;
|
|
if (poi)
|
|
{
|
|
LPITEMIDLIST pidl = ILCombine(pidlParent, poi->pidl);
|
|
if (pidl)
|
|
{
|
|
if (bMarked)
|
|
{
|
|
pii->InsertItem(pidl);
|
|
}
|
|
_InsertMarkedChildren(htiTemp, pidl, pii);
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
HRESULT CNscTree::SelectedItems(IDispatch **ppItems)
|
|
{
|
|
HRESULT hr = CreateFolderItemsFDF(_pidlRoot, ppItems);
|
|
// poke all marked items in ppitems)
|
|
if (SUCCEEDED(hr) && _hwndTree)
|
|
{
|
|
IInsertItem *pii;
|
|
hr = (*ppItems)->QueryInterface(IID_PPV_ARG(IInsertItem, &pii));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (!(_mode & MODE_NORMAL) && (_dwFlags & NSS_MULTISELECT))
|
|
{
|
|
_InsertMarkedChildren(TVI_ROOT, NULL, pii);
|
|
}
|
|
else
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
if (SUCCEEDED(GetSelectedItem(&pidl, 0)) && pidl)
|
|
{
|
|
hr = pii->InsertItem(pidl);
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
pii->Release();
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNscTree::Expand(VARIANT var, int iDepth)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
LPITEMIDLIST pidl;
|
|
|
|
if (var.vt == VT_EMPTY)
|
|
pidl = ILClone(_pidlRoot);
|
|
else
|
|
pidl = VariantToIDList(&var);
|
|
|
|
if (pidl)
|
|
{
|
|
hr = _Expand(pidl, iDepth);
|
|
if (FAILED(hr))
|
|
hr = S_FALSE;
|
|
ILFree(pidl);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNscTree::UnselectAll()
|
|
{
|
|
if (_dwFlags & NSS_MULTISELECT)
|
|
_MarkChildren(TVI_ROOT, FALSE);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
LRESULT CNscTree::OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
// when in label edit mode, don't try to activate the control or you'll get out of label editing,
|
|
// even when you click on the label edit control
|
|
if (!InLabelEdit())
|
|
InPlaceActivate(OLEIVERB_UIACTIVATE);
|
|
return S_OK;
|
|
}
|
|
|
|
LRESULT CNscTree::OnGetIShellBrowser(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
|
|
{
|
|
LRESULT lResult = NULL; // This will be the IShellBrowser *.
|
|
IShellBrowser * psb;
|
|
if (SUCCEEDED(_InternalQueryInterface(IID_PPV_ARG(IShellBrowser, &psb))))
|
|
{
|
|
lResult = (LRESULT) psb;
|
|
psb->Release();
|
|
}
|
|
|
|
bHandled = TRUE;
|
|
return lResult;
|
|
}
|
|
|
|
LRESULT CNscTree::OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
|
|
{
|
|
if (!m_bUIActive)
|
|
CComControlBase::InPlaceActivate(OLEIVERB_UIACTIVATE);
|
|
|
|
if ((HWND)wParam != _hwndTree)
|
|
::SendMessage(_hwndTree, uMsg, wParam, lParam);
|
|
bHandled = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CNscTree::OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
|
|
{
|
|
bHandled = TRUE;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
LRESULT CNscTree::OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
LPNMHDR pnm = (LPNMHDR)lParam;
|
|
if (pnm)
|
|
{
|
|
switch (pnm->code)
|
|
{
|
|
case TVN_SELCHANGEDA:
|
|
case TVN_SELCHANGED:
|
|
{
|
|
if (CSIDL_FAVORITES == _csidl)
|
|
{
|
|
IShellFolder *psf = NULL;
|
|
LPITEMIDLIST pidl = NULL;
|
|
UINT cItems, cVisits;
|
|
WCHAR szTitle[MAX_PATH];
|
|
BOOL fAvailableOffline;
|
|
|
|
szTitle[0] = 0;
|
|
|
|
HRESULT hr = BindToSelectedItemParent(IID_PPV_ARG(IShellFolder, &psf), &pidl);
|
|
if (SUCCEEDED(hr) && (SUCCEEDED(GetSelectedItemName(szTitle, ARRAYSIZE(szTitle)))))
|
|
{
|
|
WCHAR szUrl[INTERNET_MAX_URL_LENGTH], szLastVisited[MAX_PATH]; // szLastVisisted assumed to be MAX_PATH below
|
|
|
|
szUrl[0] = szLastVisited[0] = 0;
|
|
GetEventInfo(psf, pidl, &cItems, szUrl, ARRAYSIZE(szUrl), &cVisits, szLastVisited, &fAvailableOffline);
|
|
|
|
CComBSTR strName(szTitle);
|
|
CComBSTR strUrl(szUrl);
|
|
CComBSTR strDate(szLastVisited);
|
|
|
|
_FireFavoritesSelectionChange(cItems, 0, strName, strUrl, cVisits, strDate, fAvailableOffline);
|
|
}
|
|
else
|
|
_FireFavoritesSelectionChange(0, 0, NULL, NULL, 0, NULL, FALSE);
|
|
|
|
ILFree(pidl);
|
|
ATOMICRELEASE(psf);
|
|
}
|
|
IUnknown_CPContainerInvokeParam(SAFECAST(this, IShellNameSpace *),
|
|
DIID_DShellNameSpaceEvents, DISPID_SELECTIONCHANGE, NULL, 0);
|
|
}
|
|
break;
|
|
|
|
case NM_DBLCLK:
|
|
IUnknown_CPContainerInvokeParam(SAFECAST(this, IShellNameSpace *),
|
|
DIID_DShellNameSpaceEvents, DISPID_DOUBLECLICK, NULL, 0);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
LRESULT lResult;
|
|
HRESULT hr = OnWinEvent(_hwndTree, uMsg, wParam, lParam, &lResult);
|
|
|
|
bHandled = (lResult ? TRUE : FALSE);
|
|
return SUCCEEDED(hr) ? lResult : hr;
|
|
}
|
|
|
|
void CNscTree::_InitHeaderInfo()
|
|
{
|
|
if (!_pidlRoot || !_hdpaColumns || DPA_GetPtrCount(_hdpaColumns) == 0)
|
|
return;
|
|
|
|
IShellFolder *psf;
|
|
if (SUCCEEDED(IEBindToObject(_pidlRoot, &psf)))
|
|
{
|
|
IShellFolder2 *psf2;
|
|
if (SUCCEEDED(psf->QueryInterface(IID_PPV_ARG(IShellFolder2, &psf2))))
|
|
{
|
|
int i;
|
|
SHCOLUMNID scid;
|
|
|
|
for (i=0; SUCCEEDED(psf2->MapColumnToSCID(i, &scid)); i++)
|
|
{
|
|
BOOL bFound = FALSE;
|
|
HEADERINFO *phinfo;
|
|
|
|
for (int iCol=0; iCol < DPA_GetPtrCount(_hdpaColumns); iCol++)
|
|
{
|
|
phinfo = (HEADERINFO *)DPA_GetPtr(_hdpaColumns, iCol);
|
|
if (phinfo && phinfo->iFldrCol == -1 && IsEqualSCID(*(phinfo->pscid), scid))
|
|
{
|
|
bFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bFound)
|
|
{
|
|
DETAILSINFO di;
|
|
|
|
di.fmt = LVCFMT_LEFT;
|
|
di.cxChar = 20;
|
|
di.str.uType = (UINT)-1;
|
|
//di.pidl = NULL;
|
|
|
|
if (SUCCEEDED(psf2->GetDetailsOf(NULL, i, (SHELLDETAILS *)&di.fmt)))
|
|
{
|
|
phinfo->fmt = di.fmt;
|
|
phinfo->iFldrCol = i;
|
|
phinfo->cxChar = di.cxChar;
|
|
StrRetToBuf(&di.str, NULL, phinfo->szName, ARRAYSIZE(phinfo->szName));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i=DPA_GetPtrCount(_hdpaColumns)-1; i >= 0; i--)
|
|
{
|
|
HEADERINFO *phinfo = (HEADERINFO *)DPA_GetPtr(_hdpaColumns, i);
|
|
|
|
if (!phinfo || phinfo->iFldrCol == -1)
|
|
{
|
|
if (phinfo)
|
|
{
|
|
LocalFree(phinfo);
|
|
phinfo = NULL;
|
|
}
|
|
|
|
DPA_DeletePtr(_hdpaColumns, i);
|
|
}
|
|
}
|
|
psf2->Release();
|
|
}
|
|
psf->Release();
|
|
}
|
|
}
|
|
|
|
HWND CNscTree::Create(HWND hWndParent, RECT& rcPos, LPCTSTR pszWindowName, DWORD dwStyle, DWORD dwExStyle, UINT nID)
|
|
{
|
|
CWindowImpl<CNscTree>::Create(hWndParent, rcPos, pszWindowName, dwStyle, dwExStyle, nID);
|
|
|
|
LPITEMIDLIST pidl = _pidlRoot, pidlToFree = NULL;
|
|
|
|
ASSERT(m_spClientSite);
|
|
|
|
SetSite(m_spClientSite); // hook up the site chain
|
|
|
|
_dwTVFlags |= TVS_TRACKSELECT | TVS_INFOTIP | TVS_FULLROWSELECT;
|
|
if (!(_mode & MODE_CUSTOM))
|
|
{
|
|
DWORD dwValue;
|
|
DWORD dwSize = sizeof(dwValue);
|
|
BOOL fDefault = TRUE;
|
|
|
|
SHRegGetUSValue(L"Software\\Microsoft\\Internet Explorer\\Main",
|
|
L"NscSingleExpand", NULL, (LPBYTE)&dwValue, &dwSize, FALSE,
|
|
(void *) &fDefault, sizeof(fDefault));
|
|
|
|
if (dwValue)
|
|
_dwTVFlags |= TVS_SINGLEEXPAND;
|
|
}
|
|
|
|
_hwndTree = NULL;
|
|
CreateTree(m_hWnd, _dwTVFlags, &_hwndTree);
|
|
|
|
if (NULL == pidl)
|
|
{
|
|
SHGetSpecialFolderLocation(NULL, _csidl, &pidl);
|
|
pidlToFree = pidl;
|
|
}
|
|
|
|
if (pidl)
|
|
{
|
|
if (_dwFlags & NSS_HEADER)
|
|
{
|
|
if (!_hdpaColumns || DPA_GetPtrCount(_hdpaColumns) == 0)
|
|
{
|
|
_dwFlags &= ~NSS_HEADER;
|
|
}
|
|
else
|
|
{
|
|
_InitHeaderInfo();
|
|
}
|
|
}
|
|
Initialize(pidl, _grfFlags, _dwFlags);
|
|
ShowWindow(TRUE);
|
|
IUnknown_CPContainerInvokeParam(SAFECAST(this, IShellNameSpace *), DIID_DShellNameSpaceEvents, DISPID_INITIALIZED, NULL, 0);
|
|
ILFree(pidlToFree);
|
|
}
|
|
|
|
return m_hWnd;
|
|
}
|
|
|
|
HRESULT CNscTree::InPlaceActivate(LONG iVerb, const RECT* prcPosRect /*= NULL*/)
|
|
{
|
|
HRESULT hr = CComControl<CNscTree>::InPlaceActivate(iVerb, prcPosRect);
|
|
if (::GetFocus() != _hwndTree)
|
|
::SetFocus(_hwndTree);
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CNscTree::GetWindow(HWND* phwnd)
|
|
{
|
|
return IOleInPlaceActiveObjectImpl<CNscTree>::GetWindow(phwnd);
|
|
}
|
|
|
|
STDMETHODIMP CNscTree::TranslateAccelerator(MSG *pMsg)
|
|
{
|
|
// label editing edit control is taking the keystrokes, TAing them will just duplicate them
|
|
if (InLabelEdit())
|
|
return S_FALSE;
|
|
|
|
// hack so that the escape can get out to the document, because TA won't do it
|
|
// WM_KEYDOWN is because some keyup's come through that need to not close the dialog
|
|
if ((pMsg->wParam == VK_ESCAPE) && (pMsg->message == WM_KEYDOWN))
|
|
{
|
|
_FireFavoritesSelectionChange(-1, 0, NULL, NULL, 0, NULL, FALSE);
|
|
return S_FALSE;
|
|
}
|
|
|
|
//except for tabs and sys keys, let nsctree take all the keystrokes
|
|
if ((pMsg->wParam != VK_TAB) && (pMsg->message != WM_SYSCHAR) && (pMsg->message != WM_SYSKEYDOWN) && (pMsg->message != WM_SYSKEYUP))
|
|
{
|
|
// TreeView will return TRUE if it processes the key, so we return S_OK to indicate
|
|
// the keystroke was used and prevent further processing
|
|
return ::SendMessage(pMsg->hwnd, TVM_TRANSLATEACCELERATOR, 0, (LPARAM)pMsg) ? S_OK : S_FALSE;
|
|
}
|
|
else
|
|
{
|
|
CComQIPtr<IOleControlSite, &IID_IOleControlSite>spCtrlSite(m_spClientSite);
|
|
if (spCtrlSite)
|
|
return spCtrlSite->TranslateAccelerator(pMsg,0);
|
|
}
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
HRESULT CNscTree::SetObjectRects(LPCRECT prcPos, LPCRECT prcClip)
|
|
{
|
|
HRESULT hr = IOleInPlaceObjectWindowlessImpl<CNscTree>::SetObjectRects(prcPos, prcClip);
|
|
LONG lTop = 0;
|
|
|
|
if (_hwndHdr)
|
|
{
|
|
RECT rc;
|
|
|
|
::GetWindowRect(_hwndHdr, &rc);
|
|
lTop = RECTHEIGHT(rc);
|
|
::SetWindowPos(_hwndHdr, NULL, 0, 0, RECTWIDTH(*prcPos), lTop, SWP_NOZORDER | SWP_NOACTIVATE);
|
|
}
|
|
|
|
if (_hwndTree)
|
|
{
|
|
::SetWindowPos(_hwndTree, NULL, 0, lTop, RECTWIDTH(*prcPos), RECTHEIGHT(*prcPos)-lTop,
|
|
SWP_NOZORDER | SWP_NOACTIVATE);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CNscTree::SetClientSite(IOleClientSite *pClientSite)
|
|
{
|
|
SetSite(pClientSite);
|
|
return IOleObjectImpl<CNscTree>::SetClientSite(pClientSite);
|
|
}
|
|
|
|
HRESULT CNscTree::OnDraw(ATL_DRAWINFO& di)
|
|
{
|
|
//should only get called before CNscTree is initialized
|
|
return S_OK;
|
|
}
|
|
|
|
LRESULT CNscTree::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
|
|
{
|
|
bHandled = FALSE; //let default handler also do it's work
|
|
_OnWindowCleanup();
|
|
return 0;
|
|
}
|
|
|
|
BOOL IsChannelFolder(LPCWSTR pwzPath, LPWSTR pwzChannelURL);
|
|
|
|
HRESULT CNscTree::GetEventInfo(IShellFolder *psf, LPCITEMIDLIST pidl,
|
|
UINT *pcItems, LPWSTR pszUrl, DWORD cchUrl,
|
|
UINT *pcVisits, LPWSTR pszLastVisited, BOOL *pfAvailableOffline)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
TCHAR szPath[MAX_PATH];
|
|
TCHAR szUrl[MAX_URL_STRING];
|
|
|
|
szPath[0] = szUrl[0] = 0;
|
|
|
|
*pcItems = 1;
|
|
|
|
ULONG ulAttr = SFGAO_FOLDER; // make sure item is actually a folder
|
|
hr = GetPathForItem(psf, pidl, szPath, &ulAttr);
|
|
if (SUCCEEDED(hr) && (ulAttr & SFGAO_FOLDER))
|
|
{
|
|
pszLastVisited[0] = 0;
|
|
|
|
StrCpyN(pszUrl, szPath, cchUrl);
|
|
|
|
WIN32_FIND_DATA fd;
|
|
HANDLE hfind = FindFirstFile(szPath, &fd);
|
|
if (hfind != INVALID_HANDLE_VALUE)
|
|
{
|
|
SHFormatDateTime(&(fd.ftLastWriteTime), NULL, pszLastVisited, MAX_PATH);
|
|
FindClose(hfind);
|
|
}
|
|
|
|
*pcVisits = -1;
|
|
*pfAvailableOffline = 0;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
// GetPathForItem fails on channel folders, but the following GetDisplayNameOf
|
|
// succeeds.
|
|
DisplayNameOf(psf, pidl, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath));
|
|
}
|
|
|
|
hr = GetNavTargetName(psf, pidl, szUrl, ARRAYSIZE(szUrl));
|
|
|
|
// IsChannelFolder will fixup szUrl if it's a channel
|
|
IsChannelFolder(szPath, szUrl);
|
|
|
|
if (szUrl[0])
|
|
{
|
|
SHTCharToUnicode(szUrl, pszUrl, cchUrl);
|
|
|
|
//
|
|
// Get the cache info for this item. Note that we use GetUrlCacheEntryInfoEx instead
|
|
// of GetUrlCacheEntryInfo because it follows any redirects that occured. This wacky
|
|
// api uses a variable length buffer, so we have to guess the size and retry if the
|
|
// call fails.
|
|
//
|
|
BOOL fInCache = FALSE;
|
|
TCHAR szBuf[512];
|
|
LPINTERNET_CACHE_ENTRY_INFO pCE = (LPINTERNET_CACHE_ENTRY_INFO)szBuf;
|
|
DWORD dwEntrySize = sizeof(szBuf);
|
|
|
|
fInCache = GetUrlCacheEntryInfoEx(szUrl, pCE, &dwEntrySize, NULL, NULL, NULL, 0);
|
|
if (!fInCache)
|
|
{
|
|
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
// We guessed too small for the buffer so allocate the correct size & retry
|
|
pCE = (LPINTERNET_CACHE_ENTRY_INFO)LocalAlloc(LPTR, dwEntrySize);
|
|
if (pCE)
|
|
{
|
|
fInCache = GetUrlCacheEntryInfoEx(szUrl, pCE, &dwEntrySize, NULL, NULL, NULL, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
*pfAvailableOffline = IsSubscribed(szUrl);
|
|
|
|
if (fInCache)
|
|
{
|
|
*pcVisits = pCE->dwHitRate;
|
|
|
|
SHFormatDateTime(&(pCE->LastAccessTime), NULL, pszLastVisited, MAX_PATH);
|
|
}
|
|
else
|
|
{
|
|
*pcVisits = 0;
|
|
pszLastVisited[0] = 0;
|
|
}
|
|
|
|
if ((TCHAR*)pCE != szBuf)
|
|
{
|
|
LocalFree(pCE);
|
|
pCE = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*pcVisits = 0;
|
|
SHTCharToUnicode(szPath, pszUrl, cchUrl);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// [id(DISPID_FAVSELECTIONCHANGE)] void FavoritesSelectionChange([in] long cItems, [in] long hItem, [in] BSTR strName,
|
|
// [in] BSTR strUrl, [in] long cVisits, [in] BSTR strDate,
|
|
// [in] BOOL fAvailableOffline);
|
|
void CNscTree::_FireFavoritesSelectionChange(
|
|
long cItems, long hItem, BSTR strName, BSTR strUrl,
|
|
long cVisits, BSTR strDate, long fAvailableOffline)
|
|
{
|
|
VARIANTARG args[7];
|
|
|
|
IUnknown_CPContainerInvokeParam(SAFECAST(this, IShellNameSpace *),
|
|
DIID_DShellNameSpaceEvents, DISPID_FAVSELECTIONCHANGE,
|
|
args, ARRAYSIZE(args),
|
|
VT_I4, cItems,
|
|
VT_I4, hItem,
|
|
VT_BSTR, strName,
|
|
VT_BSTR, strUrl,
|
|
VT_I4, cVisits,
|
|
VT_BSTR, strDate,
|
|
VT_I4, fAvailableOffline);
|
|
}
|
|
|