414 lines
11 KiB
C++
414 lines
11 KiB
C++
// ShellTreeCtrl.cpp : implementation file
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2000-2001 by Paolo Messina
|
|
// (http://www.geocities.com/ppescher - ppescher@yahoo.com)
|
|
//
|
|
// The contents of this file are subject to the Artistic License (the "License").
|
|
// You may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at:
|
|
// http://www.opensource.org/licenses/artistic-license.html
|
|
//
|
|
// If you find this code useful, credits would be nice!
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "stdafx.h"
|
|
#include "ShellTreeCtrl.h"
|
|
#include ".\shelltreectrl.h"
|
|
|
|
#ifdef _DEBUG
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CShellTreeCtrl
|
|
|
|
CShellTreeCtrl::CShellTreeCtrl()
|
|
{
|
|
m_nCallbackMask = 0;
|
|
}
|
|
|
|
CShellTreeCtrl::~CShellTreeCtrl()
|
|
{
|
|
}
|
|
|
|
|
|
BEGIN_MESSAGE_MAP(CShellTreeCtrl, CWaitingTreeCtrl)
|
|
//{{AFX_MSG_MAP(CShellTreeCtrl)
|
|
ON_NOTIFY_REFLECT(TVN_DELETEITEM, OnDeleteItem)
|
|
ON_NOTIFY_REFLECT(TVN_GETDISPINFO, OnGetDispInfo)
|
|
//}}AFX_MSG_MAP
|
|
// ON_NOTIFY_REFLECT(TVN_SELCHANGED, OnTvnSelchanged)
|
|
// ON_NOTIFY_REFLECT(TVN_ITEMEXPANDED, OnTvnItemexpanded)
|
|
END_MESSAGE_MAP()
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CShellTreeCtrl message handlers
|
|
|
|
BOOL CShellTreeCtrl::PopulateItem(HTREEITEM hParent)
|
|
{
|
|
if (hParent == TVI_ROOT)
|
|
{
|
|
// not handled yet, do nothing in Release builds
|
|
ASSERT(FALSE);
|
|
return TRUE;
|
|
}
|
|
|
|
TVITEMDATA* pData = (TVITEMDATA*)GetItemData(hParent);
|
|
if (pData == NULL)
|
|
return TRUE; // invalid shell item, ignore it silently
|
|
|
|
// get parent pidl
|
|
ASSERT(pData->IsValid());
|
|
CShellPidl& pidlParent = pData->pidlAbs;
|
|
|
|
if (!EnumFolderItems(hParent, pidlParent, pData->nFlags))
|
|
return TRUE; // failed, won't try anymore!
|
|
|
|
// TODO: change this method!!
|
|
|
|
// do not check for children if parent is a removable media
|
|
// (just try: if it's a filesystem object, it has a path)
|
|
TCHAR path[MAX_PATH];
|
|
if (SHGetPathFromIDList(pidlParent, path))
|
|
{
|
|
path[3] = 0;
|
|
UINT type = GetDriveType(path);
|
|
if (type != DRIVE_FIXED)
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CShellTreeCtrl::PreSubclassWindow()
|
|
{
|
|
InitializeControl();
|
|
|
|
CWaitingTreeCtrl::PreSubclassWindow();
|
|
}
|
|
|
|
void CShellTreeCtrl::InitializeControl()
|
|
{
|
|
// Attach to the system image list
|
|
CShellPidl pidl((UINT)CSIDL_DESKTOP, m_hWnd);
|
|
|
|
SHFILEINFO sfi;
|
|
ZeroMemory(&sfi, sizeof(SHFILEINFO));
|
|
HIMAGELIST hSysImageList = (HIMAGELIST) SHGetFileInfo((LPCTSTR)(LPCITEMIDLIST)pidl,
|
|
0, &sfi, sizeof(SHFILEINFO), SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
|
|
|
|
//TreeView_SetImageList(m_hWnd, hSysImageList, TVSIL_NORMAL);
|
|
// postpone imagelist attaching
|
|
// (seems it doesn't like a sendmessage when dynamically created
|
|
// maybe because it has not received the WM_CREATE message yet?)
|
|
PostMessage(TVM_SETIMAGELIST, TVSIL_NORMAL, (LPARAM)hSysImageList);
|
|
}
|
|
|
|
void CShellTreeCtrl::AddRootFolderContent(LPCITEMIDLIST pidlFolder, UINT nFlags)
|
|
{
|
|
if (pidlFolder == NULL)
|
|
{
|
|
CShellPidl pidl((UINT)CSIDL_DESKTOP, m_hWnd);
|
|
InsertSubItem(TVI_ROOT, m_pDesktopFolder, NULL, pidl, nFlags);
|
|
//HTREEITEM root = GetRootItem();
|
|
//Expand(root, TVE_EXPAND);
|
|
//AfxMessageBox(GetItemText(GetChildItem(root)));
|
|
//PopulateItem(GetRootItem());
|
|
//Expand(GetRootItem(), 1);
|
|
return;
|
|
}
|
|
|
|
SetRedraw(FALSE);
|
|
EnumFolderItems(TVI_ROOT, pidlFolder, nFlags);
|
|
SetRedraw(TRUE);
|
|
}
|
|
|
|
|
|
int CALLBACK CShellTreeCtrl::CompareFunc(LPARAM lParam1,
|
|
LPARAM lParam2, LPARAM /*lParamSort*/)
|
|
{
|
|
TVITEMDATA* pData1 = (TVITEMDATA*)lParam1;
|
|
TVITEMDATA* pData2 = (TVITEMDATA*)lParam2;
|
|
ASSERT(pData1->IsValid() && pData2->IsValid());
|
|
|
|
// TODO: parent folders should be checked some day
|
|
SShellFolderPtr pParentFolder = pData2->pParentFolder;
|
|
|
|
HRESULT hr = pParentFolder->CompareIDs(0,
|
|
pData1->pidlAbs.GetLastChild(),
|
|
pData2->pidlAbs.GetLastChild() );
|
|
if (FAILED(hr))
|
|
return 0; // error, don't sort
|
|
|
|
short ret = (short)HRESULT_CODE(hr);
|
|
if (ret < 0)
|
|
return -1;
|
|
if (ret > 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
void CShellTreeCtrl::FillItem(TVITEM& item)
|
|
{
|
|
DWORD dwAttributes;
|
|
|
|
// get item data
|
|
TVITEMDATA* pData = (TVITEMDATA*)item.lParam;
|
|
ASSERT(pData->IsValid());
|
|
|
|
// get a relative pidl
|
|
LPCITEMIDLIST pidlRel = pData->pidlAbs.GetLastChild();
|
|
|
|
if (item.mask & TVIF_TEXT)
|
|
{
|
|
// get display name
|
|
CString sName;
|
|
CShellString str;
|
|
|
|
if (pData->nFlags & STCF_SHOWPATH)
|
|
{
|
|
// use an absolute or relative path, if possible
|
|
sName = pData->pidlAbs.GetPath();
|
|
if (!sName.IsEmpty() && !(pData->nFlags & STCF_SHOWFULLNAME))
|
|
sName = sName.Right(sName.ReverseFind(_T('\\')));
|
|
}
|
|
if (sName.IsEmpty())
|
|
{
|
|
// use a global or contextual displayname
|
|
DWORD uDisplayFlags = SHGDN_INFOLDER;
|
|
if (pData->nFlags & STCF_SHOWFULLNAME)
|
|
uDisplayFlags = SHGDN_NORMAL;
|
|
|
|
// pData->pParentFolder->GetDisplayNameOf(pidlRel, uDisplayFlags
|
|
// | SHGDN_INCLUDE_NONFILESYS, str.GetPointer(pidlRel));
|
|
pData->pParentFolder->GetDisplayNameOf(pidlRel, uDisplayFlags, str.GetPointer(pidlRel));
|
|
sName = str; // copy to string
|
|
}
|
|
// set item text
|
|
lstrcpyn(item.pszText, (LPCTSTR)sName, item.cchTextMax);
|
|
}
|
|
|
|
if (item.mask & (TVIF_IMAGE | TVIF_SELECTEDIMAGE))
|
|
{
|
|
// get some attributes
|
|
dwAttributes = SFGAO_FOLDER | SFGAO_LINK | SFGAO_SHARE | SFGAO_GHOSTED;
|
|
pData->pParentFolder->GetAttributesOf(1, &pidlRel, &dwAttributes);
|
|
|
|
// set correct icon
|
|
if (dwAttributes & SFGAO_GHOSTED)
|
|
{
|
|
item.mask |= LVIF_STATE;
|
|
item.stateMask |= LVIS_CUT;
|
|
item.state |= LVIS_CUT;
|
|
}
|
|
if (dwAttributes & SFGAO_SHARE)
|
|
{
|
|
item.mask |= LVIF_STATE;
|
|
item.state &= ~LVIS_OVERLAYMASK;
|
|
item.state |= INDEXTOOVERLAYMASK(1);
|
|
item.stateMask |= LVIS_OVERLAYMASK;
|
|
}
|
|
else if (dwAttributes & SFGAO_LINK)
|
|
{
|
|
item.mask |= LVIF_STATE;
|
|
item.state &= ~LVIS_OVERLAYMASK;
|
|
item.state |= INDEXTOOVERLAYMASK(2);
|
|
item.stateMask |= LVIS_OVERLAYMASK;
|
|
}
|
|
if (item.mask & TVIF_IMAGE)
|
|
{
|
|
item.iImage = pData->pidlAbs.GetIconIndex(SHGFI_SMALLICON);
|
|
item.iSelectedImage = item.iImage;
|
|
}
|
|
if ((item.mask & TVIF_SELECTEDIMAGE)
|
|
&& (dwAttributes & SFGAO_FOLDER))
|
|
{
|
|
item.iSelectedImage = pData->pidlAbs.GetIconIndex(SHGFI_SMALLICON
|
|
|SHGFI_OPENICON);
|
|
}
|
|
}
|
|
|
|
if (item.mask & TVIF_CHILDREN)
|
|
{
|
|
// get some attributes
|
|
dwAttributes = SFGAO_FOLDER;
|
|
pData->pParentFolder->GetAttributesOf(1, &pidlRel, &dwAttributes);
|
|
|
|
// get children
|
|
item.cChildren = 0;
|
|
if (dwAttributes & SFGAO_FOLDER)
|
|
{
|
|
if (pData->nFlags & STCF_INCLUDEFILES)
|
|
item.cChildren = 1;
|
|
else if (dwAttributes & SFGAO_REMOVABLE)
|
|
item.cChildren = 1;
|
|
else
|
|
{
|
|
dwAttributes = SFGAO_HASSUBFOLDER;
|
|
pData->pParentFolder->GetAttributesOf(1, &pidlRel, &dwAttributes);
|
|
|
|
item.cChildren = (dwAttributes & SFGAO_HASSUBFOLDER) ? 1 : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void CShellTreeCtrl::OnDeleteItem(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
TVITEM& item = ((LPNMTREEVIEW)pNMHDR)->itemOld;
|
|
|
|
// free item data, ignore invalid shell items
|
|
if (item.lParam != 0)
|
|
delete (TVITEMDATA*)item.lParam;
|
|
|
|
*pResult = 0;
|
|
}
|
|
|
|
void CShellTreeCtrl::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
TVITEM& item = ((LPNMTVDISPINFO)pNMHDR)->item;
|
|
|
|
// use the provided buffer for text
|
|
FillItem(item);
|
|
|
|
*pResult = 0;
|
|
}
|
|
|
|
CShellPidl CShellTreeCtrl::GetItemIDList(HTREEITEM hItem)
|
|
{
|
|
TVITEMDATA* pData = (TVITEMDATA*)GetItemData(hItem);
|
|
if (pData != NULL)
|
|
{
|
|
ASSERT(pData->IsValid());
|
|
return pData->pidlAbs;
|
|
}
|
|
return CShellPidl(); // invalid pidl
|
|
}
|
|
|
|
void CShellTreeCtrl::InsertSubItem(HTREEITEM hParent, LPSHELLFOLDER pParentFolder, LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidl, UINT nFlags)
|
|
{
|
|
TVINSERTSTRUCT tvis;
|
|
ZeroMemory(&tvis, sizeof(TVINSERTSTRUCT));
|
|
tvis.hParent = hParent;
|
|
tvis.hInsertAfter = TVI_LAST;
|
|
|
|
// provide a buffer for the item text
|
|
TCHAR szText[MAX_PATH];
|
|
tvis.item.pszText = szText;
|
|
tvis.item.cchTextMax = MAX_PATH;
|
|
|
|
// used fields
|
|
const UINT nTVIFlags = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE
|
|
| TVIF_CHILDREN | TVIF_PARAM;
|
|
|
|
// prepare item data
|
|
TVITEMDATA* pData = new TVITEMDATA;
|
|
pData->pidlAbs.Combine(pidlParent, pidl);
|
|
pData->pParentFolder = pParentFolder;
|
|
pData->nFlags = nFlags;
|
|
|
|
// set item data
|
|
ASSERT(pData->IsValid());
|
|
tvis.item.lParam = (LPARAM)pData;
|
|
|
|
// fill with pidl, text, icons and children - handle callbacks
|
|
tvis.item.mask = nTVIFlags & ~m_nCallbackMask;
|
|
FillItem(tvis.item);
|
|
|
|
if (m_nCallbackMask & TVIF_IMAGE)
|
|
tvis.item.iImage = I_IMAGECALLBACK;
|
|
if (m_nCallbackMask & TVIF_SELECTEDIMAGE)
|
|
tvis.item.iSelectedImage = I_IMAGECALLBACK;
|
|
if (m_nCallbackMask & TVIF_TEXT)
|
|
tvis.item.pszText = LPSTR_TEXTCALLBACK;
|
|
if (m_nCallbackMask & TVIF_CHILDREN)
|
|
tvis.item.cChildren = I_CHILDRENCALLBACK;
|
|
tvis.item.mask |= nTVIFlags;
|
|
|
|
// then insert new item
|
|
InsertItem(&tvis);
|
|
}
|
|
|
|
void CShellTreeCtrl::AddRootItem(LPCITEMIDLIST pidlRoot, UINT nFlags)
|
|
{
|
|
// not needed if pParentFolder is an argument
|
|
CShellPidl pidlParent;
|
|
pidlParent.CloneLastParent(pidlRoot);
|
|
SShellFolderPtr pParentFolder(m_pDesktopFolder, pidlParent);
|
|
|
|
InsertSubItem(TVI_ROOT, pParentFolder, NULL, pidlRoot, nFlags);
|
|
}
|
|
|
|
BOOL CShellTreeCtrl::EnumFolderItems(HTREEITEM hParent, LPCITEMIDLIST pidlParent, UINT nFlags)
|
|
{
|
|
// get parent shell folder
|
|
SShellFolderPtr pParentFolder(m_pDesktopFolder, pidlParent);
|
|
|
|
// not a valid folder object
|
|
if (!pParentFolder.IsValid())
|
|
return FALSE; // failed!
|
|
|
|
// enum child pidls
|
|
SEnumIDListPtr pEnumIDList(pParentFolder, SHCONTF_FOLDERS
|
|
| ((nFlags & STCF_INCLUDEFILES) ? SHCONTF_NONFOLDERS : 0)
|
|
| ((nFlags & STCF_INCLUDEHIDDEN) ? SHCONTF_INCLUDEHIDDEN : 0), m_hWnd);
|
|
|
|
if (pEnumIDList.IsValid())
|
|
{
|
|
SetPopulationCount(0);
|
|
|
|
CShellPidl pidl;
|
|
while (NOERROR == pEnumIDList->Next(1, pidl.GetPointer(), NULL))
|
|
{
|
|
// add child item, inherit some flags (inclusion)
|
|
InsertSubItem(hParent, pParentFolder, pidlParent, pidl,
|
|
nFlags & STCF_INCLUDEMASK);
|
|
|
|
// notify progress
|
|
IncreasePopulation();
|
|
}
|
|
}
|
|
|
|
if (GetPopulationCount() > 0)
|
|
{
|
|
// sort items
|
|
TVSORTCB tvscb;
|
|
tvscb.hParent = hParent;
|
|
tvscb.lpfnCompare = CompareFunc;
|
|
// tvscb.lParam = 0; // not meaningful yet
|
|
SortChildrenCB(&tvscb);
|
|
}
|
|
|
|
// notify progress
|
|
SetPopulationCount(1,1);
|
|
|
|
// success!
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CShellTreeCtrl::GetItemContextMenu(HTREEITEM hItem, CShellContextMenu& rCtxMenu)
|
|
{
|
|
TVITEMDATA* pData = (TVITEMDATA*)GetItemData(hItem);
|
|
if (!pData->IsValid())
|
|
return FALSE;
|
|
|
|
return rCtxMenu.Create(pData->pParentFolder,
|
|
pData->pidlAbs.GetLastChild());
|
|
}
|
|
|
|
void CShellTreeCtrl::SetCallbackMask(UINT nMask)
|
|
{
|
|
m_nCallbackMask = nMask &
|
|
(TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN);
|
|
}
|
|
|
|
UINT CShellTreeCtrl::GetCallbackMask()
|
|
{
|
|
return m_nCallbackMask;
|
|
} |