You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

759 lines
16 KiB
C++

// XTPShellPidl.cpp : implementation file
//
// This file is a part of the XTREME CONTROLS MFC class library.
// (c)1998-2012 Codejock Software, All Rights Reserved.
//
// THIS SOURCE FILE IS THE PROPERTY OF CODEJOCK SOFTWARE AND IS NOT TO BE
// RE-DISTRIBUTED BY ANY MEANS WHATSOEVER WITHOUT THE EXPRESSED WRITTEN
// CONSENT OF CODEJOCK SOFTWARE.
//
// THIS SOURCE CODE CAN ONLY BE USED UNDER THE TERMS AND CONDITIONS OUTLINED
// IN THE XTREME TOOLKIT PRO LICENSE AGREEMENT. CODEJOCK SOFTWARE GRANTS TO
// YOU (ONE SOFTWARE DEVELOPER) THE LIMITED RIGHT TO USE THIS SOFTWARE ON A
// SINGLE COMPUTER.
//
// CONTACT INFORMATION:
// support@codejock.com
// http://www.codejock.com
//
/////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "Common/XTPVC80Helpers.h" // Visual Studio 2005 helper functions
#include "Common/XTPResourceManager.h"
#include "XTPShellPidl.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
CXTPShellPidl::CShellSpecialFolder::CShellSpecialFolder(int nFolder)
{
m_lpFolder = NULL;
if (FAILED(::SHGetDesktopFolder(&m_lpFolder)))
{
m_lpFolder = NULL;
return;
}
if (nFolder != CSIDL_DESKTOP)
{
CXTPShellPidl::CShellMalloc lpMalloc;
LPSHELLFOLDER lpFolder = NULL;
if (lpMalloc)
{
LPITEMIDLIST pidlFolder = NULL;
if (SUCCEEDED(::SHGetSpecialFolderLocation(NULL, nFolder, &pidlFolder)))
{
m_lpFolder->BindToObject(pidlFolder, 0, IID_IShellFolder, (LPVOID*)&lpFolder);
lpMalloc.Free(pidlFolder);
}
}
m_lpFolder->Release();
m_lpFolder = lpFolder;
}
}
/////////////////////////////////////////////////////////////////////////////
// CXTPShellPidl
CXTPShellPidl::CXTPShellPidl()
: m_bShowShellLinkIcons(TRUE)
, m_ulSFGAOFlags(0)
{
}
CXTPShellPidl::~CXTPShellPidl()
{
}
/////////////////////////////////////////////////////////////////////////////
// Functions that deal with PIDLs
LPITEMIDLIST CXTPShellPidl::IDLFromPath(LPCTSTR strPath)
{
IShellFolder *psfDeskTop = NULL;
LPITEMIDLIST pidlPath = NULL;
if (FAILED(::SHGetDesktopFolder(&psfDeskTop)))
{
return NULL;
}
// Get the folders IDL
psfDeskTop->ParseDisplayName(NULL, NULL,
(LPOLESTR)XTP_CT2CW(strPath), NULL, &pidlPath, NULL);
psfDeskTop->Release();
return pidlPath;
}
LPITEMIDLIST CXTPShellPidl::OneUpPIDL(LPITEMIDLIST pidlPath)
{
if (pidlPath)
{
// Get the last item
LPITEMIDLIST pidlLast = GetLastITEM(pidlPath);
// if not a NULL
if (pidlLast)
{
// don't attempt to go any lower than 1 IDL
if (pidlLast == pidlPath) return pidlPath;
// set its cb to 0
pidlLast->mkid.cb = 0;
}
}
return pidlPath;
}
CString CXTPShellPidl::OneUpPATH(const CString& path)
{
// Make a string buffer
TCHAR newPath[MAX_PATH];
ZeroMemory(newPath, sizeof(newPath));
if (path.GetLength())
{
LPITEMIDLIST pidlPath = IDLFromPath(path);
if (OneUpPIDL(pidlPath))
{
SHGetPathFromIDList(pidlPath, newPath);
}
}
return CString(newPath);
}
LPITEMIDLIST CXTPShellPidl::GetLastITEM(LPITEMIDLIST pidl)
{
LPSTR lpMem = NULL;
LPITEMIDLIST pidlLast = NULL;
if (pidl)
{
lpMem = (LPSTR)pidl;
// walk the list until we find a null
while (*lpMem != 0)
{
if (LPITEMIDLIST(lpMem)->mkid.cb == 0) break;// maybe ?
pidlLast = (LPITEMIDLIST)lpMem;
lpMem += LPITEMIDLIST(lpMem)->mkid.cb;
}
}
return pidlLast;
}
LPITEMIDLIST CXTPShellPidl::CopyPidlItem(LPITEMIDLIST pidl, UINT nItem)
{
if (!pidl)
return NULL;
while (nItem--)
{
if (pidl->mkid.cb == 0)
return NULL;
pidl = GetNextPidlItem(pidl);
}
int nSize = pidl->mkid.cb + sizeof(pidl->mkid.cb);
LPITEMIDLIST pidlCopy = CreatePidl(CShellMalloc(), nSize);
if (pidlCopy)
{
ZeroMemory(pidlCopy, nSize);
MEMCPY_S(pidlCopy, pidl, pidl->mkid.cb);
}
return pidlCopy;
}
LPITEMIDLIST CXTPShellPidl::CopyIDList(LPITEMIDLIST pidl)
{
LPITEMIDLIST pidlCopy = NULL;
UINT bytes = 0;
bytes = GetPidlItemCount(pidl);
pidlCopy = CreatePidl(CShellMalloc(), bytes);
if (pidlCopy)
{
ZeroMemory(pidlCopy, bytes);
MEMCPY_S(pidlCopy, pidl, bytes);
}
return pidlCopy;
}
LPITEMIDLIST CXTPShellPidl::GetNextPidlItem(LPCITEMIDLIST pidl)
{
LPSTR lpMem = (LPSTR)pidl;
lpMem += pidl->mkid.cb;
return (LPITEMIDLIST)lpMem;
}
UINT CXTPShellPidl::GetPidlCount(LPCITEMIDLIST pidl)
{
UINT nCount = 0;
if (pidl)
{
while (pidl->mkid.cb)
{
++nCount;
pidl = GetNextPidlItem(pidl);
}
}
return nCount;
}
UINT CXTPShellPidl::GetPidlItemCount(LPCITEMIDLIST pidl)
{
UINT cbTotal = 0;
if (pidl)
{
cbTotal += sizeof(pidl->mkid.cb); // Null terminator
while (pidl->mkid.cb)
{
cbTotal += pidl->mkid.cb;
pidl = GetNextPidlItem(pidl);
}
}
return cbTotal;
}
LPITEMIDLIST CXTPShellPidl::CreatePidl(LPMALLOC lpMalloc, UINT cbSize)
{
if (!lpMalloc)
return NULL;
// zero-init for external task allocate
LPITEMIDLIST pidl = (LPITEMIDLIST)lpMalloc->Alloc(cbSize);
if (pidl)
{
memset(pidl, 0, cbSize);
}
return pidl;
}
void CXTPShellPidl::FreePidl(LPITEMIDLIST pidl)
{
if (!pidl)
return;
CShellMalloc lpMalloc;
if (lpMalloc)
{
lpMalloc.Free(pidl);
}
}
BOOL CXTPShellPidl::ComparePidls(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2, LPSHELLFOLDER pShellFolder /*NULL*/)
{
if (pidl1 == NULL || pidl2 == NULL)
return FALSE;
BOOL bLPCreated = FALSE;
if (pShellFolder == NULL)
{
if (FAILED(::SHGetDesktopFolder(&pShellFolder)))
return FALSE;
bLPCreated = TRUE;
}
HRESULT hr = pShellFolder->CompareIDs(0, pidl1, pidl2);
if (bLPCreated)
{
pShellFolder->Release();
}
return ((short)hr) == 0;
}
LPITEMIDLIST CXTPShellPidl::ConcatPidls(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
{
return ConcatPidls(CShellMalloc(), pidl1, pidl2);
}
LPITEMIDLIST CXTPShellPidl::ConcatPidls(LPMALLOC lpMalloc, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
{
LPITEMIDLIST pidlNew;
UINT cb1;
UINT cb2;
// May be NULL
if (pidl1)
{
cb1 = GetPidlItemCount(pidl1) - sizeof(pidl1->mkid.cb);
}
else
{
cb1 = 0;
}
cb2 = GetPidlItemCount(pidl2);
pidlNew = CreatePidl(lpMalloc, cb1 + cb2);
if (pidlNew)
{
if (pidl1)
{
MEMCPY_S(pidlNew, pidl1, cb1);
}
MEMCPY_S(((LPSTR)pidlNew) + cb1, pidl2, cb2);
}
return pidlNew;
}
LPITEMIDLIST CXTPShellPidl::DuplicateItem(LPMALLOC lpMalloc, LPITEMIDLIST lpi)
{
LPITEMIDLIST lpiTemp = (LPITEMIDLIST)lpMalloc->Alloc(lpi->mkid.cb + sizeof(lpi->mkid.cb));
MEMCPY_S((PVOID)lpiTemp, (CONST VOID *)lpi, lpi->mkid.cb + sizeof(lpi->mkid.cb));
return lpiTemp;
}
BOOL CXTPShellPidl::GetName(LPSHELLFOLDER lpsf, LPITEMIDLIST lpi, DWORD dwFlags, CString& strFriendlyName)
{
STRRET str;
if (lpsf->GetDisplayNameOf(lpi, dwFlags, &str) == NOERROR)
{
switch (str.uType)
{
case STRRET_WSTR:
{
strFriendlyName = str.pOleStr;
CShellMalloc pMalloc;
if (pMalloc) pMalloc.Free(str.pOleStr);
}
break;
case STRRET_OFFSET:
{
LPSTR lpszOffset = (LPSTR)lpi + str.uOffset;
strFriendlyName = lpszOffset;
}
break;
case STRRET_CSTR:
strFriendlyName = (LPSTR)str.cStr;
break;
default:
return FALSE;
}
}
else
{
return FALSE;
}
return TRUE;
}
LPITEMIDLIST CXTPShellPidl::GetFullyQualPidl(LPSHELLFOLDER lpsf, LPITEMIDLIST lpi)
{
CString strBuff;
LPSHELLFOLDER lpsfDeskTop;
LPITEMIDLIST lpifq;
ULONG ulEaten, ulAttribs;
if (!GetName(lpsf, lpi, SHGDN_FORPARSING, strBuff))
{
return NULL;
}
if (FAILED(::SHGetDesktopFolder(&lpsfDeskTop)))
{
return NULL;
}
if (FAILED(lpsfDeskTop->ParseDisplayName(NULL, NULL, (LPOLESTR)XTP_CT2CW(strBuff),
&ulEaten, &lpifq, &ulAttribs)))
{
lpsfDeskTop->Release();
return NULL;
}
lpsfDeskTop->Release();
return lpifq;
}
ULONG CXTPShellPidl::GetAttributes(LPSHELLFOLDER lpsf, LPITEMIDLIST lpi, ULONG uFlags) const
{
if (!lpsf || !lpi)
return 0;
if ((uFlags & SFGAO_DISPLAYATTRMASK) == 0)
{
lpsf->GetAttributesOf(1, (LPCITEMIDLIST*)&lpi, &uFlags);
return uFlags;
}
ULONG ulAttrs = SFGAO_REMOVABLE;
lpsf->GetAttributesOf(1, (LPCITEMIDLIST*)&lpi, &ulAttrs);
if ((ulAttrs & SFGAO_REMOVABLE) != 0)
return SFGAO_REMOVABLE | SFGAO_HASSUBFOLDER | SFGAO_FOLDER;
lpsf->GetAttributesOf(1, (LPCITEMIDLIST*)&lpi, &uFlags);
return uFlags;
}
// Static variables used for passing data to the subclassing wndProc
WNDPROC CXTPShellPidl::m_pOldWndProc = NULL; // regular window proc
LPCONTEXTMENU2 CXTPShellPidl::m_pIContext2 = NULL; // active shell context menu
BOOL CXTPShellPidl::ShowContextMenu(HWND hwnd, LPSHELLFOLDER lpsfParent, LPITEMIDLIST lpi, LPPOINT lppt)
{
return ShowContextMenu(hwnd, lpsfParent, (LPCITEMIDLIST*)&lpi, 1, lppt);
}
void CXTPShellPidl::OnShowContextMenu(int /*idCmd*/, CMINVOKECOMMANDINFO& /*cmi*/)
{
// handle in derived class.
}
BOOL CXTPShellPidl::ShowContextMenu(HWND hwnd, LPSHELLFOLDER lpsfParent, LPCITEMIDLIST* lpi, int nCount, LPPOINT lppt)
{
LPCONTEXTMENU lpcm;
CMINVOKECOMMANDINFO cmi;
BOOL bSuccess = TRUE;
int cmType; // "version" # of context menu
// assume that psfFolder and pidl are valid
HRESULT hr = GetSHContextMenu(lpsfParent, lpi, nCount, (void**)&lpcm, &cmType);
if (SUCCEEDED(hr))
{
HMENU hMenu = ::CreatePopupMenu();
if (hMenu != NULL)
{
hr = lpcm->QueryContextMenu(hMenu, 0, 1, 0x7fff, CMF_EXPLORE);
if (SUCCEEDED(hr))
{
// install the subclassing "hook", for versions 2 or 3
if (cmType > 1)
{
m_pOldWndProc = (WNDPROC)
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)HookWndProc);
m_pIContext2 = (LPCONTEXTMENU2)lpcm; // cast ok for ICMv3
}
else
m_pOldWndProc = NULL;
int idCmd = ::TrackPopupMenu(hMenu,
TPM_LEFTALIGN | TPM_RETURNCMD | TPM_RIGHTBUTTON,
lppt->x, lppt->y, 0, hwnd, NULL);
if (m_pOldWndProc) // restore old wndProc
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)m_pOldWndProc);
if (idCmd != 0)
{
cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
cmi.fMask = 0;
cmi.hwnd = hwnd;
cmi.lpVerb = (LPCSTR)MAKEINTRESOURCE(idCmd-1);
cmi.lpParameters = NULL;
cmi.lpDirectory = NULL;
cmi.nShow = SW_SHOWNORMAL;
cmi.dwHotKey = 0;
cmi.hIcon = NULL;
if (SUCCEEDED(lpcm->InvokeCommand(&cmi)))
{
OnShowContextMenu(idCmd-1, cmi);
}
}
m_pIContext2 = NULL; // prevents accidental use
}
else
{
bSuccess = FALSE;
}
::DestroyMenu(hMenu);
}
else
bSuccess = FALSE;
lpcm->Release();
}
else
{
CString strMessage;
strMessage.Format(_T("GetUIObjectOf failed! hr=%lx"), hr);
AfxMessageBox(strMessage);
bSuccess = FALSE;
}
return bSuccess;
}
int CXTPShellPidl::GetItemIcon(LPITEMIDLIST lpi, UINT uFlags) const
{
SHFILEINFO sfi;
::SHGetFileInfo((TCHAR*)lpi, 0, &sfi, sizeof(SHFILEINFO), uFlags);
return sfi.iIcon;
}
HIMAGELIST CXTPShellPidl::GetSystemImageList(UINT uFlags) const
{
SHFILEINFO sfi;
for (TCHAR cDrive = _T('C'); cDrive <= _T('Z'); cDrive++)
{
if (GetDriveType(CString(cDrive) + _T(":")) == DRIVE_FIXED)
{
return (HIMAGELIST)::SHGetFileInfo(CString(cDrive) + _T(":\\"), 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | uFlags);
}
}
return NULL;
}
HRESULT CXTPShellPidl::GetSHContextMenu(LPSHELLFOLDER psfFolder, LPCITEMIDLIST* localPidl, int nCount, void** ppCM, int* pcmType)
{
*ppCM = NULL;
LPCONTEXTMENU pICv1 = NULL; // plain version
HRESULT hr;
// try to obtain the lowest possible IContextMenu
if (nCount == 0)
{
hr = psfFolder->CreateViewObject(NULL, IID_IContextMenu, (void**)&pICv1);
}
else
{
hr = psfFolder->GetUIObjectOf(NULL, nCount, localPidl, IID_IContextMenu, NULL, (void**)&pICv1);
}
// try to obtain a higher level pointer, first 3 then 2
if (pICv1)
{
hr = pICv1->QueryInterface(IID_IContextMenu3, ppCM);
if (NOERROR == hr)
*pcmType = 3;
else
{
hr = pICv1->QueryInterface(IID_IContextMenu2, ppCM);
if (NOERROR == hr) *pcmType = 2;
}
// free initial "v1.0" interface
if (*ppCM)
pICv1->Release();
// no higher version supported
else
{
*pcmType = 1;
*ppCM = pICv1;
hr = NOERROR; // never mind the query failures, this'll do
}
}
return hr;
}
LRESULT CALLBACK CXTPShellPidl::HookWndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
UINT uItem;
TCHAR szBuf[MAX_PATH];
switch (msg)
{
case WM_DRAWITEM:
case WM_MEASUREITEM:
{
if (wp)
break; // not menu related
}
case WM_INITMENUPOPUP:
{
m_pIContext2->HandleMenuMsg(msg, wp, lp);
return (msg == WM_INITMENUPOPUP ? 0 : TRUE); // handled
}
case WM_MENUSELECT:
{
// if this is a shell item, get it's descriptive text
uItem = (UINT) LOWORD(wp);
if (0 == (MF_POPUP & HIWORD(wp)) && uItem >= 1 && uItem <= 0x7fff)
{
CFrameWnd * pWnd = ((CFrameWnd*)(AfxGetApp()->m_pMainWnd));
if (!pWnd)
return 0;
szBuf[0] = 0;
// set the status bar text
if (SUCCEEDED(m_pIContext2->GetCommandString(uItem-1, GCS_HELPTEXT,
NULL, (LPSTR)szBuf, _countof(szBuf))))
{
pWnd->SetMessageText(szBuf);
}
else
{
pWnd->SetMessageText(_T(""));
}
return 0;
}
break;
}
default:
break;
}
// for all untreated messages, call the original wndproc
return ::CallWindowProc(m_pOldWndProc, hWnd, msg, wp, lp);
}
int CXTPShellPidl::TreeViewCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
if (!lParam1 || !lParam2)
return 0;
XTP_TVITEMDATA* lptvid1 = (XTP_TVITEMDATA*)lParam1;
XTP_TVITEMDATA* lptvid2 = (XTP_TVITEMDATA*)lParam2;
BOOL bAsc = (lParamSort > 0); // positive - ascending, negative - descending
int iIndex = abs((int)lParamSort) - 1; // remove sort flag from index
HRESULT hr = lptvid1->lpsfParent->CompareIDs(
MAKELPARAM(LOWORD(iIndex), HIWORD(SHCIDS_ALLFIELDS)),
bAsc? lptvid1->lpi: lptvid2->lpi,
bAsc? lptvid2->lpi: lptvid1->lpi);
if (FAILED(hr))
{
return 0;
}
return (short)SCODE_CODE(GetScode(hr));
}
int CXTPShellPidl::ListViewCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
if (!lParam1 || !lParam2)
return 0;
XTP_LVITEMDATA* lplvid1 = (XTP_LVITEMDATA*)lParam1;
XTP_LVITEMDATA* lplvid2 = (XTP_LVITEMDATA*)lParam2;
BOOL bAsc = (lParamSort > 0); // positive - ascending, negative - descending
int iIndex = abs((int)lParamSort) - 1; // remove sort flag from index
HRESULT hr = lplvid1->lpsfParent->CompareIDs(
MAKELPARAM(LOWORD(iIndex), HIWORD(SHCIDS_ALLFIELDS)),
bAsc ? lplvid1->lpi : lplvid2->lpi,
bAsc ? lplvid2->lpi : lplvid1->lpi);
if (FAILED(hr))
{
return 0;
}
return (short)SCODE_CODE(GetScode(hr));
}
void CXTPShellPidl::MapShellFlagsToItemAttributes(CTreeCtrl* pTreeCtrl, HTREEITEM hItem, DWORD dwAttributes)
{
// Display with 'cut' feedback if the item is ghosted (about to be moved to a different location or a hidden one)
if (dwAttributes & SFGAO_GHOSTED)
{
pTreeCtrl->SetItemState(hItem, TVIS_CUT, TVIS_CUT);
}
else
{
pTreeCtrl->SetItemState(hItem, 0, TVIS_CUT);
}
UINT nImage; // assign proper overlay image (link, share)
if ((dwAttributes & SFGAO_LINK) && m_bShowShellLinkIcons)
{
nImage = INDEXTOOVERLAYMASK(2);
}
else
if (dwAttributes & SFGAO_SHARE)
{
nImage = INDEXTOOVERLAYMASK(1);
}
else
{
nImage = 0; // no overlay
}
// NB: we use here TVIS_OVERLAYMASK (as for the tree control) though
// the list view control also calls this function and LVIS_OVERLAYMASK
// should be used in that case. Reason for that is both of them refer
// to results of INDEXTOOVERLAYMASK macro and hence they have to be the same,
// namely INDEXTOOVERLAYMASK(15)
pTreeCtrl->SetItemState(hItem, nImage, TVIS_OVERLAYMASK);
}
void CXTPShellPidl::MapShellFlagsToItemAttributes(CListCtrl* pListCtrl, int iItem, DWORD dwAttributes)
{
// Display with 'cut' feedback if the item is ghosted (about to be moved to a different location or a hidden one)
if (dwAttributes & SFGAO_GHOSTED)
{
pListCtrl->SetItemState(iItem, LVIS_CUT, LVIS_CUT);
}
else
{
pListCtrl->SetItemState(iItem, 0, LVIS_CUT);
}
UINT nImage; // assign proper overlay image (link, share)
if ((dwAttributes & SFGAO_LINK) && m_bShowShellLinkIcons)
{
nImage = INDEXTOOVERLAYMASK(2);
}
else
if (dwAttributes & SFGAO_SHARE)
{
nImage = INDEXTOOVERLAYMASK(1);
}
else
{
nImage = 0; // no overlay
}
// NB: we use here TVIS_OVERLAYMASK (as for the tree control) though
// the list view control also calls this function and LVIS_OVERLAYMASK
// should be used in that case. Reason for that is both of them refer
// to results of INDEXTOOVERLAYMASK macro and hence they have to be the same,
// namely INDEXTOOVERLAYMASK(15)
pListCtrl->SetItemState(iItem, nImage, TVIS_OVERLAYMASK);
}