WindowsXP-SP1/shell/shdocvw/nsc.cpp

7974 lines
259 KiB
C++

#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);
#if 0
if ((_dwFlags & NSS_HEADER) && _hwndHdr)
{
RECT rc;
GetWindowRect(_hwndHdr, &rc);
lTop = RECTHEIGHT(rc);
}
#endif
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;
SHFILEINFO sfi;
HIMAGELIST himl = (HIMAGELIST)SHGetFileInfo(TEXT(DEFAULT_PATHSTR), 0, &sfi,
sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
TreeView_SetImageList(_hwndTree, himl, TVSIL_NORMAL);
_DtRegister();
//failure ignored intentionally
THR(CoCreateInstance(CLSID_ShellTaskScheduler, NULL, CLSCTX_INPROC,
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)
{
HRESULT hr = S_OK;
#ifdef UNUSED
ASSERT(_psfCache);
ASSERT(_pidlRoot);
int cAdded = 0;
IStream* pstm = NULL;
_fWeChangedOrder = TRUE;
if (FAILED(hr = _PopulateOrderList(TVI_ROOT)))
return hr;
pstm = OpenPidlOrderStream((LPCITEMIDLIST)CSIDL_FAVORITES, _pidlRoot, REG_SUBKEY_FAVORITESA, STGM_CREATE | STGM_WRITE);
_CacheShellFolder(TVI_ROOT);
if (pstm == NULL || _psfCache == NULL)
{
ATOMICRELEASE(pstm);
_FreeOrderList(TVI_ROOT);
return S_OK;
}
_fOrdered = FALSE;
ORDERINFO oinfo;
oinfo.psf = _psfCache;
(oinfo.psf)->AddRef();
oinfo.dwSortBy = OI_SORTBYNAME;
DPA_Sort(_hdpaOrd, OrderItem_Compare,(LPARAM)&oinfo);
ATOMICRELEASE(oinfo.psf);
OrderList_Reorder(_hdpaOrd);
OrderList_SaveToStream(pstm, _hdpaOrd, _psfCache);
ATOMICRELEASE(pstm);
_FreeOrderList(TVI_ROOT);
Refresh();
_fWeChangedOrder = FALSE;
#endif
return hr;
}
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;
}
// filter function... let clients filter what gets added here
BOOL CNscTree::_ShouldAdd(LPCITEMIDLIST pidl)
{
// send notify up to parent to let them filter
return TRUE;
}
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
ULONG ulAttr = SFGAO_FOLDER;
LPITEMIDLIST pidlTarget = NULL;
if (SUCCEEDED(_psfCache->GetAttributesOf(1, &pidlItem, &ulAttr)) &&
(ulAttr & SFGAO_FOLDER) &&
!(SUCCEEDED(SHGetNavigateTarget(_psfCache, pidlItem, &pidlTarget, &ulAttr)) &&
((_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 && psf->CompareIDs(0, poi->pidl, pidl) == 0;
}
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 (!_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;
}
}
// Update the + next to the parent folder. Note that History and Favorites
// set ALL of their items to be Folder items, so this is not needed for
// favorites.
// do this only on downlevel as comctl v6 takes care of pluses
if (_mode == MODE_NORMAL && !IsOS(OS_WHISTLERORGREATER))
{
LPCITEMIDLIST pidl = _CacheParentShellFolder(htiParent, NULL);
if (pidl && !ILIsEmpty(pidl))
{
DWORD dwAttrib = SFGAO_HASSUBFOLDER;
if (SUCCEEDED(_psfCache->GetAttributesOf(1, &pidl, &dwAttrib)) &&
!(dwAttrib & SFGAO_HASSUBFOLDER))
{
TV_ITEM tvi;
tvi.mask = TVIF_CHILDREN | TVIF_HANDLE;
tvi.hItem = htiParent;
tvi.cChildren = 0;
TreeView_SetItem(_hwndTree, &tvi);
}
}
}
_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::_OnSHNotifyUpdateDir: 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 (_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 ((short)SCODE_CODE(_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)
{
HRESULT hr;
if (g_fRunningOnNT)
{
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;
hr = pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)(&ici));
}
else
{
CMINVOKECOMMANDINFO ici = {0};
ici.cbSize = sizeof(ici);
ici.hwnd = hwndParent;
ici.nShow = SW_NORMAL;
ici.lpVerb = CMDSTR_NEWFOLDERA;
ici.fMask = CMIC_MASK_FLAG_NO_UI;
// Win95 doesn't work with CMIC_MASK_UNICODE & CMDSTR_NEWFOLDERW
hr = pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)(&ici));
}
return hr;
}
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;
if (GetUIVersion() < 5)
{
IShellView *psv;
hr = _psfCache->CreateViewObject(_hwndTree, IID_PPV_ARG(IShellView, &psv));
if (SUCCEEDED(hr))
{
hr = psv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARG(IContextMenu, &pcm));
if (SUCCEEDED(hr))
{
IUnknown_SetSite(pcm, psv);
}
psv->Release();
}
}
else
{
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.
EVAL(SUCCEEDED(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;
if (IsOS(OS_WHISTLERORGREATER))
dwFlags |= 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)
{
WCHAR psz[MAX_PATH];
int iTemp = 0;
DWORD cbSize = ARRAYSIZE(psz);
if (SUCCEEDED(AssocQueryString(0, ASSOCSTR_DEFAULTICON, TEXT("InternetShortcut"), NULL, psz, &cbSize)))
iTemp = PathParseIconLocation(psz);
_iDefaultFavoriteIcon = Shell_GetCachedImageIndex(psz, iTemp, 0);
cbSize = ARRAYSIZE(psz);
if (SUCCEEDED(AssocQueryString(0, ASSOCSTR_DEFAULTICON, TEXT("Folder"), NULL, psz, &cbSize)))
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 (HRESULT_CODE(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
{
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);
}
}
}
else if (MODE_NORMAL == _mode && !IsOS(OS_WHISTLERORGREATER)) //see comment in OnItemExpanding
{
// we didn't add anything, we better remove +
TreeView_SetChildren(_hwndTree, hti, NSC_CHILDREN_REMOVE);
}
// 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))
{
if (IsOS(OS_WHISTLERORGREATER))
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
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;
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)
{
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)
{
UINT idCmd;
_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.
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);
}
#ifdef UNIX
// IEUNIX (APPCOMPAT): If we lose activation in the middle of rename operation
// and we have invalid name in the edit box, rename operation will popup
// a message box which causes IE on unix to go into infinite focus changing
// loop. To workaround this problem, we are considering the operation as
// cancelled and we copy the original value into the buffer.
BOOL fHasActivation = FALSE;
if (GetActiveWindow() && IsChild(GetActiveWindow(), _hwndTree))
fHasActivation = TRUE;
if (!fHasActivation)
{
TV_ITEM tvi;
tvi.mask = TVIF_TEXT;
tvi.hItem = (HTREEITEM)ptvdi->item.hItem;
tvi.pszText = ptvdi->item.pszText;
tvi.cchTextMax = ptvdi->item.cchTextMax;
TreeView_GetItem(_hwndTree, &tvi);
}
#endif
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;
#ifdef UNIX
SHChangeNotifyHandleEvents();
#endif
}
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);
}
#ifdef DEBUG
{
HTREEITEM hti = TreeView_GetSelection(_hwndTree);
LPITEMIDLIST pidl = _GetFullIDList(hti);
if (pidl)
{
TCHAR sz[MAX_PATH];
SHGetNameAndFlags(pidl, SHGDN_NORMAL, sz, SIZECHARS(sz), NULL);
TraceMsg(TF_NSC, "NSCBand: Selecting %s", sz);
//
// On NT4 and W95 shell this call will miss the history
// shell extension junction point. It will then deref into
// history pidls as if they were shell pidls. On short
// history pidls this would fault on a debug version of the OS.
//
// SHGetNameAndFlags(pidl, SHGDN_FORPARSING | SHGDN_FORADDRESSBAR, sz, SIZECHARS(sz), NULL);
//TraceMsg(TF_NSC, "displayname = %s", sz);
ILFree(pidl);
}
}
#endif // DEBUG
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)
{
UINT uFlags = TVGN_CARET;
if (IsOS(OS_WHISTLERORGREATER))
uFlags |= TVSI_NOSINGLEEXPAND; // new for v6
else if (_fSingleExpand)
_fIgnoreNextItemExpanding = TRUE;
_fInExpand = TRUE; // Treeview will force expand the parents, make sure we know it's not the user clicking on items
TreeView_Select(hwnd, hti, uFlags);
_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
{
EVAL(SUCCEEDED(_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)
{
LPITEMIDLIST pidlSelected;
if (SUCCEEDED(GetSelectedItem(&pidlSelected, 0)))
{
if (ILIsEqual(pidl, pidlSelected) || ILIsParent(pidl, pidlSelected, FALSE))
{
IShellFolder *psf;
if (SUCCEEDED(SHBindToIDListParent(pidl, IID_PPV_ARG(IShellFolder, &psf), NULL)))
{
BOOL fSelected = FALSE;
for (HTREEITEM htiSelect = TreeView_GetNextSibling(_hwndTree, hti); htiSelect; htiSelect = TreeView_GetNextSibling(_hwndTree, htiSelect))
{
PORDERITEM poi = _GetTreeOrderItem(htiSelect);
if (poi)
{
if (!SHGetAttributes(psf, poi->pidl, SFGAO_REMOVABLE))
{
_SelectNoExpand(_hwndTree, htiSelect);
fSelected = TRUE;
break;
}
}
}
if (!fSelected)
{
_SelectNoExpand(_hwndTree, TreeView_GetParent(_hwndTree, hti));
}
psf->Release();
}
}
ILFree(pidlSelected);
}
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 (EVAL(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 (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 = MAX_URL_STRING;
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
{
HTREEITEM hti;
dwFlags |= DISUBITEM;
if (((HTREEITEM)pnm->dwItemSpec) == _htiActiveBorder)
dwFlags |= DISUBFIRST;
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)
{
if (NULL == _pcm2Selection)
{
return E_FAIL;
}
else
{
// HACK alert. Work around bug in win95 user code for WM_DRAWITEM that sign extends
// itemID
if (!g_fRunningOnNT && WM_DRAWITEM == uMsg)
{
LPDRAWITEMSTRUCT lpDraw = (LPDRAWITEMSTRUCT)lParam;
if (0xFFFF0000 == (lpDraw->itemID & 0xFFFF0000) &&
(lpDraw->itemID & 0xFFFF) >= FCIDM_BROWSERFIRST &&
(lpDraw->itemID & 0xFFFF) <= FCIDM_BROWSERLAST)
{
lpDraw->itemID = lpDraw->itemID & 0xFFFF;
}
}
return _pcm2Selection->HandleMenuMsg(uMsg,wParam,lParam);
}
}
IContextMenu *CNscTree::CSelectionContextMenu::_QuerySelection()
{
CNscTree* pnsc = IToClass(CNscTree, _scm, this);
ATOMICRELEASE(_pcmSelection);
ATOMICRELEASE(_pcm2Selection);
pnsc->_QuerySelection(&_pcmSelection, NULL);
if (_pcmSelection)
{
_pcmSelection->QueryInterface(IID_PPV_ARG(IContextMenu2, &_pcm2Selection));
AddRef();
return SAFECAST(this, IContextMenu*);
}
return NULL;
}
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);
}
void CNscTree::_DrawActiveBorder(HDC hdc, LPRECT prc)
{
MoveToEx(hdc, prc->left, prc->top, NULL);
LineTo(hdc, prc->right, prc->bottom);
}
#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 0
//focus is currently shown by drawing the selection with a different color
// (in default scheme, it's blue when has focus, gray when not)
if (dwFlags & DIFOCUSRECT)
{
rc = *prc;
InflateRect(&rc, -1, -1);
DrawFocusRect(hdc, &rc);
}
#endif
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;
TCHAR szPath[MAX_PATH];
TV_ITEM tvi;
tvi.hItem = TreeView_GetSelection(_hwndTree);
if (tvi.hItem != NULL)
{
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);
}
BOOL bSubscribed;
// 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).
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;
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::get_ReadyState(READYSTATE *plReady)
{
*plReady = _lReadyState;
return S_OK;
}
void CNscTree::_ReadyStateChange(READYSTATE lReady)
{
if (_lReadyState < lReady)
{
_lReadyState = lReady;
//post beta 1 item (need trident v3)
//IUnknown_CPContainerOnChanged(SAFECAST(this, IShellNameSpace *), DISPID_READYSTATE);
}
}
*/
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], szUrl[INTERNET_MAX_URL_LENGTH], szLastVisited[MAX_PATH];
BOOL fAvailableOffline;
szTitle[0] = szUrl[0] = szLastVisited[0] = 0;
HRESULT hr = BindToSelectedItemParent(IID_PPV_ARG(IShellFolder, &psf), &pidl);
if (SUCCEEDED(hr) && (SUCCEEDED(GetSelectedItemName(szTitle, ARRAYSIZE(szTitle)))))
{
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.
STRRET str;
if (SUCCEEDED(psf->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &str)))
StrRetToBuf(&str, pidl, 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);
}