// 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); }