You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
509 lines
16 KiB
509 lines
16 KiB
#include "shellprv.h"
|
|
#include "shcombox.h"
|
|
#include "filetype.h"
|
|
#include "recdocs.h"
|
|
#include "ids.h"
|
|
|
|
// Adds the specified item to a comboboxex window
|
|
HRESULT AddCbxItemToComboBox(HWND hwndComboEx, PCCBXITEM pItem, INT_PTR *pnPosAdded)
|
|
{
|
|
ASSERT(hwndComboEx);
|
|
|
|
// Convert to COMBOBOXEXITEM.
|
|
COMBOBOXEXITEM cei;
|
|
cei.mask = pItem->mask;
|
|
cei.iItem = pItem->iItem;
|
|
cei.pszText = (LPTSTR)pItem->szText;
|
|
cei.cchTextMax = ARRAYSIZE(pItem->szText);
|
|
cei.iImage = pItem->iImage;
|
|
cei.iSelectedImage = pItem->iSelectedImage;
|
|
cei.iOverlay = pItem->iOverlay;
|
|
cei.iIndent = pItem->iIndent;
|
|
cei.lParam = pItem->lParam;
|
|
|
|
int nPos = (int)::SendMessage(hwndComboEx, CBEM_INSERTITEM, 0, (LPARAM)&cei);
|
|
|
|
*pnPosAdded = nPos;
|
|
|
|
return nPos < 0 ? E_FAIL : S_OK;
|
|
}
|
|
|
|
// Adds the specified item to a comboboxex window, and invokes
|
|
// a notification callback function if successful.
|
|
HRESULT AddCbxItemToComboBoxCallback(IN HWND hwndComboEx, IN PCBXITEM pItem, IN ADDCBXITEMCALLBACK pfn, IN LPARAM lParam)
|
|
{
|
|
INT_PTR iPos = -1;
|
|
|
|
if (pfn && E_ABORT == pfn(CBXCB_ADDING, pItem, lParam))
|
|
return E_ABORT;
|
|
|
|
HRESULT hr = AddCbxItemToComboBox(hwndComboEx, pItem, &iPos);
|
|
|
|
if (pfn && S_OK == hr)
|
|
{
|
|
((CBXITEM*)pItem)->iItem = iPos;
|
|
pfn(CBXCB_ADDED, pItem, lParam);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// image list indices known
|
|
void MakeCbxItemKnownImage(CBXITEM* pcbi, LPCTSTR pszDisplayName, void *pvData,
|
|
int iImage, int iSelectedImage, INT_PTR nPos, int iIndent)
|
|
{
|
|
ZeroMemory(pcbi, sizeof(*pcbi));
|
|
|
|
StringCchCopy(pcbi->szText, ARRAYSIZE(pcbi->szText), pszDisplayName);
|
|
pcbi->lParam = (LPARAM)pvData;
|
|
pcbi->iIndent = iIndent;
|
|
pcbi->iItem = nPos;
|
|
pcbi->mask = (CBEIF_TEXT | CBEIF_INDENT | CBEIF_LPARAM);
|
|
if (-1 != iImage)
|
|
{
|
|
pcbi->mask |= CBEIF_IMAGE;
|
|
pcbi->iImage = iImage;
|
|
}
|
|
if (-1 != iSelectedImage)
|
|
{
|
|
pcbi->mask |= CBEIF_SELECTEDIMAGE;
|
|
pcbi->iSelectedImage = iSelectedImage;
|
|
}
|
|
}
|
|
|
|
// Retrieves the system image list indices for the specified ITEMIDLIST
|
|
HRESULT _GetPidlIcon(LPCITEMIDLIST pidl, int *piImage, int *piSelectedImage)
|
|
{
|
|
IShellFolder *psfParent;
|
|
LPCITEMIDLIST pidlChild;
|
|
|
|
HRESULT hr = SHBindToIDListParent(pidl, IID_PPV_ARG(IShellFolder, &psfParent), &pidlChild);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*piImage = SHMapPIDLToSystemImageListIndex(psfParent, pidlChild, NULL);
|
|
|
|
*piSelectedImage = *piImage;
|
|
|
|
psfParent->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// image icon image list indices unknown
|
|
STDAPI_(void) MakeCbxItem(CBXITEM* pcbi, LPCTSTR pszDisplayName, void *pvData, LPCITEMIDLIST pidlIcon, INT_PTR nPos, int iIndent)
|
|
{
|
|
int iImage = -1;
|
|
int iSelectedImage = -1;
|
|
|
|
if (pidlIcon)
|
|
_GetPidlIcon(pidlIcon, &iImage, &iSelectedImage);
|
|
|
|
MakeCbxItemKnownImage(pcbi, pszDisplayName, pvData, iImage, iSelectedImage, nPos, iIndent);
|
|
}
|
|
|
|
HRESULT _MakeFileTypeCbxItem(
|
|
OUT CBXITEM* pcbi,
|
|
IN LPCTSTR pszDisplayName,
|
|
IN LPCTSTR pszExt,
|
|
IN LPCITEMIDLIST pidlIcon,
|
|
IN INT_PTR nPos,
|
|
IN int iIndent)
|
|
{
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
void *pvData = NULL;
|
|
LPITEMIDLIST pidlToFree = NULL;
|
|
|
|
if (!pidlIcon)
|
|
{
|
|
TCHAR szFileName[MAX_PATH] = TEXT("C:\\notexist"); // This is bogus and that's ok
|
|
|
|
StringCchCat(szFileName, ARRAYSIZE(szFileName), pszExt);
|
|
pidlIcon = pidlToFree = SHSimpleIDListFromPath(szFileName);
|
|
}
|
|
|
|
if (pidlIcon && Str_SetPtr((LPTSTR *)&pvData, pszExt))
|
|
{
|
|
MakeCbxItem(pcbi, pszDisplayName, pvData, pidlIcon, nPos, iIndent);
|
|
hr = S_OK;
|
|
}
|
|
|
|
ILFree(pidlToFree); // may be NULL
|
|
|
|
return hr;
|
|
}
|
|
|
|
// Enumerates children of the indicated special shell item id.
|
|
HRESULT EnumSpecialItemIDs(int csidl, DWORD dwSHCONTF, LPFNPIDLENUM_CB pfn, void *pvData)
|
|
{
|
|
LPITEMIDLIST pidlFolder;
|
|
if (SUCCEEDED(SHGetFolderLocation(NULL, csidl, NULL, 0, &pidlFolder)))
|
|
{
|
|
IShellFolder *psf;
|
|
if (SUCCEEDED(SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, pidlFolder, &psf))))
|
|
{
|
|
IEnumIDList * penum;
|
|
if (S_OK == psf->EnumObjects(NULL, dwSHCONTF, &penum))
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
BOOL bContinue = TRUE;
|
|
|
|
while (bContinue && (S_OK == penum->Next(1, &pidl, NULL)))
|
|
{
|
|
LPITEMIDLIST pidlFull = ILCombine(pidlFolder, pidl);
|
|
if (pidlFull)
|
|
{
|
|
if (FAILED(pfn(pidlFull, pvData)))
|
|
bContinue = FALSE;
|
|
|
|
ILFree(pidlFull);
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
penum->Release();
|
|
}
|
|
psf->Release();
|
|
}
|
|
ILFree(pidlFolder);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDAPI_(HIMAGELIST) GetSystemImageListSmallIcons()
|
|
{
|
|
HIMAGELIST himlSmall;
|
|
Shell_GetImageLists(NULL, &himlSmall);
|
|
return himlSmall;
|
|
}
|
|
|
|
HRESULT _MakeLocalDrivesCbxItem(CBXITEM* pItem, LPCITEMIDLIST pidl)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
HRESULT hr = S_FALSE;
|
|
ULONG ulAttrs = SFGAO_FOLDER | SFGAO_FILESYSTEM | SFGAO_NONENUMERATED;
|
|
|
|
if (SUCCEEDED(SHGetNameAndFlags(pidl, SHGDN_FORPARSING, szPath, SIZECHARS(szPath), &ulAttrs)) &&
|
|
((SFGAO_FOLDER | SFGAO_FILESYSTEM) == (ulAttrs & (SFGAO_FOLDER | SFGAO_FILESYSTEM | SFGAO_NONENUMERATED))) &&
|
|
(GetDriveType(szPath) == DRIVE_FIXED))
|
|
{
|
|
TCHAR szDisplayName[MAX_PATH];
|
|
SHGetNameAndFlags(pidl, SHGDN_NORMAL, szDisplayName, SIZECHARS(szDisplayName), NULL);
|
|
|
|
LPTSTR pszPath = NULL;
|
|
Str_SetPtr(&pszPath, szPath);
|
|
|
|
MakeCbxItem(pItem, szDisplayName, (void *)pszPath, pidl, LISTINSERT_LAST, NO_ITEM_INDENT);
|
|
hr = S_OK;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
HWND hwndComboBox;
|
|
ADDCBXITEMCALLBACK pfn;
|
|
LPARAM lParam;
|
|
} ENUMITEMPARAM;
|
|
|
|
HRESULT _PopulateLocalDrivesCB(LPCITEMIDLIST pidl, void *pv)
|
|
{
|
|
CBXITEM item;
|
|
HRESULT hr = _MakeLocalDrivesCbxItem(&item, pidl);
|
|
if (hr == S_OK)
|
|
{
|
|
ENUMITEMPARAM *peip = (ENUMITEMPARAM *) pv;
|
|
item.iID = CSIDL_DRIVES;
|
|
hr = AddCbxItemToComboBoxCallback(peip->hwndComboBox, &item, peip->pfn, peip->lParam);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
STDAPI PopulateLocalDrivesCombo(HWND hwndComboBoxEx, ADDCBXITEMCALLBACK pfn, LPARAM lParam)
|
|
{
|
|
ENUMITEMPARAM eip;
|
|
|
|
eip.hwndComboBox = hwndComboBoxEx;
|
|
eip.pfn = pfn;
|
|
eip.lParam = lParam;
|
|
|
|
::SendMessage(hwndComboBoxEx, CB_RESETCONTENT, 0, 0);
|
|
|
|
return EnumSpecialItemIDs(CSIDL_DRIVES, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, _PopulateLocalDrivesCB, &eip);
|
|
}
|
|
|
|
// File Associations selector combo methods
|
|
|
|
HRESULT _AddFileType(IN HWND hwndComboBox, IN LPCTSTR pszDisplayName, IN LPCTSTR pszExt, IN LPCITEMIDLIST pidlIcon, IN int iIndent,
|
|
IN OPTIONAL ADDCBXITEMCALLBACK pfn, IN OPTIONAL LPARAM lParam);
|
|
|
|
|
|
HRESULT _AddFileTypes(HWND hwndComboBox, ADDCBXITEMCALLBACK pfn, LPARAM lParam)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwSubKey = 0;
|
|
TCHAR szExtension[MAX_PATH]; // string containing the classes key
|
|
DWORD dwExtension;
|
|
BOOL bFoundFirstExt = FALSE;
|
|
|
|
// Enumerate extensions from registry to get file types
|
|
dwExtension = ARRAYSIZE(szExtension);
|
|
while (hr != E_ABORT && SHEnumKeyEx(HKEY_CLASSES_ROOT, dwSubKey, szExtension, &dwExtension) != ERROR_NO_MORE_ITEMS)
|
|
{
|
|
if (*szExtension == TEXT('.')) // find the file type identifier and description from the extension
|
|
{
|
|
IQueryAssociations *pqa;
|
|
if (SUCCEEDED(AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &pqa))))
|
|
{
|
|
if (SUCCEEDED(pqa->Init(0, szExtension, NULL, NULL)))
|
|
{
|
|
TCHAR szDesc[MAX_PATH];
|
|
DWORD dwAttributes = 0;
|
|
DWORD dwSize = sizeof(dwAttributes);
|
|
BOOL fAdd;
|
|
|
|
if (SUCCEEDED(pqa->GetData(NULL, ASSOCDATA_EDITFLAGS, NULL, &dwAttributes, &dwSize)))
|
|
{
|
|
fAdd = !(dwAttributes & FTA_Exclude);
|
|
}
|
|
else
|
|
{
|
|
dwSize = ARRAYSIZE(szDesc);
|
|
fAdd = SUCCEEDED(pqa->GetString(NULL, ASSOCSTR_DEFAULTICON, NULL, NULL, &dwSize));
|
|
}
|
|
|
|
if (fAdd)
|
|
{
|
|
dwSize = ARRAYSIZE(szDesc);
|
|
pqa->GetString(NULL, ASSOCSTR_FRIENDLYDOCNAME, NULL, szDesc, &dwSize);
|
|
hr = _AddFileType(hwndComboBox, szDesc, szExtension, NULL, NO_ITEM_INDENT, pfn, lParam);
|
|
}
|
|
}
|
|
pqa->Release();
|
|
}
|
|
|
|
bFoundFirstExt = TRUE;
|
|
}
|
|
else if (bFoundFirstExt) // stop after first non-ext key (if sorted registry)
|
|
break;
|
|
|
|
dwSubKey++;
|
|
dwExtension = ARRAYSIZE(szExtension);
|
|
}
|
|
|
|
if (hr != E_ABORT && LoadString(HINST_THISDLL, IDS_FOLDERTYPENAME, szExtension, ARRAYSIZE(szExtension)))
|
|
{
|
|
LPITEMIDLIST pidlIcon = SHCloneSpecialIDList(NULL, CSIDL_RECENT, FALSE);
|
|
if (pidlIcon)
|
|
{
|
|
hr = _AddFileType(hwndComboBox, szExtension, TEXT("."), pidlIcon, NO_ITEM_INDENT, pfn, lParam);
|
|
ILFree(pidlIcon);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT _AddFileType(HWND hwndComboBox, LPCTSTR pszDisplayName, LPCTSTR pszExt, LPCITEMIDLIST pidlIcon, int iIndent, ADDCBXITEMCALLBACK pfn, LPARAM lParam)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL bExists = FALSE;
|
|
|
|
LRESULT lRet = ::SendMessage(hwndComboBox, CB_FINDSTRINGEXACT, 0, (LPARAM) pszDisplayName);
|
|
LRESULT nIndex = lRet;
|
|
|
|
// Is the string already in the list?
|
|
if (CB_ERR != nIndex)
|
|
{
|
|
// Yes, so we want to combine our extension with the current extension or extension list
|
|
// and erase the old one. Then we can continue to add it below.
|
|
LPTSTR pszOldExt = NULL;
|
|
|
|
lRet = SendMessage(hwndComboBox, CB_GETITEMDATA, nIndex, 0);
|
|
if (!(0 == lRet || CB_ERR == lRet))
|
|
{
|
|
pszOldExt = (LPTSTR)lRet;
|
|
UINT cchLen = lstrlen(pszOldExt) + 1 + lstrlen(pszExt) + 1;
|
|
|
|
LPTSTR pszNewExt = (LPTSTR)LocalReAlloc(pszOldExt, sizeof(TCHAR) * cchLen, LMEM_ZEROINIT | LMEM_MOVEABLE);
|
|
if (pszNewExt)
|
|
{
|
|
StringCchCat(pszNewExt, cchLen, TEXT(";"));
|
|
StringCchCat(pszNewExt, cchLen, pszExt);
|
|
lRet = ::SendMessage(hwndComboBox, CB_SETITEMDATA, (WPARAM)nIndex, (LPARAM)pszNewExt);
|
|
}
|
|
bExists = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!bExists)
|
|
{
|
|
// No, so we can add it.
|
|
TCHAR szString[MAX_URL_STRING];
|
|
INT_PTR nPos = 0;
|
|
INT_PTR nLast = CB_ERR;
|
|
|
|
lRet = ::SendMessage(hwndComboBox, CB_GETCOUNT, 0, 0);
|
|
|
|
if (lRet == CB_ERR)
|
|
return E_FAIL;
|
|
|
|
nLast = lRet - 1;
|
|
*szString = 0;
|
|
|
|
// Combo box is never populated with strings longer than MAX_PATH
|
|
lRet = ::SendMessage(hwndComboBox, CB_GETLBTEXT, (WPARAM)nLast, (LPARAM)szString);
|
|
|
|
if (lRet == CB_ERR)
|
|
return E_FAIL;
|
|
|
|
// Base case, does his the new string need to be inserted into the end?
|
|
if ((-1 == nLast) || (0 > StrCmp(szString, pszDisplayName)))
|
|
{
|
|
// Yes, so add it to the end.
|
|
CBXITEM item;
|
|
hr = _MakeFileTypeCbxItem(&item, pszDisplayName, pszExt, pidlIcon, (nLast + 1), iIndent);
|
|
if (SUCCEEDED(hr))
|
|
hr = AddCbxItemToComboBoxCallback(hwndComboBox, &item, pfn, lParam);
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG
|
|
INT_PTR nCycleDetector = nLast + 5;
|
|
#endif // DEBUG
|
|
BOOL bDisplayName = TRUE;
|
|
do
|
|
{
|
|
// Determine ordered insertion point:
|
|
INT_PTR nTest = nPos + ((nLast - nPos) / 2);
|
|
// Combo box is never populated with strings longer than MAX_PATH
|
|
bDisplayName = CB_ERR != ::SendMessage(hwndComboBox, CB_GETLBTEXT, (WPARAM)nTest, (LPARAM)szString);
|
|
|
|
if (bDisplayName)
|
|
{
|
|
// Does the string need to before nTest?
|
|
if (0 > StrCmp(pszDisplayName, szString))
|
|
nLast = nTest; // Yes
|
|
else
|
|
{
|
|
if (nPos == nTest)
|
|
nPos++;
|
|
else
|
|
nPos = nTest; // No
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
ASSERT(nCycleDetector); // Make sure we converge.
|
|
nCycleDetector--;
|
|
#endif // DEBUG
|
|
}
|
|
|
|
} while (bDisplayName && nLast - nPos);
|
|
|
|
if (bDisplayName)
|
|
{
|
|
CBXITEM item;
|
|
hr = _MakeFileTypeCbxItem(&item, pszDisplayName, pszExt, pidlIcon, nPos, iIndent);
|
|
if (SUCCEEDED(hr))
|
|
hr = AddCbxItemToComboBoxCallback(hwndComboBox, &item, pfn, lParam);
|
|
}
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDAPI PopulateFileAssocCombo(HWND hwndComboBoxEx, ADDCBXITEMCALLBACK pfn, LPARAM lParam)
|
|
{
|
|
ASSERT(hwndComboBoxEx);
|
|
|
|
::SendMessage(hwndComboBoxEx, CB_RESETCONTENT, 0, 0);
|
|
|
|
HRESULT hr = _AddFileTypes(hwndComboBoxEx, pfn, lParam);
|
|
if (E_ABORT == hr)
|
|
return hr;
|
|
|
|
// Now add this to the top of the list.
|
|
CBXITEM item;
|
|
TCHAR szDisplayName[MAX_PATH];
|
|
LoadString(HINST_THISDLL, IDS_SNS_ALL_FILE_TYPES, szDisplayName, ARRAYSIZE(szDisplayName));
|
|
MakeCbxItem(&item, szDisplayName, (void *)FILEASSOCIATIONSID_ALLFILETYPES, NULL, LISTINSERT_FIRST, NO_ITEM_NOICON_INDENT);
|
|
|
|
return AddCbxItemToComboBoxCallback(hwndComboBoxEx, &item, pfn, lParam);
|
|
}
|
|
|
|
void *_getFileAssocComboData(HWND hwndComboBox)
|
|
{
|
|
LRESULT nSelected = ::SendMessage(hwndComboBox, CB_GETCURSEL, 0, 0);
|
|
|
|
if (-1 == nSelected)
|
|
return NULL;
|
|
|
|
LRESULT itemData = ::SendMessage(hwndComboBox, CB_GETITEMDATA, nSelected, 0);
|
|
|
|
if (itemData == CB_ERR)
|
|
itemData = NULL;
|
|
|
|
return (LPVOID)itemData;
|
|
}
|
|
|
|
DWORD _getFileAssocComboID(HWND hwndComboBox)
|
|
{
|
|
DWORD dwID = 0;
|
|
void *pvData = _getFileAssocComboData(hwndComboBox);
|
|
|
|
// Is this an ID?
|
|
if (pvData && ((DWORD_PTR)pvData <= FILEASSOCIATIONSID_MAX))
|
|
{
|
|
// Yes, so let's get it.
|
|
dwID = PtrToUlong(pvData);
|
|
}
|
|
|
|
return dwID;
|
|
}
|
|
|
|
LONG GetFileAssocComboSelItemText(IN HWND hwndComboBox, OUT LPTSTR *ppszText)
|
|
{
|
|
ASSERT(hwndComboBox);
|
|
ASSERT(ppszText);
|
|
*ppszText = NULL;
|
|
|
|
int nSel = (LONG)::SendMessage(hwndComboBox, CB_GETCURSEL, 0, 0);
|
|
if (nSel >= 0)
|
|
{
|
|
DWORD dwID = _getFileAssocComboID(hwndComboBox);
|
|
|
|
if (dwID > FILEASSOCIATIONSID_FILE_PATH)
|
|
{
|
|
*ppszText = StrDup(TEXT(".*"));
|
|
}
|
|
else
|
|
{
|
|
LPTSTR pszText = (LPTSTR)_getFileAssocComboData(hwndComboBox);
|
|
if (pszText)
|
|
{
|
|
*ppszText = StrDup((LPCTSTR)pszText);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!*ppszText)
|
|
{
|
|
nSel = -1;
|
|
}
|
|
return nSel;
|
|
}
|
|
|
|
|
|
LRESULT DeleteFileAssocComboItem(IN LPNMHDR pnmh)
|
|
{
|
|
PNMCOMBOBOXEX pnmce = (PNMCOMBOBOXEX)pnmh;
|
|
if (pnmce->ceItem.lParam)
|
|
{
|
|
// Is this a pidl?
|
|
if ((pnmce->ceItem.lParam) > FILEASSOCIATIONSID_MAX)
|
|
{
|
|
// Yes, so let's free it.
|
|
Str_SetPtr((LPTSTR *)&pnmce->ceItem.lParam, NULL);
|
|
}
|
|
}
|
|
return 1L;
|
|
}
|