windows-nt/Source/XPSP1/NT/shell/shell32/mulprsht.cpp
2020-09-26 16:20:57 +08:00

3480 lines
119 KiB
C++

#include "shellprv.h"
#pragma hdrstop
#include "propsht.h"
#include <winbase.h>
#include <shellids.h>
#include "util.h" // for GetFileDescription
#include "prshtcpp.h" // for progress dlg and recursive apply
#include "shlexec.h" // for SIDKEYNAME
#include "datautil.h"
#include <efsui.h> // for EfsDetail
#include "ascstr.h" // for IAssocStore
// drivesx.c
STDAPI_(DWORD) PathGetClusterSize(LPCTSTR pszPath);
STDAPI_(DWORD) DrivesPropertiesThreadProc(void *pv);
// version.c
STDAPI_(void) AddVersionPage(LPCTSTR pszFilePath, LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam);
// link.c
STDAPI_(BOOL) AddLinkPage(LPCTSTR pszFile, LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam);
BOOL _IsBiDiCalendar(void);
const DWORD aFileGeneralHelpIds[] = {
IDD_LINE_1, NO_HELP,
IDD_LINE_2, NO_HELP,
IDD_LINE_3, NO_HELP,
IDD_ITEMICON, IDH_FPROP_GEN_ICON,
IDD_NAMEEDIT, IDH_FPROP_GEN_NAME,
IDC_CHANGEFILETYPE, IDH_FPROP_GEN_CHANGE,
IDD_FILETYPE_TXT, IDH_FPROP_GEN_TYPE,
IDD_FILETYPE, IDH_FPROP_GEN_TYPE,
IDD_OPENSWITH_TXT, IDH_FPROP_GEN_OPENSWITH,
IDD_OPENSWITH, IDH_FPROP_GEN_OPENSWITH,
IDD_LOCATION_TXT, IDH_FPROP_GEN_LOCATION,
IDD_LOCATION, IDH_FPROP_GEN_LOCATION,
IDD_FILESIZE_TXT, IDH_FPROP_GEN_SIZE,
IDD_FILESIZE, IDH_FPROP_GEN_SIZE,
IDD_FILESIZE_COMPRESSED, IDH_FPROP_GEN_COMPRESSED_SIZE,
IDD_FILESIZE_COMPRESSED_TXT, IDH_FPROP_GEN_COMPRESSED_SIZE,
IDD_CONTAINS_TXT, IDH_FPROP_FOLDER_CONTAINS,
IDD_CONTAINS, IDH_FPROP_FOLDER_CONTAINS,
IDD_CREATED_TXT, IDH_FPROP_GEN_DATE_CREATED,
IDD_CREATED, IDH_FPROP_GEN_DATE_CREATED,
IDD_LASTMODIFIED_TXT, IDH_FPROP_GEN_LASTCHANGE,
IDD_LASTMODIFIED, IDH_FPROP_GEN_LASTCHANGE,
IDD_LASTACCESSED_TXT, IDH_FPROP_GEN_LASTACCESS,
IDD_LASTACCESSED, IDH_FPROP_GEN_LASTACCESS,
IDD_ATTR_GROUPBOX, IDH_COMM_GROUPBOX,
IDD_READONLY, IDH_FPROP_GEN_READONLY,
IDD_HIDDEN, IDH_FPROP_GEN_HIDDEN,
IDD_ARCHIVE, IDH_FPROP_GEN_ARCHIVE,
IDC_ADVANCED, IDH_FPROP_GEN_ADVANCED,
IDC_DRV_PROPERTIES, IDH_FPROP_GEN_MOUNTEDPROP,
IDD_FILETYPE_TARGET, IDH_FPROP_GEN_MOUNTEDTARGET,
IDC_DRV_TARGET, IDH_FPROP_GEN_MOUNTEDTARGET,
0, 0
};
const DWORD aFolderGeneralHelpIds[] = {
IDD_LINE_1, NO_HELP,
IDD_LINE_2, NO_HELP,
IDD_LINE_3, NO_HELP,
IDD_ITEMICON, IDH_FPROP_GEN_ICON,
IDD_NAMEEDIT, IDH_FPROP_GEN_NAME,
IDC_CHANGEFILETYPE, IDH_FPROP_GEN_CHANGE,
IDD_FILETYPE_TXT, IDH_FPROP_GEN_TYPE,
IDD_FILETYPE, IDH_FPROP_GEN_TYPE,
IDD_OPENSWITH_TXT, IDH_FPROP_GEN_OPENSWITH,
IDD_OPENSWITH, IDH_FPROP_GEN_OPENSWITH,
IDD_LOCATION_TXT, IDH_FPROP_GEN_LOCATION,
IDD_LOCATION, IDH_FPROP_GEN_LOCATION,
IDD_FILESIZE_TXT, IDH_FPROP_GEN_SIZE,
IDD_FILESIZE, IDH_FPROP_GEN_SIZE,
IDD_FILESIZE_COMPRESSED, IDH_FPROP_GEN_COMPRESSED_SIZE,
IDD_FILESIZE_COMPRESSED_TXT, IDH_FPROP_GEN_COMPRESSED_SIZE,
IDD_CONTAINS_TXT, IDH_FPROP_FOLDER_CONTAINS,
IDD_CONTAINS, IDH_FPROP_FOLDER_CONTAINS,
IDD_CREATED_TXT, IDH_FPROP_GEN_DATE_CREATED,
IDD_CREATED, IDH_FPROP_GEN_DATE_CREATED,
IDD_LASTMODIFIED_TXT, IDH_FPROP_GEN_LASTCHANGE,
IDD_LASTMODIFIED, IDH_FPROP_GEN_LASTCHANGE,
IDD_LASTACCESSED_TXT, IDH_FPROP_GEN_LASTACCESS,
IDD_LASTACCESSED, IDH_FPROP_GEN_LASTACCESS,
IDD_ATTR_GROUPBOX, IDH_COMM_GROUPBOX,
IDD_READONLY, IDH_FPROP_GEN_FOLDER_READONLY,
IDD_HIDDEN, IDH_FPROP_GEN_HIDDEN,
IDD_ARCHIVE, IDH_FPROP_GEN_ARCHIVE,
IDC_ADVANCED, IDH_FPROP_GEN_ADVANCED,
IDC_DRV_PROPERTIES, IDH_FPROP_GEN_MOUNTEDPROP,
IDD_FILETYPE_TARGET, IDH_FPROP_GEN_MOUNTEDTARGET,
IDC_DRV_TARGET, IDH_FPROP_GEN_MOUNTEDTARGET,
0, 0
};
const DWORD aMultiPropHelpIds[] = {
IDD_LINE_1, NO_HELP,
IDD_LINE_2, NO_HELP,
IDD_ITEMICON, IDH_FPROP_GEN_ICON,
IDD_CONTAINS, IDH_MULTPROP_NAME,
IDD_FILETYPE_TXT, IDH_FPROP_GEN_TYPE,
IDD_FILETYPE, IDH_FPROP_GEN_TYPE,
IDD_LOCATION_TXT, IDH_FPROP_GEN_LOCATION,
IDD_LOCATION, IDH_FPROP_GEN_LOCATION,
IDD_FILESIZE_TXT, IDH_FPROP_GEN_SIZE,
IDD_FILESIZE, IDH_FPROP_GEN_SIZE,
IDD_FILESIZE_COMPRESSED, IDH_FPROP_GEN_COMPRESSED_SIZE,
IDD_FILESIZE_COMPRESSED_TXT, IDH_FPROP_GEN_COMPRESSED_SIZE,
IDD_ATTR_GROUPBOX, IDH_COMM_GROUPBOX,
IDD_READONLY, IDH_FPROP_GEN_READONLY,
IDD_HIDDEN, IDH_FPROP_GEN_HIDDEN,
IDD_ARCHIVE, IDH_FPROP_GEN_ARCHIVE,
IDC_ADVANCED, IDH_FPROP_GEN_ADVANCED,
0, 0
};
const DWORD aAdvancedHelpIds[] = {
IDD_ITEMICON, NO_HELP,
IDC_MANAGEFILES_TXT, NO_HELP,
IDD_MANAGEFOLDERS_TXT, NO_HELP,
IDD_ARCHIVE, IDH_FPROP_GEN_ARCHIVE,
IDD_INDEX, IDH_FPROP_GEN_INDEX,
IDD_COMPRESS, IDH_FPROP_GEN_COMPRESSED,
IDD_ENCRYPT, IDH_FPROP_GEN_ENCRYPT,
0, 0
};
FOLDERCONTENTSINFO* Create_FolderContentsInfo()
{
FOLDERCONTENTSINFO *pfci = (FOLDERCONTENTSINFO*)LocalAlloc(LPTR, sizeof(*pfci));
if (pfci)
{
pfci->_cRef = 1;
}
return pfci;
}
void Free_FolderContentsInfoMembers(FOLDERCONTENTSINFO* pfci)
{
if (pfci->hida)
{
GlobalFree(pfci->hida);
pfci->hida = NULL;
}
}
LONG AddRef_FolderContentsInfo(FOLDERCONTENTSINFO* pfci)
{
ASSERTMSG(pfci != NULL, "AddRef_FolderContentsInfo: caller passed a null pfci");
if (pfci)
{
return InterlockedIncrement(&pfci->_cRef);
}
return 0;
}
LONG Release_FolderContentsInfo(FOLDERCONTENTSINFO* pfci)
{
if (pfci)
{
if (InterlockedDecrement(&pfci->_cRef))
{
return pfci->_cRef;
}
Free_FolderContentsInfoMembers(pfci);
LocalFree(pfci);
}
return 0;
}
void UpdateSizeCount(FILEPROPSHEETPAGE * pfpsp)
{
TCHAR szNum[32], szNum1[64];
LPTSTR pszFmt = ShellConstructMessageString(HINST_THISDLL,
MAKEINTRESOURCE(pfpsp->pfci->cbSize ? IDS_SIZEANDBYTES : IDS_SIZE),
ShortSizeFormat64(pfpsp->pfci->cbSize, szNum, ARRAYSIZE(szNum)),
AddCommas64(pfpsp->pfci->cbSize, szNum1, ARRAYSIZE(szNum1)));
if (pszFmt)
{
SetDlgItemText(pfpsp->hDlg, IDD_FILESIZE, pszFmt);
LocalFree(pszFmt);
}
pszFmt = ShellConstructMessageString(HINST_THISDLL,
MAKEINTRESOURCE(pfpsp->pfci->cbActualSize ? IDS_SIZEANDBYTES : IDS_SIZE),
ShortSizeFormat64(pfpsp->pfci->cbActualSize, szNum, ARRAYSIZE(szNum)),
AddCommas64(pfpsp->pfci->cbActualSize, szNum1, ARRAYSIZE(szNum1)));
if (pszFmt)
{
SetDlgItemText(pfpsp->hDlg, IDD_FILESIZE_COMPRESSED, pszFmt);
LocalFree(pszFmt);
}
pszFmt = ShellConstructMessageString(HINST_THISDLL,
MAKEINTRESOURCE(IDS_NUMFILES),
AddCommas(pfpsp->pfci->cFiles, szNum, ARRAYSIZE(szNum)),
AddCommas(pfpsp->pfci->cFolders, szNum1, ARRAYSIZE(szNum1)));
if (pszFmt && !pfpsp->fMountedDrive)
{
SetDlgItemText(pfpsp->hDlg, IDD_CONTAINS, pszFmt);
LocalFree(pszFmt);
}
}
STDAPI_(BOOL) HIDA_FillFindData(HIDA hida, UINT iItem, LPTSTR pszPath, WIN32_FIND_DATA *pfd, BOOL fReturnCompressedSize)
{
BOOL fRet = FALSE; // assume error
*pszPath = 0; // assume error
LPITEMIDLIST pidl = HIDA_ILClone(hida, iItem);
if (pidl)
{
if (SHGetPathFromIDList(pidl, pszPath))
{
if (pfd)
{
HANDLE h = FindFirstFile(pszPath, pfd);
if (h == INVALID_HANDLE_VALUE)
{
// error, zero the bits
ZeroMemory(pfd, sizeof(*pfd));
}
else
{
FindClose(h);
// if the user wants the compressed file size, and compression is supported, then go get it
if (fReturnCompressedSize && (pfd->dwFileAttributes & (FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_SPARSE_FILE)))
{
pfd->nFileSizeLow = SHGetCompressedFileSize(pszPath, &pfd->nFileSizeHigh);
}
}
}
fRet = TRUE;
}
ILFree(pidl);
}
return fRet;
}
DWORD CALLBACK SizeThreadProc(void *pv)
{
FOLDERCONTENTSINFO* pfci = (FOLDERCONTENTSINFO*)pv;
pfci->cbSize = 0;
pfci->cbActualSize = 0;
pfci->cFiles = 0;
pfci->cFolders = 0;
if (pfci->bContinue && pfci->hDlg)
{
// update the dialog every 1/4 second
SetTimer(pfci->hDlg, IDT_SIZE, 250, NULL);
}
TCHAR szPath[MAX_PATH];
for (UINT iItem = 0; HIDA_FillFindData(pfci->hida, iItem, szPath, &pfci->fd, FALSE) && pfci->bContinue; iItem++)
{
if (pfci->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
FolderSize(szPath, pfci);
if (pfci->fMultipleFiles)
{
// for multiple file/folder properties, count myself
pfci->cFolders++;
}
}
else
{ // file selected
ULARGE_INTEGER ulSize, ulSizeOnDisk;
DWORD dwClusterSize = PathGetClusterSize(szPath);
// if compression is supported, we check to see if the file is sparse or compressed
if (pfci->fIsCompressionAvailable && (pfci->fd.dwFileAttributes & (FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_SPARSE_FILE)))
{
ulSizeOnDisk.LowPart = SHGetCompressedFileSize(szPath, &ulSizeOnDisk.HighPart);
}
else
{
// not compressed or sparse, so just round to the cluster size
ulSizeOnDisk.LowPart = pfci->fd.nFileSizeLow;
ulSizeOnDisk.HighPart = pfci->fd.nFileSizeHigh;
ulSizeOnDisk.QuadPart = ROUND_TO_CLUSTER(ulSizeOnDisk.QuadPart, dwClusterSize);
}
// add the size in
ulSize.LowPart = pfci->fd.nFileSizeLow;
ulSize.HighPart = pfci->fd.nFileSizeHigh;
pfci->cbSize += ulSize.QuadPart;
// add the size on disk in
pfci->cbActualSize += ulSizeOnDisk.QuadPart;
// increment the # of files
pfci->cFiles++;
}
// set this so the progress bar knows how much total work there is to do
// ISSUE RAID BUG - 120446 - Need to Guard access to pfci->ulTotalNumberOfBytes.QuadParts
pfci->ulTotalNumberOfBytes.QuadPart = pfci->cbActualSize;
} // end of For Loop.
if (pfci->bContinue && pfci->hDlg)
{
KillTimer(pfci->hDlg, IDT_SIZE);
// make sure that there is a WM_TIMER message in the queue so we will get the "final" results
PostMessage(pfci->hDlg, WM_TIMER, (WPARAM)IDT_SIZE, (LPARAM)NULL);
}
pfci->fIsSizeThreadAlive = FALSE;
Release_FolderContentsInfo(pfci);
return 0;
}
DWORD CALLBACK SizeThread_AddRefCallBack(void *pv)
{
FOLDERCONTENTSINFO* pfci = (FOLDERCONTENTSINFO *)pv;
AddRef_FolderContentsInfo(pfci);
pfci->fIsSizeThreadAlive = TRUE;
return 0;
}
void CreateSizeThread(FILEPROPSHEETPAGE * pfpsp)
{
if (pfpsp->pfci->bContinue)
{
if (!pfpsp->pfci->fIsSizeThreadAlive)
{
SHCreateThread(SizeThreadProc, pfpsp->pfci, CTF_COINIT, SizeThread_AddRefCallBack);
}
else
{
// previous size thread still running, so bail
}
}
}
void KillSizeThread(FILEPROPSHEETPAGE * pfpsp)
{
// signal the thread to stop
pfpsp->pfci->bContinue = FALSE;
}
DWORD GetVolumeFlags(LPCTSTR pszPath, OUT OPTIONAL LPTSTR pszFileSys, int cchFileSys)
{
TCHAR szRoot[MAX_PATH];
/* Is this mounted point, e.g. c:\ or c:\hostfolder\ */
if (!PathGetMountPointFromPath(pszPath, szRoot, ARRAYSIZE(szRoot)))
{
//no
lstrcpyn(szRoot, pszPath, ARRAYSIZE(szRoot));
PathStripToRoot(szRoot);
}
// GetVolumeInformation requires a trailing backslash. Append one
PathAddBackslash(szRoot);
if (pszFileSys)
*pszFileSys = 0 ;
DWORD dwVolumeFlags;
if (GetVolumeInformation(szRoot, NULL, 0, NULL, NULL, &dwVolumeFlags, pszFileSys, cchFileSys))
{
return dwVolumeFlags;
}
else
{
return 0;
}
}
//
// This function sets the initial file attributes based on the dwFlagsAND / dwFlagsOR
// for the multiple file case
//
void SetInitialFileAttribs(FILEPROPSHEETPAGE* pfpsp, DWORD dwFlagsAND, DWORD dwFlagsOR)
{
DWORD dwTriState = dwFlagsAND ^ dwFlagsOR; // this dword now has all the bits that are in the BST_INDETERMINATE state
#ifdef DEBUG
// the pfpsp struct should have been zero inited, make sure that our ATTRIBUTESTATE
// structs are zero inited
ATTRIBUTESTATE asTemp = {0};
ASSERT(memcmp(&pfpsp->asInitial, &asTemp, sizeof(pfpsp->asInitial)) == 0);
#endif // DEBUG
// set the inital state based on the flags
if (dwTriState & FILE_ATTRIBUTE_READONLY)
{
pfpsp->asInitial.fReadOnly = BST_INDETERMINATE;
}
else if (dwFlagsAND & FILE_ATTRIBUTE_READONLY)
{
pfpsp->asInitial.fReadOnly = BST_CHECKED;
}
if (dwTriState & FILE_ATTRIBUTE_HIDDEN)
{
pfpsp->asInitial.fHidden = BST_INDETERMINATE;
}
else if (dwFlagsAND & FILE_ATTRIBUTE_HIDDEN)
{
pfpsp->asInitial.fHidden = BST_CHECKED;
}
if (dwTriState & FILE_ATTRIBUTE_ARCHIVE)
{
pfpsp->asInitial.fArchive = BST_INDETERMINATE;
}
else if (dwFlagsAND & FILE_ATTRIBUTE_ARCHIVE)
{
pfpsp->asInitial.fArchive = BST_CHECKED;
}
if (dwTriState & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
{
pfpsp->asInitial.fIndex = BST_INDETERMINATE;
}
else if (!(dwFlagsAND & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED))
{
pfpsp->asInitial.fIndex = BST_CHECKED;
}
if (dwTriState & FILE_ATTRIBUTE_COMPRESSED)
{
pfpsp->asInitial.fCompress = BST_INDETERMINATE;
}
else if (dwFlagsAND & FILE_ATTRIBUTE_COMPRESSED)
{
pfpsp->asInitial.fCompress = BST_CHECKED;
}
if (dwTriState & FILE_ATTRIBUTE_ENCRYPTED)
{
pfpsp->asInitial.fEncrypt = BST_INDETERMINATE;
}
else if (dwFlagsAND & FILE_ATTRIBUTE_ENCRYPTED)
{
pfpsp->asInitial.fEncrypt = BST_CHECKED;
}
}
//
// Updates the size fields for single and multiple file property sheets.
//
// NOTE: if you have the the WIN32_FIND_DATA already, then pass it for perf
//
STDAPI_(void) UpdateSizeField(FILEPROPSHEETPAGE* pfpsp, WIN32_FIND_DATA* pfd)
{
WIN32_FIND_DATA wfd;
if (pfpsp->pfci->fMultipleFiles)
{
// multiple selection case
// create the size and # of files thread
CreateSizeThread(pfpsp);
}
else
{
// if the caller didn't pass pfd, then go get the WIN32_FIND_DATA now
if (!pfd)
{
HANDLE hFind = FindFirstFile(pfpsp->szPath, &wfd);
if (hFind == INVALID_HANDLE_VALUE)
{
// if this failed we should clear out all the values as to not show garbage on the screen.
ZeroMemory(&wfd, sizeof(wfd));
}
else
{
FindClose(hFind);
}
pfd = &wfd;
}
if (pfpsp->fMountedDrive)
{
// mounted drive case
SetDateTimeText(pfpsp->hDlg, IDD_CREATED, &pfd->ftCreationTime);
}
else if (pfpsp->fIsDirectory)
{
// single folder case, in the UI we call this "Modified"
// but since NTFS updates ftModified when the contents of the
// folder changes (FAT does not) we use ftCreationTime as the
// stable end user notiion of "Modified"
SetDateTimeText(pfpsp->hDlg, IDD_CREATED, &pfd->ftCreationTime);
// create the size and # of files thread
CreateSizeThread(pfpsp);
}
else
{
TCHAR szNum1[MAX_COMMA_AS_K_SIZE];
TCHAR szNum2[MAX_COMMA_NUMBER_SIZE];
ULARGE_INTEGER ulSize = { pfd->nFileSizeLow, pfd->nFileSizeHigh };
DWORD dwClusterSize = PathGetClusterSize(pfpsp->szPath);
// fill in the "Size:" field
LPTSTR pszFmt = ShellConstructMessageString(HINST_THISDLL,
MAKEINTRESOURCE(ulSize.QuadPart ? IDS_SIZEANDBYTES : IDS_SIZE),
ShortSizeFormat64(ulSize.QuadPart, szNum1, ARRAYSIZE(szNum1)),
AddCommas64(ulSize.QuadPart, szNum2, ARRAYSIZE(szNum2)));
if (pszFmt)
{
SetDlgItemText(pfpsp->hDlg, IDD_FILESIZE, pszFmt);
LocalFree(pszFmt);
}
//
// fill in the "Size on disk:" field
//
if (pfd->dwFileAttributes & (FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_SPARSE_FILE))
{
// the file is compressed or sparse, so for "size on disk" use the compressed size
ulSize.LowPart = SHGetCompressedFileSize(pfpsp->szPath, &ulSize.HighPart);
}
else
{
// the file isint comrpessed so just round to the cluster size for the "size on disk"
ulSize.LowPart = pfd->nFileSizeLow;
ulSize.HighPart = pfd->nFileSizeHigh;
ulSize.QuadPart = ROUND_TO_CLUSTER(ulSize.QuadPart, dwClusterSize);
}
pszFmt = ShellConstructMessageString(HINST_THISDLL,
MAKEINTRESOURCE(ulSize.QuadPart ? IDS_SIZEANDBYTES : IDS_SIZE),
ShortSizeFormat64(ulSize.QuadPart, szNum1, ARRAYSIZE(szNum1)),
AddCommas64(ulSize.QuadPart, szNum2, ARRAYSIZE(szNum2)));
if (pszFmt && !pfpsp->fMountedDrive)
{
SetDlgItemText(pfpsp->hDlg, IDD_FILESIZE_COMPRESSED, pszFmt);
LocalFree(pszFmt);
}
//
// we always touch the file in the process of getting its info, so the
// ftLastAccessTime is always TODAY, which makes this field pretty useless...
// date and time
SetDateTimeText(pfpsp->hDlg, IDD_CREATED, &pfd->ftCreationTime);
SetDateTimeText(pfpsp->hDlg, IDD_LASTMODIFIED, &pfd->ftLastWriteTime);
{
// FAT implementation doesn't support last accessed time (gets the date right, but not the time),
// so we won't display it
DWORD dwFlags = FDTF_LONGDATE | FDTF_RELATIVE;
if (NULL == StrStrI(pfpsp->szFileSys, TEXT("FAT")))
dwFlags |= FDTF_LONGTIME; // for non FAT file systems
SetDateTimeTextEx(pfpsp->hDlg, IDD_LASTACCESSED, &pfd->ftLastAccessTime, dwFlags);
}
}
}
}
//
// Descriptions:
// This function fills fields of the multiple object property sheet.
//
BOOL InitMultiplePrsht(FILEPROPSHEETPAGE* pfpsp)
{
SHFILEINFO sfi;
TCHAR szBuffer[MAX_PATH+1];
BOOL fMultipleType = FALSE;
BOOL fSameLocation = TRUE;
DWORD dwFlagsOR = 0; // start all clear
DWORD dwFlagsAND = (DWORD)-1; // start all set
DWORD dwVolumeFlagsAND = (DWORD)-1; // start all set
TCHAR szType[MAX_PATH];
TCHAR szDirPath[MAX_PATH];
szDirPath[0] = 0;
szType[0] = 0;
// For all the selected files compare their types and get their attribs
for (int iItem = 0; HIDA_FillFindData(pfpsp->pfci->hida, iItem, szBuffer, NULL, FALSE); iItem++)
{
DWORD dwFileAttributes = GetFileAttributes(szBuffer);
dwFlagsAND &= dwFileAttributes;
dwFlagsOR |= dwFileAttributes;
// process types only if we haven't already found that there are several types
if (!fMultipleType)
{
SHGetFileInfo((LPTSTR)IDA_GetIDListPtr((LPIDA)GlobalLock(pfpsp->pfci->hida), iItem), 0,
&sfi, sizeof(sfi), SHGFI_PIDL|SHGFI_TYPENAME);
if (szType[0] == 0)
lstrcpyn(szType, sfi.szTypeName, ARRAYSIZE(szType));
else
fMultipleType = lstrcmp(szType, sfi.szTypeName) != 0;
}
dwVolumeFlagsAND &= GetVolumeFlags(szBuffer, pfpsp->szFileSys, ARRAYSIZE(pfpsp->szFileSys));
// check to see if the files are in the same location
if (fSameLocation)
{
PathRemoveFileSpec(szBuffer);
if (szDirPath[0] == 0)
lstrcpyn(szDirPath, szBuffer, ARRAYSIZE(szDirPath));
else
fSameLocation = (lstrcmpi(szDirPath, szBuffer) == 0);
}
}
if ((dwVolumeFlagsAND & FS_FILE_ENCRYPTION) && !SHRestricted(REST_NOENCRYPTION))
{
// all the files are on volumes that support encryption (eg NTFS)
pfpsp->fIsEncryptionAvailable = TRUE;
}
if (dwVolumeFlagsAND & FS_FILE_COMPRESSION)
{
pfpsp->pfci->fIsCompressionAvailable = TRUE;
}
//
// HACK (reinerf) - we dont have a FS_SUPPORTS_INDEXING so we
// use the FILE_SUPPORTS_SPARSE_FILES flag, because native index support
// appeared first on NTFS5 volumes, at the same time sparse file support
// was implemented.
//
if (dwVolumeFlagsAND & FILE_SUPPORTS_SPARSE_FILES)
{
// yup, we are on NTFS5 or greater
pfpsp->fIsIndexAvailable = TRUE;
}
// if any of the files was a directory, then we set this flag
if (dwFlagsOR & FILE_ATTRIBUTE_DIRECTORY)
{
pfpsp->fIsDirectory = TRUE;
}
// setup all the flags based on what we found out
SetInitialFileAttribs(pfpsp, dwFlagsAND, dwFlagsOR);
// set the current attributes to the same as the initial
pfpsp->asCurrent = pfpsp->asInitial;
//
// now setup all the controls on the dialog based on the attribs
// that we have
//
// check for multiple file types
if (fMultipleType)
{
LoadString(HINST_THISDLL, IDS_MULTIPLETYPES, szBuffer, ARRAYSIZE(szBuffer));
}
else
{
LoadString(HINST_THISDLL, IDS_ALLOFTYPE, szBuffer, ARRAYSIZE(szBuffer));
StrCatBuff(szBuffer, szType, ARRAYSIZE(szBuffer));
}
SetDlgItemText(pfpsp->hDlg, IDD_FILETYPE, szBuffer);
if (fSameLocation)
{
LoadString(HINST_THISDLL, IDS_ALLIN, szBuffer, ARRAYSIZE(szBuffer));
StrCatBuff(szBuffer, szDirPath, ARRAYSIZE(szBuffer));
lstrcpyn(pfpsp->szPath, szDirPath, ARRAYSIZE(pfpsp->szPath));
}
else
{
LoadString(HINST_THISDLL, IDS_VARFOLDERS, szBuffer, ARRAYSIZE(szBuffer));
}
//Keep Functionality same as NT4 by avoiding PathCompactPath.
SetDlgItemTextWithToolTip(pfpsp->hDlg, IDD_LOCATION, szBuffer, &pfpsp->hwndTip);
//
// check the ReadOnly and Hidden checkboxes, they always appear on the general tab
//
if (pfpsp->asInitial.fReadOnly == BST_INDETERMINATE)
{
SendDlgItemMessage(pfpsp->hDlg, IDD_READONLY, BM_SETSTYLE, BS_AUTO3STATE, 0);
}
CheckDlgButton(pfpsp->hDlg, IDD_READONLY, pfpsp->asCurrent.fReadOnly);
if (pfpsp->asInitial.fHidden == BST_INDETERMINATE)
{
SendDlgItemMessage(pfpsp->hDlg, IDD_HIDDEN, BM_SETSTYLE, BS_AUTO3STATE, 0);
}
CheckDlgButton(pfpsp->hDlg, IDD_HIDDEN, pfpsp->asCurrent.fHidden);
// to avoid people making SYSTEM files HIDDEN (SYSTEM HIDDEN files are
// never show to the user) we don't let people make SYSTEM files HIDDEN
if (dwFlagsOR & FILE_ATTRIBUTE_SYSTEM)
EnableWindow(GetDlgItem(pfpsp->hDlg, IDD_HIDDEN), FALSE);
// Archive is only on the general tab for FAT, otherwise it is under the "Advanced attributes"
// and FAT volumes dont have the "Advanced attributes" button.
if (pfpsp->pfci->fIsCompressionAvailable || pfpsp->fIsEncryptionAvailable)
{
// if compression is available, then we must be on NTFS
DestroyWindow(GetDlgItem(pfpsp->hDlg, IDD_ARCHIVE));
}
else
{
// we are on FAT/FAT32, so get rid of the "Advanced attributes" button, and set the inital Archive state
DestroyWindow(GetDlgItem(pfpsp->hDlg, IDC_ADVANCED));
if (pfpsp->asInitial.fArchive == BST_INDETERMINATE)
{
SendDlgItemMessage(pfpsp->hDlg, IDD_ARCHIVE, BM_SETSTYLE, BS_AUTO3STATE, 0);
}
CheckDlgButton(pfpsp->hDlg, IDD_ARCHIVE, pfpsp->asCurrent.fArchive);
}
UpdateSizeField(pfpsp, NULL);
return TRUE;
}
void Free_DlgDependentFilePropSheetPage(FILEPROPSHEETPAGE* pfpsp)
{
// this frees the members that are dependent on pfpsp->hDlg still
// being valid
if (pfpsp)
{
ASSERT(IsWindow(pfpsp->hDlg)); // our window had better still be valid!
ReplaceDlgIcon(pfpsp->hDlg, IDD_ITEMICON, NULL);
if (pfpsp->pfci && !pfpsp->pfci->fMultipleFiles)
{
// single-file specific members
if (!pfpsp->fIsDirectory)
{
// cleanup the typeicon for non-folders
ReplaceDlgIcon(pfpsp->hDlg, IDD_TYPEICON, NULL);
}
}
}
}
void Free_DlgIndepFilePropSheetPage(FILEPROPSHEETPAGE *pfpsp)
{
if (pfpsp)
{
IAssocStore* pas = (IAssocStore *)pfpsp->pAssocStore;
if (pas)
{
delete pas;
pfpsp->pAssocStore = NULL;
}
Release_FolderContentsInfo(pfpsp->pfci);
pfpsp->pfci = NULL;
ILFree(pfpsp->pidl);
pfpsp->pidl = NULL;
ILFree(pfpsp->pidlTarget);
pfpsp->pidlTarget = NULL;
}
}
//
// Descriptions:
// Callback for the property sheet code
//
UINT CALLBACK FilePrshtCallback(HWND hwnd, UINT uMsg, LPPROPSHEETPAGE ppsp)
{
if (uMsg == PSPCB_RELEASE)
{
FILEPROPSHEETPAGE * pfpsp = (FILEPROPSHEETPAGE *)ppsp;
// Careful! pfpsp can be NULL in low memory situations
if (pfpsp)
{
KillSizeThread(pfpsp);
Free_DlgIndepFilePropSheetPage(pfpsp);
}
}
return 1;
}
//
// DESCRIPTION:
//
// Opens the file for compression. It handles the case where a READONLY
// file is trying to be compressed or uncompressed. Since read only files
// cannot be opened for WRITE_DATA, it temporarily resets the file to NOT
// be READONLY in order to open the file, and then sets it back once the
// file has been compressed.
//
// Taken from WinFile module wffile.c without change. Originally from
// G. Kimura's compact.c. Now taken from shcompui without change.
//
// ARGUMENTS:
//
// phFile
// Address of file handle variable for handle of open file if
// successful.
//
// szFile
// Name string of file to be opened.
//
// RETURNS:
//
// TRUE = File successfully opened. Handle in *phFile.
// FALSE = File couldn't be opened. *phFile == INVALID_HANDLE_VALUE
//
///////////////////////////////////////////////////////////////////////////////
BOOL OpenFileForCompress(HANDLE *phFile, LPCTSTR szFile)
{
//
// Try to open the file - READ_DATA | WRITE_DATA.
//
if ((*phFile = CreateFile(szFile,
FILE_READ_DATA | FILE_WRITE_DATA,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
NULL)) != INVALID_HANDLE_VALUE)
{
//
// Successfully opened the file.
//
return TRUE;
}
if (GetLastError() != ERROR_ACCESS_DENIED)
{
return FALSE;
}
//
// Try to open the file - READ_ATTRIBUTES | WRITE_ATTRIBUTES.
//
HANDLE hAttr = CreateFile(szFile,
FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (hAttr == INVALID_HANDLE_VALUE)
{
return FALSE;
}
//
// See if the READONLY attribute is set.
//
BY_HANDLE_FILE_INFORMATION fi;
if ((!GetFileInformationByHandle(hAttr, &fi)) ||
(!(fi.dwFileAttributes & FILE_ATTRIBUTE_READONLY)))
{
//
// If the file could not be open for some reason other than that
// the readonly attribute was set, then fail.
//
CloseHandle(hAttr);
return FALSE;
}
//
// Turn OFF the READONLY attribute.
//
fi.dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
if (!SetFileAttributes(szFile, fi.dwFileAttributes))
{
CloseHandle(hAttr);
return FALSE;
}
//
// Try again to open the file - READ_DATA | WRITE_DATA.
//
*phFile = CreateFile(szFile,
FILE_READ_DATA | FILE_WRITE_DATA,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
//
// Close the file handle opened for READ_ATTRIBUTE | WRITE_ATTRIBUTE.
//
CloseHandle(hAttr);
//
// Make sure the open succeeded. If it still couldn't be opened with
// the readonly attribute turned off, then fail.
//
if (*phFile == INVALID_HANDLE_VALUE)
{
return FALSE;
}
//
// Turn the READONLY attribute back ON.
//
fi.dwFileAttributes |= FILE_ATTRIBUTE_READONLY;
if (!SetFileAttributes(szFile, fi.dwFileAttributes))
{
CloseHandle(*phFile);
*phFile = INVALID_HANDLE_VALUE;
return FALSE;
}
//
// Return success. A valid file handle is in *phFile.
//
return TRUE;
}
// One half second (500 ms = 0.5s)
#define ENCRYPT_RETRY_PERIOD 500
// Retry 4 times (at least 2s)
#define ENCRYPT_MAX_RETRIES 4
//
// This function encrypts/decrypts a file. If the readonly bit is set, the
// function will clear it and encrypt/decrypt and then set the RO bit back
// We will also remove/replace the system bit for known encryptable system
// files
//
// szPath a string that has the full path to the file
// fCompress TRUE - compress the file
// FALSE - decompress the file
//
//
// return: TRUE - the file was sucessfully encryped/decryped
// FALSE - the file could not be encryped/decryped
//
STDAPI_(BOOL) SHEncryptFile(LPCTSTR pszPath, BOOL fEncrypt)
{
BOOL bRet = fEncrypt ? EncryptFile(pszPath) : DecryptFile(pszPath, 0);
if (!bRet)
{
DWORD dwLastError = GetLastError();
DWORD dwAttribs = GetFileAttributes(pszPath);
// Check to see if the attributes are blocking the encryption and we can change them
if (dwAttribs & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY))
{
BOOL fStripAttribs = TRUE;
if (dwAttribs & FILE_ATTRIBUTE_SYSTEM)
{
fStripAttribs = FALSE;
// We can only strip attributes if it is a know encryptable system file
WCHAR szStream[MAX_PATH + 13];
StrCpyNW(szStream, pszPath, ARRAYSIZE(szStream));
StrCatW(szStream, TEXT(":encryptable"));
HANDLE hStream = CreateFile(szStream, GENERIC_READ, NULL, NULL, OPEN_EXISTING, NULL, NULL);
if (hStream != INVALID_HANDLE_VALUE)
{
CloseHandle(hStream);
fStripAttribs = TRUE;
}
}
if (fStripAttribs)
{
if (SetFileAttributes(pszPath, dwAttribs & ~FILE_ATTRIBUTE_READONLY & ~FILE_ATTRIBUTE_SYSTEM))
{
int i = 0;
while (!bRet && i < ENCRYPT_MAX_RETRIES)
{
bRet = fEncrypt ? EncryptFile(pszPath) : DecryptFile(pszPath, 0);
if (!bRet)
{
i++;
Sleep(ENCRYPT_RETRY_PERIOD);
}
}
SetFileAttributes(pszPath, dwAttribs);
}
}
}
// If we failed after all this, make sure we return the right error code
if (!bRet)
{
ASSERT(dwLastError != ERROR_SUCCESS);
SetLastError(dwLastError);
}
}
return bRet;
}
//
// This function compresses/uncompresses a file.
//
// szPath a string that has the full path to the file
// fCompress TRUE - compress the file
// FALSE - decompress the file
//
//
// return: TRUE - the file was sucessfully compressed/uncompressed
// FALSE - the file could not be compressed/uncompressed
//
BOOL CompressFile(LPCTSTR pzsPath, BOOL fCompress)
{
DWORD dwAttribs = GetFileAttributes(pzsPath);
if (dwAttribs & FILE_ATTRIBUTE_ENCRYPTED)
{
// We will fail to compress/decompress the file if is encryped. We don't want
// to bother the user w/ error messages in this case (since encryption "takes
// presidence" over compression), so we just return success.
return TRUE;
}
HANDLE hFile;
if (OpenFileForCompress(&hFile, pzsPath))
{
USHORT uState = fCompress ? COMPRESSION_FORMAT_DEFAULT : COMPRESSION_FORMAT_NONE;
ULONG Length;
BOOL bRet = DeviceIoControl(hFile,
FSCTL_SET_COMPRESSION,
&uState,
sizeof(USHORT),
NULL,
0,
&Length,
FALSE);
CloseHandle(hFile);
return bRet;
}
else
{
// couldnt get a file handle
return FALSE;
}
}
BOOL IsValidFileName(LPCTSTR pszFileName)
{
if (!pszFileName || !pszFileName[0])
{
return FALSE;
}
LPCTSTR psz = pszFileName;
do
{
// we are only passed the file name, so its ok to use PIVC_LFN_NAME
if (!PathIsValidChar(*psz, PIVC_LFN_NAME))
{
// found a non-legal character
return FALSE;
}
psz = CharNext(psz);
}
while (*psz);
// didn't find any illegal characters
return TRUE;
}
// renames the file, or checks to see if it could be renamed if fCommit == FALSE
BOOL ApplyRename(FILEPROPSHEETPAGE* pfpsp, BOOL fCommit)
{
ASSERT(pfpsp->fRename);
TCHAR szNewName[MAX_PATH];
Edit_GetText(GetDlgItem(pfpsp->hDlg, IDD_NAMEEDIT), szNewName, ARRAYSIZE(szNewName));
if (StrCmpC(pfpsp->szInitialName, szNewName) != 0)
{
// the name could be changed from C:\foo.txt to C:\FOO.txt, this is
// technically the same name to PathFileExists, but we should allow it
// anyway
BOOL fCaseChange = (lstrcmpi(pfpsp->szInitialName, szNewName) == 0);
// get the dir where the file lives
TCHAR szDir[MAX_PATH];
lstrcpyn(szDir, pfpsp->szPath, ARRAYSIZE(szDir));
PathRemoveFileSpec(szDir);
// find out the old name with the extension (we cant use pfpsp->szInitialName here,
// because it might not have had the extension depending on the users view|options settings)
LPCTSTR pszOldName = PathFindFileName(pfpsp->szPath);
if (!pfpsp->fShowExtension)
{
// the extension is hidden, so add it to the new path the user typed
LPCTSTR pszExt = PathFindExtension(pfpsp->szPath);
if (*pszExt)
{
// Note that we can't call PathAddExtension, because it removes the existing extension.
lstrcatn(szNewName, pszExt, ARRAYSIZE(szNewName));
}
}
// is this a test or is it the real thing? (test needed so we can put up error UI before we get
// the PSN_LASTCHANCEAPPLY)
if (fCommit)
{
if (SHRenameFileEx(pfpsp->hDlg, NULL, szDir, pszOldName, szNewName) == ERROR_SUCCESS)
{
SHChangeNotify(SHCNE_RENAMEITEM, SHCNF_FLUSH | SHCNF_PATH, pszOldName, szNewName);
}
else
{
return FALSE; // dont need error ui because SHRenameFile takes care of that for us.
}
}
else
{
TCHAR szNewPath[MAX_PATH];
PathCombine(szNewPath, szDir, szNewName);
if (!IsValidFileName(szNewName) || (PathFileExists(szNewPath) && !fCaseChange))
{
LRESULT lRet = SHRenameFileEx(pfpsp->hDlg, NULL, szDir, pszOldName, szNewName);
if (lRet == ERROR_SUCCESS)
{
// Whoops, I guess we really CAN rename the file (this case can happen if the user
// tries to add a whole bunch of .'s to the end of a folder name).
// Rename it back so we can succeed when we call this fn. again with fCommit = TRUE;
lRet = SHRenameFileEx(NULL, NULL, szDir, szNewName, pszOldName);
ASSERT(lRet == ERROR_SUCCESS);
return TRUE;
}
// SHRenameFileEx put up the error UI for us, so just return false.
return FALSE;
}
}
// we dont bother doing anything if the rename succeeded since we only do renames
// if the dialog is about to close (user hit "ok")
}
return TRUE;
}
//
// this is the dlg proc for Attribute Errors
//
// returns
//
// IDCANCEL - user clicked abort
// IDRETRY - user clicked retry
// IDIGNORE - user clicked ignore
// IDIGNOREALL - user clikced ignore all
//
BOOL_PTR CALLBACK FailedApplyAttribDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
{
ATTRIBUTEERROR* pae = (ATTRIBUTEERROR*)lParam;
TCHAR szPath[MAX_PATH];
lstrcpyn(szPath, pae->pszPath, ARRAYSIZE(szPath));
// Modify very long path names so that they fit into the message box.
// get the size of the text boxes
RECT rc;
GetWindowRect(GetDlgItem(hDlg, IDD_NAME), &rc);
PathCompactPath(NULL, szPath, rc.right - rc.left);
SetDlgItemText(hDlg, IDD_NAME, szPath);
// Default message if FormatMessage doesn't recognize dwLastError
TCHAR szTemplate[MAX_PATH];
LoadString(HINST_THISDLL, IDS_UNKNOWNERROR, szTemplate, ARRAYSIZE(szTemplate));
TCHAR szErrorMsg[MAX_PATH];
wnsprintf(szErrorMsg, ARRAYSIZE(szErrorMsg), szTemplate, pae->dwLastError);
// Try the system error message
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, pae->dwLastError, 0, szErrorMsg, ARRAYSIZE(szErrorMsg), NULL);
SetDlgItemText(hDlg, IDD_ERROR_TXT, szErrorMsg);
EnableWindow(hDlg, TRUE);
break;
}
case WM_COMMAND:
{
UINT uCtrlID = GET_WM_COMMAND_ID(wParam, lParam);
switch (uCtrlID)
{
case IDIGNOREALL: // = 10 (this comes from shell32.rc, the rest come from winuser.h)
case IDCANCEL: // = 2
case IDRETRY: // = 4
case IDIGNORE: // = 5
EndDialog(hDlg, uCtrlID);
return TRUE;
break;
default:
return FALSE;
}
break;
}
default :
return FALSE;
}
return FALSE;
}
//
// This function displays the "and error has occured [abort] [retry] [ignore] [ignore all]" message
// If the user hits abort, then we return FALSE so that our caller knows to abort the operation
//
// returns the id of the button pressed (one of: IDIGNOREALL, IDIGNORE, IDCANCEL, IDRETRY)
//
int FailedApplyAttribsErrorDlg(HWND hWndParent, ATTRIBUTEERROR* pae)
{
// Put up the error message box - ABORT, RETRY, IGNORE, IGNORE ALL.
int iRet = (int)DialogBoxParam(HINST_THISDLL,
MAKEINTRESOURCE(DLG_ATTRIBS_ERROR),
hWndParent,
FailedApplyAttribDlgProc,
(LPARAM)pae);
//
// if the user hits the ESC key or the little X thingy, then
// iRet = 0, so we set iRet = IDCANCEL
//
if (!iRet)
{
iRet = IDCANCEL;
}
return iRet;
}
//
// we check to see if this is a known bad file that we skip applying attribs to
//
BOOL IsBadAttributeFile(LPCTSTR pszFile, FILEPROPSHEETPAGE* pfpsp)
{
const static LPTSTR s_rgszBadFiles[] = {
{TEXT("pagefile.sys")},
{TEXT("hiberfil.sys")},
{TEXT("ntldr")},
{TEXT("ntdetect.com")},
{TEXT("explorer.exe")},
{TEXT("System Volume Information")},
{TEXT("cmldr")},
{TEXT("desktop.ini")},
{TEXT("ntuser.dat")},
{TEXT("ntuser.dat.log")},
{TEXT("ntuser.pol")},
{TEXT("usrclass.dat")},
{TEXT("usrclass.dat.log")}};
LPTSTR pszFileName = PathFindFileName(pszFile);
for (int i = 0; i < ARRAYSIZE(s_rgszBadFiles); i++)
{
if (lstrcmpi(s_rgszBadFiles[i], pszFileName) == 0)
{
// this file matched on of the "bad" files that we dont apply attributes to
return TRUE;
}
}
// ok to muck with this file
return FALSE;
}
// This is the encryption warning callback dlg proc
BOOL_PTR CALLBACK EncryptionWarningDlgProc(HWND hDlgWarning, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
{
LPCTSTR pszPath = (LPCTSTR)lParam;
SetWindowPtr(hDlgWarning, DWLP_USER, (void*) pszPath);
// set the initial state of the radio buttons
CheckDlgButton(hDlgWarning, IDC_ENCRYPT_PARENTFOLDER, TRUE);
break;
}
case WM_COMMAND:
{
if ((LOWORD(wParam) == IDOK) && (IsDlgButtonChecked(hDlgWarning, IDC_ENCRYPT_PARENTFOLDER) == BST_CHECKED))
{
LPTSTR pszPath = (LPTSTR) GetWindowPtr(hDlgWarning, DWLP_USER);
if (pszPath)
{
LPITEMIDLIST pidl = ILCreateFromPath(pszPath);
if (pidl)
{
SHChangeNotifySuspendResume(TRUE, pidl, TRUE, 0);
}
RetryEncryptParentFolder:
if (!SHEncryptFile(pszPath, TRUE))
{
ATTRIBUTEERROR ae = {pszPath, GetLastError()};
if (FailedApplyAttribsErrorDlg(hDlgWarning, &ae) == IDRETRY)
{
goto RetryEncryptParentFolder;
}
}
if (pidl)
{
SHChangeNotifySuspendResume(FALSE, pidl, TRUE, 0);
ILFree(pidl);
}
}
}
break;
}
}
// we want the MessageBoxCheckExDlgProc have a crack at everything as well,
// so return false here
return FALSE;
}
//
// This function warns the user that they are encrypting a file that is not in and encrypted
// folder. Most editors (MS word included), do a "safe-save" where they rename the file being
// edited, and then save the new modified version out, and then delete the old original. This
// causes an encrypted document that is NOT in an encrypted folder to become decrypted so we
// warn the user here.
//
// returns:
// TRUE - the user hit "ok" (either compress just the file, or the parent folder as well)
// FALSE - the user hit "cancel"
//
int WarnUserAboutDecryptedParentFolder(LPCTSTR pszPath, HWND hWndParent)
{
// check for the root case (no parent), or the directory case
if (PathIsRoot(pszPath) || PathIsDirectory(pszPath))
return TRUE;
int iRet = IDOK; // assume everything is okidokey
// first check to see if the parent folder is encrypted
TCHAR szParentFolder[MAX_PATH];
lstrcpyn(szParentFolder, pszPath, ARRAYSIZE(szParentFolder));
PathRemoveFileSpec(szParentFolder);
DWORD dwAttribs = GetFileAttributes(szParentFolder);
if ((dwAttribs != (DWORD)-1) && !(dwAttribs & FILE_ATTRIBUTE_ENCRYPTED) && !PathIsRoot(szParentFolder))
{
// the parent folder is NOT encrypted and the parent folder isin't the root, so warn the user
iRet = SHMessageBoxCheckEx(hWndParent, HINST_THISDLL, MAKEINTRESOURCE(DLG_ENCRYPTWARNING), EncryptionWarningDlgProc,
(void *)szParentFolder, IDOK, TEXT("EncryptionWarning"));
}
return (iRet == IDOK);
}
//
// Sets attributes of a file based on the info in pfpsp
//
// szFilename - the name of the file to compress
//
// pfpsp - the filepropsheetpage info
//
// hwndParent - Parent hwnd in case we need to put up some ui
//
// pbSomethingChanged - pointer to a bool that says whether or not something actually was
// changed during the operation.
// TRUE - we applied at leaset one attribute
// FALSE - we didnt change anything (either an error or all the attribs already matched)
//
// return value: TRUE - the operation was sucessful
// FALSE - there was an error and the user hit cancel to abort the operation
//
//
// NOTE: the caller of this function must take care of generating the SHChangeNotifies so that
// we dont end up blindly sending them for every file in a dir (the caller will send
// one for just that dir). That is why we have the pbSomethingChanged variable.
//
STDAPI_(BOOL) ApplyFileAttributes(LPCTSTR pszPath, FILEPROPSHEETPAGE* pfpsp, HWND hwndParent, BOOL* pbSomethingChanged)
{
DWORD dwLastError = ERROR_SUCCESS;
BOOL bCallSetFileAttributes = FALSE;
LPITEMIDLIST pidl = NULL;
// assume nothing changed to start with
*pbSomethingChanged = 0;
if ((pfpsp->fRecursive || pfpsp->pfci->fMultipleFiles) && IsBadAttributeFile(pszPath, pfpsp))
{
// we are doing a recursive operation or a multiple file operation, so we skip files
// that we dont want to to mess with because they will ususally give error dialogs
if (pfpsp->pProgressDlg)
{
// since we are skipping this file, we subtract its size from both
// ulTotal and ulCompleted. This will make sure the progress bar isint
// messed up by files like pagefile.sys who are huge but get "compressed"
// in milliseconds.
ULARGE_INTEGER ulTemp;
ulTemp.LowPart = pfpsp->fd.nFileSizeLow;
ulTemp.HighPart = pfpsp->fd.nFileSizeHigh;
// guard against underflow
if (pfpsp->ulNumberOfBytesDone.QuadPart < ulTemp.QuadPart)
{
pfpsp->ulNumberOfBytesDone.QuadPart = 0;
}
else
{
pfpsp->ulNumberOfBytesDone.QuadPart -= ulTemp.QuadPart;
}
pfpsp->pfci->ulTotalNumberOfBytes.QuadPart -= ulTemp.QuadPart;
UpdateProgressBar(pfpsp);
}
// return telling the user everying is okidokey
return TRUE;
}
RetryApplyAttribs:
DWORD dwInitialAttributes = GetFileAttributes(pszPath);
if (dwInitialAttributes == -1)
{
// we were unable to get the file attribues, doh!
dwLastError = GetLastError();
goto RaiseErrorMsg;
}
if (pfpsp->pProgressDlg)
{
// update the progress dialog file name
SetProgressDlgPath(pfpsp, pszPath, TRUE);
}
//
// we only allow attribs that SetFileAttributes can handle
//
DWORD dwNewAttributes = (dwInitialAttributes & (FILE_ATTRIBUTE_READONLY |
FILE_ATTRIBUTE_HIDDEN |
FILE_ATTRIBUTE_ARCHIVE |
FILE_ATTRIBUTE_OFFLINE |
FILE_ATTRIBUTE_SYSTEM |
FILE_ATTRIBUTE_TEMPORARY |
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED));
BOOL bIsSuperHidden = IS_SYSTEM_HIDDEN(dwInitialAttributes);
if (pfpsp->asInitial.fReadOnly != pfpsp->asCurrent.fReadOnly)
{
// don't allow changing of folders read only bit, since this is a trigger
// for shell special folder stuff like thumbnails, etc.
if (!(dwInitialAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
if (pfpsp->asCurrent.fReadOnly)
{
dwNewAttributes |= FILE_ATTRIBUTE_READONLY;
}
else
{
dwNewAttributes &= ~FILE_ATTRIBUTE_READONLY;
}
bCallSetFileAttributes = TRUE;
}
}
//
// don't allow setting of hidden on system files, as this will make them disappear for good.
//
if (pfpsp->asInitial.fHidden != pfpsp->asCurrent.fHidden && !(dwNewAttributes & FILE_ATTRIBUTE_SYSTEM))
{
if (pfpsp->asCurrent.fHidden)
{
dwNewAttributes |= FILE_ATTRIBUTE_HIDDEN;
}
else
{
dwNewAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
}
bCallSetFileAttributes = TRUE;
}
if (pfpsp->asInitial.fArchive != pfpsp->asCurrent.fArchive)
{
if (pfpsp->asCurrent.fArchive)
{
dwNewAttributes |= FILE_ATTRIBUTE_ARCHIVE;
}
else
{
dwNewAttributes &= ~FILE_ATTRIBUTE_ARCHIVE;
}
bCallSetFileAttributes = TRUE;
}
if (pfpsp->asInitial.fIndex != pfpsp->asCurrent.fIndex)
{
if (pfpsp->asCurrent.fIndex)
{
dwNewAttributes &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
}
else
{
dwNewAttributes |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
}
bCallSetFileAttributes = TRUE;
}
// did something change that we need to call SetFileAttributes for?
if (bCallSetFileAttributes)
{
if (SetFileAttributes(pszPath, dwNewAttributes))
{
// success! set fSomethingChanged so we know to send out
// a changenotify
*pbSomethingChanged = TRUE;
}
else
{
// get the last error value now so we know why it failed
dwLastError = GetLastError();
goto RaiseErrorMsg;
}
}
// We need to be careful about the order we compress/encrypt in since these
// operations are mutually exclusive.
// We therefore do the uncompressing/decrypting first
if ((pfpsp->asInitial.fCompress != pfpsp->asCurrent.fCompress) &&
(pfpsp->asCurrent.fCompress == BST_UNCHECKED))
{
if (!CompressFile(pszPath, FALSE))
{
// get the last error value now so we know why it failed
dwLastError = GetLastError();
goto RaiseErrorMsg;
}
else
{
// success
*pbSomethingChanged = TRUE;
}
}
if ((pfpsp->asInitial.fEncrypt != pfpsp->asCurrent.fEncrypt) &&
(pfpsp->asCurrent.fEncrypt == BST_UNCHECKED))
{
BOOL fSucceeded = SHEncryptFile(pszPath, FALSE); // try to decrypt the file
if (!fSucceeded)
{
// get the last error value now so we know why it failed
dwLastError = GetLastError();
if (ERROR_SHARING_VIOLATION == dwLastError)
{
// Encrypt/Decrypt needs exclusive access to the file, this is a problem if we
// initiate encrypt for a folder from Explorer, then most probably the folder will
// be opened. We don't do "SHChangeNotifySuspendResume" right away for perf reasons,
// we wait for it to fail and then we try again. (stephstm)
ASSERT(pidl == NULL);
pidl = ILCreateFromPath(pszPath);
if (pidl)
{
SHChangeNotifySuspendResume(TRUE, pidl, TRUE, 0);
}
// retry to decrypt after the suspend
fSucceeded = SHEncryptFile(pszPath, FALSE);
if (!fSucceeded)
{
// get the last error value now so we know why it failed
dwLastError = GetLastError();
}
}
}
if (fSucceeded)
{
// success
*pbSomethingChanged = TRUE;
dwLastError = ERROR_SUCCESS;
}
else
{
ASSERT(dwLastError != ERROR_SUCCESS);
goto RaiseErrorMsg;
}
}
// now check for encrypt/compress
if ((pfpsp->asInitial.fCompress != pfpsp->asCurrent.fCompress) &&
(pfpsp->asCurrent.fCompress == BST_CHECKED))
{
if (!CompressFile(pszPath, TRUE))
{
// get the last error value now so we know why it failed
dwLastError = GetLastError();
goto RaiseErrorMsg;
}
else
{
// success
*pbSomethingChanged = TRUE;
}
}
if ((pfpsp->asInitial.fEncrypt != pfpsp->asCurrent.fEncrypt) &&
(pfpsp->asCurrent.fEncrypt == BST_CHECKED))
{
// only prompt for encrypting the parent folder on non-recursive operations
if (!pfpsp->fRecursive && !WarnUserAboutDecryptedParentFolder(pszPath, hwndParent))
{
// user cancled the operation
return FALSE;
}
BOOL fSucceeded = SHEncryptFile(pszPath, TRUE); // try to encrypt the file
if (!fSucceeded)
{
// get the last error value now so we know why it failed
dwLastError = GetLastError();
if (ERROR_SHARING_VIOLATION == dwLastError)
{
// Encrypt/Decrypt needs exclusive access to the file, this is a problem if we
// initiate encrypt for a folder from Explorer, then most probably the folder will
// be opened. We don't do "SHChangeNotifySuspendResume" right away for perf reasons,
// we wait for it to fail and then we try again. (stephstm)
ASSERT(pidl == NULL);
pidl = ILCreateFromPath(pszPath);
if (pidl)
{
SHChangeNotifySuspendResume(TRUE, pidl, TRUE, 0);
}
// retry to encrypt after the suspend
fSucceeded = SHEncryptFile(pszPath, TRUE);
if (!fSucceeded)
{
// get the last error value now so we know why it failed
dwLastError = GetLastError();
}
}
}
if (fSucceeded)
{
// success
*pbSomethingChanged = TRUE;
dwLastError = ERROR_SUCCESS;
}
else
{
ASSERT(dwLastError != ERROR_SUCCESS);
goto RaiseErrorMsg;
}
}
RaiseErrorMsg:
if (pidl)
{
SHChangeNotifySuspendResume(FALSE, pidl, TRUE, 0);
ILFree(pidl);
pidl = NULL;
}
// if we are ignoring all errors or we dont have an hwnd to use as a parent,
// then dont show any error msgs.
if (pfpsp->fIgnoreAllErrors || !hwndParent)
{
dwLastError = ERROR_SUCCESS;
}
// If kernel threw up an error dialog (such as "the disk is write proctected")
// and the user hit "abort" then return false to avoid a second error dialog
if (dwLastError == ERROR_REQUEST_ABORTED)
{
return FALSE;
}
// put up the error dlg if necessary, but not for super hidden files
if (dwLastError != ERROR_SUCCESS)
{
// !PathIsRoot is required, since the root path (eg c:\) is superhidden by default even after formatting a drive,
// why the filesystem thinks that the root should be +s +r after a format is a mystery to me...
if (bIsSuperHidden && !ShowSuperHidden() && !PathIsRoot(pszPath))
{
dwLastError = ERROR_SUCCESS;
}
else
{
ATTRIBUTEERROR ae;
ae.pszPath = pszPath;
ae.dwLastError = dwLastError;
int iRet = FailedApplyAttribsErrorDlg(hwndParent, &ae);
switch (iRet)
{
case IDRETRY:
// we clear out dwError and try again
dwLastError = ERROR_SUCCESS;
goto RetryApplyAttribs;
break;
case IDIGNOREALL:
pfpsp->fIgnoreAllErrors = TRUE;
dwLastError = ERROR_SUCCESS;
break;
case IDIGNORE:
dwLastError = ERROR_SUCCESS;
break;
case IDCANCEL:
default:
break;
}
}
}
// update the progress bar
if (pfpsp->pProgressDlg)
{
ULARGE_INTEGER ulTemp;
// it is the callers responsibility to make sure that pfpsp->fd is filled with
// the proper information for the file we are applying attributes to.
ulTemp.LowPart = pfpsp->fd.nFileSizeLow;
ulTemp.HighPart = pfpsp->fd.nFileSizeHigh;
pfpsp->ulNumberOfBytesDone.QuadPart += ulTemp.QuadPart;
UpdateProgressBar(pfpsp);
}
return (dwLastError == ERROR_SUCCESS) ? TRUE : FALSE;
}
//
// Set the text of a dialog item and attach a tooltip if necessary.
//
STDAPI_(void) SetDlgItemTextWithToolTip(HWND hDlg, UINT id, LPCTSTR pszText, HWND *phwnd)
{
HWND hwnd = GetDlgItem(hDlg, id);
if (hwnd)
{
SetWindowText(hwnd, pszText);
RECT rc;
HDC hDC;
if (GetClientRect(hwnd, &rc) && (hDC = GetDC(hDlg)) != NULL)
{
HFONT hFont = GetWindowFont(hwnd);
if (hFont)
{
// set the dlg font into the DC so we can calc the size
hFont = (HFONT)SelectObject(hDC, hFont);
SIZE size = {0};
GetTextExtentPoint32(hDC, pszText, lstrlen(pszText), &size);
// restore the prev. hFont
SelectObject(hDC, hFont);
if (size.cx > rc.right)
{
// our text size is bigger than the dlg width, so its clipped
if (*phwnd == NULL)
{
*phwnd = CreateWindow(TOOLTIPS_CLASS,
c_szNULL,
WS_POPUP | TTS_NOPREFIX,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
hDlg,
NULL,
HINST_THISDLL,
NULL);
}
if (*phwnd)
{
TOOLINFO ti;
ti.cbSize = sizeof(ti);
ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
ti.hwnd = hDlg;
ti.uId = (UINT_PTR)hwnd;
ti.lpszText = (LPTSTR)pszText; // const -> non const
ti.hinst = HINST_THISDLL;
SendMessage(*phwnd, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti);
}
}
}
ReleaseDC(hDlg, hDC);
}
}
}
void UpdateTriStateCheckboxes(FILEPROPSHEETPAGE* pfpsp)
{
// we turn off tristate after applying attibs for those things that were tri-state
// initially but are not anymore since we sucessfully applied the attributes
if (pfpsp->hDlg)
{
if (pfpsp->asInitial.fReadOnly == BST_INDETERMINATE && pfpsp->asCurrent.fReadOnly != BST_INDETERMINATE)
{
SendDlgItemMessage(pfpsp->hDlg, IDD_READONLY, BM_SETSTYLE, BS_AUTOCHECKBOX, 0);
}
if (pfpsp->asInitial.fHidden == BST_INDETERMINATE && pfpsp->asCurrent.fHidden != BST_INDETERMINATE)
{
SendDlgItemMessage(pfpsp->hDlg, IDD_HIDDEN, BM_SETSTYLE, BS_AUTOCHECKBOX, 0);
}
// Archive is only on the general tab for files on FAT/FAT32 volumes
if (!pfpsp->pfci->fIsCompressionAvailable && pfpsp->asInitial.fArchive == BST_INDETERMINATE && pfpsp->asCurrent.fArchive != BST_INDETERMINATE)
{
SendDlgItemMessage(pfpsp->hDlg, IDD_ARCHIVE, BM_SETSTYLE, BS_AUTOCHECKBOX, 0);
}
}
}
//
// This applies the attributes to the selected files (multiple file case)
//
// return value:
// TRUE We sucessfully applied all the attributes
// FALSE The user hit cancel, and we stoped
//
STDAPI_(BOOL) ApplyMultipleFileAttributes(FILEPROPSHEETPAGE* pfpsp)
{
BOOL bRet = FALSE;
// create the progress dialog. This may fail if out of memory. If it does fail, we will
// abort the operation because it will also probably fail if out of memory.
if (CreateAttributeProgressDlg(pfpsp))
{
BOOL bSomethingChanged = FALSE;
bRet = TRUE;
// make sure that HIDA_FillFindDatat returns the compressed size, else our progress est will be way off
TCHAR szPath[MAX_PATH];
for (int iItem = 0; HIDA_FillFindData(pfpsp->pfci->hida, iItem, szPath, &pfpsp->fd, TRUE); iItem++)
{
if (HasUserCanceledAttributeProgressDlg(pfpsp))
{
// the user hit cancel on the progress dlg, so stop
bRet = FALSE;
break;
}
if (pfpsp->fRecursive && (pfpsp->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
// apply attribs to the subfolders
bRet = ApplyRecursiveFolderAttribs(szPath, pfpsp);
// send out a notification for the whole dir, regardless if the user hit cancel since
// something could have changed
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, szPath, NULL);
}
else
{
HWND hwndParent = NULL;
// if we have a progress hwnd, try to use it as our parent. This will fail
// if the progress dialog isn't being displayed yet.
IUnknown_GetWindow((IUnknown*)pfpsp->pProgressDlg, &hwndParent);
if (!hwndParent)
{
// the progress dlg isint here yet, so use the property page hwnd
hwndParent = GetParent(pfpsp->hDlg);
}
// apply the attribs to this item only
bRet = ApplyFileAttributes(szPath, pfpsp, hwndParent, &bSomethingChanged);
if (bSomethingChanged)
{
// something changed, so send out a notification for that file
DeleteFileThumbnail(szPath);
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szPath, NULL);
}
}
}
// destroy the progress dialog
DestroyAttributeProgressDlg(pfpsp);
if (bRet)
{
// since we just sucessfully applied attribs, reset any tri-state checkboxes as necessary
UpdateTriStateCheckboxes(pfpsp);
// the user did NOT hit cancel, so update the prop sheet to reflect the new attribs
pfpsp->asInitial = pfpsp->asCurrent;
}
// flush any change-notifications we generated
SHChangeNotifyHandleEvents();
}
return bRet;
}
STDAPI_(BOOL) ApplySingleFileAttributes(FILEPROPSHEETPAGE* pfpsp)
{
BOOL bRet = TRUE;
BOOL bSomethingChanged = FALSE;
if (!pfpsp->fRecursive)
{
bRet = ApplyFileAttributes(pfpsp->szPath, pfpsp, GetParent(pfpsp->hDlg), &bSomethingChanged);
if (bSomethingChanged)
{
// something changed, so generate a notification for the item
DeleteFileThumbnail(pfpsp->szPath);
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, pfpsp->szPath, NULL);
}
}
else
{
// We only should be doing a recursive operation if we have a directory!
ASSERT(pfpsp->fIsDirectory);
// create the progress dialog. This may fail if out of memory. If it does fail, we will
// abort the operation because it will also probably fail if out of memory.
if (CreateAttributeProgressDlg(pfpsp))
{
// apply attribs to this folder & sub files/folders
bRet = ApplyRecursiveFolderAttribs(pfpsp->szPath, pfpsp);
// HACKHACK: send out a notification for the item so that defview will refresh properly
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, pfpsp->szPath, NULL);
// send out a notification for the whole dir, regardless of the return value since
// something could have changed even if the user hit cancel
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, pfpsp->szPath, NULL);
DestroyAttributeProgressDlg(pfpsp);
}
else
{
bRet = FALSE;
}
}
if (bRet)
{
// since we just sucessfully applied attribs, reset any tri-state checkboxes as necessary
UpdateTriStateCheckboxes(pfpsp);
// the user did NOT hit cancel, so update the prop sheet to reflect the new attribs
pfpsp->asInitial = pfpsp->asCurrent;
// (reinerf) need to update the size fields (eg file was just compressed)
}
// handle any events we may have generated
SHChangeNotifyHandleEvents();
return bRet;
}
//
// this function sets the string that tells the user what attributes they are about to
// apply
//
BOOL SetAttributePromptText(HWND hDlgRecurse, FILEPROPSHEETPAGE* pfpsp)
{
TCHAR szAttribsToApply[MAX_PATH];
TCHAR szTemp[MAX_PATH];
szAttribsToApply[0] = 0;
if (pfpsp->asInitial.fReadOnly != pfpsp->asCurrent.fReadOnly)
{
if (pfpsp->asCurrent.fReadOnly)
EVAL(LoadString(HINST_THISDLL, IDS_READONLY, szTemp, ARRAYSIZE(szTemp)));
else
EVAL(LoadString(HINST_THISDLL, IDS_NOTREADONLY, szTemp, ARRAYSIZE(szTemp)));
StrCatBuff(szAttribsToApply, szTemp, ARRAYSIZE(szAttribsToApply));
}
if (pfpsp->asInitial.fHidden != pfpsp->asCurrent.fHidden)
{
if (pfpsp->asCurrent.fHidden)
EVAL(LoadString(HINST_THISDLL, IDS_HIDE, szTemp, ARRAYSIZE(szTemp)));
else
EVAL(LoadString(HINST_THISDLL, IDS_UNHIDE, szTemp, ARRAYSIZE(szTemp)));
StrCatBuff(szAttribsToApply, szTemp, ARRAYSIZE(szAttribsToApply));
}
if (pfpsp->asInitial.fArchive != pfpsp->asCurrent.fArchive)
{
if (pfpsp->asCurrent.fArchive)
EVAL(LoadString(HINST_THISDLL, IDS_ARCHIVE, szTemp, ARRAYSIZE(szTemp)));
else
EVAL(LoadString(HINST_THISDLL, IDS_UNARCHIVE, szTemp, ARRAYSIZE(szTemp)));
StrCatBuff(szAttribsToApply, szTemp, ARRAYSIZE(szAttribsToApply));
}
if (pfpsp->asInitial.fIndex != pfpsp->asCurrent.fIndex)
{
if (pfpsp->asCurrent.fIndex)
EVAL(LoadString(HINST_THISDLL, IDS_INDEX, szTemp, ARRAYSIZE(szTemp)));
else
EVAL(LoadString(HINST_THISDLL, IDS_DISABLEINDEX, szTemp, ARRAYSIZE(szTemp)));
StrCatBuff(szAttribsToApply, szTemp, ARRAYSIZE(szAttribsToApply));
}
if (pfpsp->asInitial.fCompress != pfpsp->asCurrent.fCompress)
{
if (pfpsp->asCurrent.fCompress)
EVAL(LoadString(HINST_THISDLL, IDS_COMPRESS, szTemp, ARRAYSIZE(szTemp)));
else
EVAL(LoadString(HINST_THISDLL, IDS_UNCOMPRESS, szTemp, ARRAYSIZE(szTemp)));
StrCatBuff(szAttribsToApply, szTemp, ARRAYSIZE(szAttribsToApply));
}
if (pfpsp->asInitial.fEncrypt != pfpsp->asCurrent.fEncrypt)
{
if (pfpsp->asCurrent.fEncrypt)
EVAL(LoadString(HINST_THISDLL, IDS_ENCRYPT, szTemp, ARRAYSIZE(szTemp)));
else
EVAL(LoadString(HINST_THISDLL, IDS_DECRYPT, szTemp, ARRAYSIZE(szTemp)));
StrCatBuff(szAttribsToApply, szTemp, ARRAYSIZE(szAttribsToApply));
}
if (!*szAttribsToApply)
{
// nothing changed bail
return FALSE;
}
// remove the trailing ", "
int iLength = lstrlen(szAttribsToApply);
ASSERT(iLength >= 3);
lstrcpy(&szAttribsToApply[iLength - 2], TEXT("\0"));
SetDlgItemText(hDlgRecurse, IDD_ATTRIBSTOAPPLY, szAttribsToApply);
return TRUE;
}
//
// This dlg proc is for the prompt to ask the user if they want to have their changes apply
// to only the directories, or all files/folders within the directories.
//
BOOL_PTR CALLBACK RecursivePromptDlgProc(HWND hDlgRecurse, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
FILEPROPSHEETPAGE* pfpsp = (FILEPROPSHEETPAGE *)GetWindowLongPtr(hDlgRecurse, DWLP_USER);
switch (uMessage)
{
case WM_INITDIALOG:
{
SetWindowLongPtr(hDlgRecurse, DWLP_USER, lParam);
pfpsp = (FILEPROPSHEETPAGE *)lParam;
// set the initial state of the radio button
CheckDlgButton(hDlgRecurse, IDD_RECURSIVE, TRUE);
// set the IDD_ATTRIBSTOAPPLY based on what attribs we are applying
if (!SetAttributePromptText(hDlgRecurse, pfpsp))
{
// we should not get here because we check for the no attribs
// to apply earlier
ASSERT(FALSE);
EndDialog(hDlgRecurse, TRUE);
}
// load either "this folder" or "the selected items"
TCHAR szFolderText[MAX_PATH];
LoadString(HINST_THISDLL, pfpsp->pfci->fMultipleFiles ? IDS_THESELECTEDITEMS : IDS_THISFOLDER, szFolderText, ARRAYSIZE(szFolderText));
// set the IDD_RECURSIVE_TXT text to have "this folder" or "the selected items"
TCHAR szFormatString[MAX_PATH];
GetDlgItemText(hDlgRecurse, IDD_RECURSIVE_TXT, szFormatString, ARRAYSIZE(szFormatString));
TCHAR szDlgText[MAX_PATH];
wsprintf(szDlgText, szFormatString, szFolderText);
SetDlgItemText(hDlgRecurse, IDD_RECURSIVE_TXT, szDlgText);
// set the IDD_NOTRECURSIVE raido button text to have "this folder" or "the selected items"
GetDlgItemText(hDlgRecurse, IDD_NOTRECURSIVE, szFormatString, ARRAYSIZE(szFormatString));
wsprintf(szDlgText, szFormatString, szFolderText);
SetDlgItemText(hDlgRecurse, IDD_NOTRECURSIVE, szDlgText);
// set the IDD_RECURSIVE raido button text to have "this folder" or "the selected items"
GetDlgItemText(hDlgRecurse, IDD_RECURSIVE, szFormatString, ARRAYSIZE(szFormatString));
wsprintf(szDlgText, szFormatString, szFolderText);
SetDlgItemText(hDlgRecurse, IDD_RECURSIVE, szDlgText);
return TRUE;
}
case WM_COMMAND:
{
UINT uCtrlID = GET_WM_COMMAND_ID(wParam, lParam);
switch (uCtrlID)
{
case IDOK:
pfpsp->fRecursive = (IsDlgButtonChecked(hDlgRecurse, IDD_RECURSIVE) == BST_CHECKED);
// fall through
case IDCANCEL:
EndDialog(hDlgRecurse, (uCtrlID == IDCANCEL) ? FALSE : TRUE);
break;
}
}
default:
return FALSE;
}
}
//
// This wndproc handles the "Advanced Attributes..." button on the general tab for
//
// return - FALSE: the user hit cancle
// TRUE: the user hit ok
//
BOOL_PTR CALLBACK AdvancedFileAttribsDlgProc(HWND hDlgAttribs, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
FILEPROPSHEETPAGE* pfpsp = (FILEPROPSHEETPAGE *)GetWindowLongPtr(hDlgAttribs, DWLP_USER);
switch (uMessage)
{
case WM_INITDIALOG:
{
SetWindowLongPtr(hDlgAttribs, DWLP_USER, lParam);
pfpsp = (FILEPROPSHEETPAGE *)lParam;
// set the initial state of the checkboxes
if (pfpsp->asInitial.fArchive == BST_INDETERMINATE)
{
SendDlgItemMessage(hDlgAttribs, IDD_ARCHIVE, BM_SETSTYLE, BS_AUTO3STATE, 0);
}
CheckDlgButton(hDlgAttribs, IDD_ARCHIVE, pfpsp->asCurrent.fArchive);
if (pfpsp->asInitial.fIndex == BST_INDETERMINATE)
{
SendDlgItemMessage(hDlgAttribs, IDD_INDEX, BM_SETSTYLE, BS_AUTO3STATE, 0);
}
CheckDlgButton(hDlgAttribs, IDD_INDEX, pfpsp->asCurrent.fIndex);
if (pfpsp->asInitial.fCompress == BST_INDETERMINATE)
{
SendDlgItemMessage(hDlgAttribs, IDD_COMPRESS, BM_SETSTYLE, BS_AUTO3STATE, 0);
}
CheckDlgButton(hDlgAttribs, IDD_COMPRESS, pfpsp->asCurrent.fCompress);
if (pfpsp->asInitial.fEncrypt == BST_INDETERMINATE)
{
SendDlgItemMessage(hDlgAttribs, IDD_ENCRYPT, BM_SETSTYLE, BS_AUTO3STATE, 0);
}
CheckDlgButton(hDlgAttribs, IDD_ENCRYPT, pfpsp->asCurrent.fEncrypt);
// assert that compression and encryption are mutually exclusive
ASSERT(!((pfpsp->asCurrent.fCompress == BST_CHECKED) && (pfpsp->asCurrent.fEncrypt == BST_CHECKED)));
// gray any checkboxs that are not supported by this filesystem
EnableWindow(GetDlgItem(hDlgAttribs, IDD_INDEX), pfpsp->fIsIndexAvailable);
EnableWindow(GetDlgItem(hDlgAttribs, IDD_COMPRESS), pfpsp->pfci->fIsCompressionAvailable);
EnableWindow(GetDlgItem(hDlgAttribs, IDD_ENCRYPT), pfpsp->fIsEncryptionAvailable);
if (pfpsp->fIsEncryptionAvailable &&
pfpsp->asInitial.fEncrypt &&
!pfpsp->fIsDirectory &&
!pfpsp->pfci->fMultipleFiles)
{
// we only support the Advanced button for the single folder case
EnableWindow(GetDlgItem(hDlgAttribs, IDC_ADVANCED), TRUE);
}
else
{
EnableWindow(GetDlgItem(hDlgAttribs, IDC_ADVANCED), FALSE);
}
// load either "this folder" or "the selected items"
TCHAR szFolderText[MAX_PATH];
LoadString(HINST_THISDLL, pfpsp->pfci->fMultipleFiles ? IDS_THESELECTEDITEMS : IDS_THISFOLDER, szFolderText, ARRAYSIZE(szFolderText));
// set the IDC_MANAGEFILES_TXT text to have "this folder" or "the selected items"
TCHAR szFormatString[MAX_PATH];
GetDlgItemText(hDlgAttribs, IDC_MANAGEFILES_TXT, szFormatString, ARRAYSIZE(szFormatString));
TCHAR szDlgText[MAX_PATH];
wsprintf(szDlgText, szFormatString, szFolderText);
SetDlgItemText(hDlgAttribs, IDC_MANAGEFILES_TXT, szDlgText);
return TRUE;
}
case WM_HELP:
WinHelp((HWND)((LPHELPINFO)lParam)->hItemHandle, NULL, HELP_WM_HELP, (ULONG_PTR)(LPTSTR)aAdvancedHelpIds);
break;
case WM_CONTEXTMENU:
if ((int)SendMessage(hDlgAttribs, WM_NCHITTEST, 0, lParam) != HTCLIENT)
{
// not in our client area, so don't process it
return FALSE;
}
WinHelp((HWND)wParam, NULL, HELP_CONTEXTMENU, (ULONG_PTR)(void *)aAdvancedHelpIds);
break;
case WM_COMMAND:
{
UINT uCtrlID = GET_WM_COMMAND_ID(wParam, lParam);
switch (uCtrlID)
{
case IDD_COMPRESS:
// encrypt and compress are mutually exclusive
if (IsDlgButtonChecked(hDlgAttribs, IDD_COMPRESS) == BST_CHECKED)
{
// the user checked compress so uncheck the encrypt checkbox
CheckDlgButton(hDlgAttribs, IDD_ENCRYPT, BST_UNCHECKED);
}
break;
case IDD_ENCRYPT:
// encrypt and compress are mutually exclusive
if (IsDlgButtonChecked(hDlgAttribs, IDD_ENCRYPT) == BST_CHECKED)
{
// the user checked encrypt, so uncheck the compression checkbox
CheckDlgButton(hDlgAttribs, IDD_COMPRESS, BST_UNCHECKED);
if (!pfpsp->pfci->fMultipleFiles && pfpsp->asInitial.fEncrypt)
{
EnableWindow(GetDlgItem(hDlgAttribs, IDC_ADVANCED), TRUE);
}
}
else
{
EnableWindow(GetDlgItem(hDlgAttribs, IDC_ADVANCED), FALSE);
}
break;
case IDC_ADVANCED:
ASSERT(pfpsp->fIsEncryptionAvailable && pfpsp->asInitial.fEncrypt && !pfpsp->pfci->fMultipleFiles);
// bring up the EfsDetail dialog
EfsDetail(hDlgAttribs, pfpsp->szPath);
break;
case IDOK:
pfpsp->asCurrent.fArchive = IsDlgButtonChecked(hDlgAttribs, IDD_ARCHIVE);
if (pfpsp->asCurrent.fArchive == BST_INDETERMINATE)
{
// if its indeterminate, it better had been indeterminate to start with
ASSERT(pfpsp->asInitial.fArchive == BST_INDETERMINATE);
}
pfpsp->asCurrent.fIndex = IsDlgButtonChecked(hDlgAttribs, IDD_INDEX);
if (pfpsp->asCurrent.fIndex == BST_INDETERMINATE)
{
// if its indeterminate, it better had been indeterminate to start with
ASSERT(pfpsp->asInitial.fIndex == BST_INDETERMINATE);
}
pfpsp->asCurrent.fCompress = IsDlgButtonChecked(hDlgAttribs, IDD_COMPRESS);
if (pfpsp->asCurrent.fCompress == BST_INDETERMINATE)
{
// if its indeterminate, it better had been indeterminate to start with
ASSERT(pfpsp->asInitial.fCompress == BST_INDETERMINATE);
}
pfpsp->asCurrent.fEncrypt = IsDlgButtonChecked(hDlgAttribs, IDD_ENCRYPT);
if (pfpsp->asCurrent.fEncrypt == BST_INDETERMINATE)
{
// if its indeterminate, it better had been indeterminate to start with
ASSERT(pfpsp->asInitial.fEncrypt == BST_INDETERMINATE);
}
// fall through...
case IDCANCEL:
ReplaceDlgIcon(hDlgAttribs, IDD_ITEMICON, NULL);
EndDialog(hDlgAttribs, (uCtrlID == IDCANCEL) ? FALSE : TRUE);
break;
}
}
default:
return FALSE;
}
return TRUE;
}
//
// Descriptions:
// This is the dialog procedure for multiple object property sheet.
//
BOOL_PTR CALLBACK MultiplePrshtDlgProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
FILEPROPSHEETPAGE * pfpsp = (FILEPROPSHEETPAGE *)GetWindowLongPtr(hDlg, DWLP_USER);
switch (uMessage)
{
case WM_INITDIALOG:
SetWindowLongPtr(hDlg, DWLP_USER, lParam);
pfpsp = (FILEPROPSHEETPAGE *)lParam;
pfpsp->hDlg = hDlg;
pfpsp->pfci->hDlg = hDlg;
InitMultiplePrsht(pfpsp);
break;
case WM_HELP:
WinHelp((HWND)((LPHELPINFO)lParam)->hItemHandle, NULL, HELP_WM_HELP, (ULONG_PTR)(void *)aMultiPropHelpIds);
break;
case WM_CONTEXTMENU:
if ((int)SendMessage(hDlg, WM_NCHITTEST, 0, lParam) != HTCLIENT)
{
// not in our client area, so don't process it
return FALSE;
}
WinHelp((HWND)wParam, NULL, HELP_CONTEXTMENU, (ULONG_PTR)(void *)aMultiPropHelpIds);
break;
case WM_TIMER:
UpdateSizeCount(pfpsp);
break;
case WM_COMMAND:
switch (GET_WM_COMMAND_ID(wParam, lParam))
{
case IDD_READONLY:
case IDD_HIDDEN:
case IDD_ARCHIVE:
break;
case IDC_ADVANCED:
// the dialog box returns fase if the user hit cancel, and true if they hit ok,
// so if they cancelled, return immediately and don't send the PSM_CHANGED message
// because nothing actually changed
if (!DialogBoxParam(HINST_THISDLL,
MAKEINTRESOURCE(pfpsp->fIsDirectory ? DLG_FOLDERATTRIBS : DLG_FILEATTRIBS),
hDlg,
AdvancedFileAttribsDlgProc,
(LPARAM)pfpsp))
{
// the user has cancled
return TRUE;
}
break;
default:
return TRUE;
}
// check to see if we need to enable the Apply button
if (GET_WM_COMMAND_CMD(wParam, lParam) == BN_CLICKED)
{
SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
}
break;
case WM_DESTROY:
Free_DlgDependentFilePropSheetPage(pfpsp);
break;
case WM_NOTIFY:
switch (((NMHDR *)lParam)->code)
{
case PSN_APPLY:
{
//
// Get the final state of the checkboxes
//
pfpsp->asCurrent.fReadOnly = IsDlgButtonChecked(hDlg, IDD_READONLY);
if (pfpsp->asCurrent.fReadOnly == BST_INDETERMINATE)
{
// if its indeterminate, it better had been indeterminate to start with
ASSERT(pfpsp->asInitial.fReadOnly == BST_INDETERMINATE);
}
pfpsp->asCurrent.fHidden = IsDlgButtonChecked(hDlg, IDD_HIDDEN);
if (pfpsp->asCurrent.fHidden == BST_INDETERMINATE)
{
// if its indeterminate, it better had been indeterminate to start with
ASSERT(pfpsp->asInitial.fHidden == BST_INDETERMINATE);
}
if (!pfpsp->pfci->fIsCompressionAvailable)
{
// at least one of the files is on FAT, so the Archive checkbox is on the general page
pfpsp->asCurrent.fArchive = IsDlgButtonChecked(hDlg, IDD_ARCHIVE);
if (pfpsp->asCurrent.fArchive == BST_INDETERMINATE)
{
// if its indeterminate, it better had been indeterminate to start with
ASSERT(pfpsp->asInitial.fArchive == BST_INDETERMINATE);
}
}
BOOL bRet = TRUE;
// check to see if the user actually changed something, if they didnt, then
// we dont have to apply anything
if (memcmp(&pfpsp->asInitial, &pfpsp->asCurrent, sizeof(pfpsp->asInitial)) != 0)
{
// NOTE: We dont check to see if all the dirs are empty, that would be too expensive.
// We only do that in the single file case.
if (pfpsp->fIsDirectory)
{
// check to see if the user wants to apply the attribs recursively or not
bRet = (int)DialogBoxParam(HINST_THISDLL,
MAKEINTRESOURCE(DLG_ATTRIBS_RECURSIVE),
hDlg,
RecursivePromptDlgProc,
(LPARAM)pfpsp);
}
if (bRet)
bRet = ApplyMultipleFileAttributes(pfpsp);
if (!bRet)
{
// the user hit cancel, so we return true to prevent the property sheet form closeing
SetWindowLongPtr(hDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
return TRUE;
}
else
{
// update the size / last accessed time
UpdateSizeField(pfpsp, NULL);
}
}
break;
}
// fall through
default:
return FALSE;
}
break;
default:
return FALSE;
}
return TRUE;
}
// in:
// hdlg
// id text control id
// pftUTC UTC time time to be set
//
STDAPI_(void) SetDateTimeText(HWND hdlg, int id, const FILETIME *pftUTC)
{
SetDateTimeTextEx(hdlg, id, pftUTC, FDTF_LONGDATE | FDTF_LONGTIME | FDTF_RELATIVE) ;
}
STDAPI_(void) SetDateTimeTextEx(HWND hdlg, int id, const FILETIME *pftUTC, DWORD dwFlags)
{
if (!IsNullTime(pftUTC))
{
LCID locale = GetUserDefaultLCID();
if ((PRIMARYLANGID(LANGIDFROMLCID(locale)) == LANG_ARABIC) ||
(PRIMARYLANGID(LANGIDFROMLCID(locale)) == LANG_HEBREW))
{
HWND hWnd = GetDlgItem(hdlg, id);
DWORD dwExStyle = GetWindowLong(hdlg, GWL_EXSTYLE);
if ((BOOLIFY(dwExStyle & WS_EX_RTLREADING)) != (BOOLIFY(dwExStyle & RTL_MIRRORED_WINDOW)))
dwFlags |= FDTF_RTLDATE;
else
dwFlags |= FDTF_LTRDATE;
}
TCHAR szBuf[64];
SHFormatDateTime(pftUTC, &dwFlags, szBuf, SIZECHARS(szBuf));
SetDlgItemText(hdlg, id, szBuf);
}
}
//
// Descriptions:
// Detects BiDi Calender
//
BOOL _IsBiDiCalendar(void)
{
TCHAR chCalendar[32];
BOOL fBiDiCalender = FALSE;
//
// Let's verify the calendar type whether it's gregorian or not.
if (GetLocaleInfo(LOCALE_USER_DEFAULT,
LOCALE_ICALENDARTYPE,
(TCHAR *) &chCalendar[0],
ARRAYSIZE(chCalendar)))
{
CALTYPE defCalendar = StrToInt((TCHAR *)&chCalendar[0]);
LCID locale = GetUserDefaultLCID();
if ((defCalendar == CAL_HIJRI) ||
(defCalendar == CAL_HEBREW) ||
((defCalendar == CAL_GREGORIAN) &&
((PRIMARYLANGID(LANGIDFROMLCID(locale)) == LANG_ARABIC) ||
(PRIMARYLANGID(LANGIDFROMLCID(locale)) == LANG_HEBREW))) ||
(defCalendar == CAL_GREGORIAN_ARABIC) ||
(defCalendar == CAL_GREGORIAN_XLIT_ENGLISH) ||
(defCalendar == CAL_GREGORIAN_XLIT_FRENCH))
{
fBiDiCalender = TRUE;
}
}
return fBiDiCalender;
}
// Set the friendly display name into control uId.
BOOL SetPidlToWindow(HWND hwnd, UINT uId, LPITEMIDLIST pidl)
{
BOOL fRes = FALSE;
LPCITEMIDLIST pidlItem;
IShellFolder* psf;
if (SUCCEEDED(SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlItem)))
{
TCHAR szPath[MAX_PATH];
// SHGDN_FORADDRESSBAR | SHGDN_FORPARSING because we want:
// c:\winnt\.... and http://weird, but not ::{GUID} or Folder.{GUID}
if (SUCCEEDED(DisplayNameOf(psf, pidlItem, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath))))
{
SetDlgItemText(hwnd, IDD_TARGET, szPath);
fRes = TRUE;
}
psf->Release();
}
return fRes;
}
//
// Descriptions:
// This function fills fields of the "general" dialog box (a page of
// a property sheet) with attributes of the associated file.
//
BOOL InitSingleFilePrsht(FILEPROPSHEETPAGE * pfpsp)
{
SHFILEINFO sfi;
// get info about the file.
SHGetFileInfo(pfpsp->szPath, pfpsp->fd.dwFileAttributes, &sfi, sizeof(sfi),
SHGFI_ICON|SHGFI_LARGEICON|
SHGFI_DISPLAYNAME|
SHGFI_TYPENAME | SHGFI_ADDOVERLAYS);
// .ani cursor hack!
if (lstrcmpi(PathFindExtension(pfpsp->szPath), TEXT(".ani")) == 0)
{
HICON hIcon = (HICON)LoadImage(NULL, pfpsp->szPath, IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
if (hIcon)
{
if (sfi.hIcon)
DestroyIcon(sfi.hIcon);
sfi.hIcon = hIcon;
}
}
// icon
ReplaceDlgIcon(pfpsp->hDlg, IDD_ITEMICON, sfi.hIcon);
// set the initial rename state
pfpsp->fRename = FALSE;
// set the file type
if (pfpsp->fMountedDrive)
{
//Borrow szVolumeGUID
TCHAR szVolumeGUID[MAX_PATH];
LoadString(HINST_THISDLL, IDS_MOUNTEDVOLUME, szVolumeGUID, ARRAYSIZE(szVolumeGUID));
SetDlgItemText(pfpsp->hDlg, IDD_FILETYPE, szVolumeGUID);
//use szVolumeLabel temporarily
TCHAR szVolumeLabel[MAX_PATH];
lstrcpyn(szVolumeLabel, pfpsp->szPath, ARRAYSIZE(szVolumeLabel));
PathAddBackslash(szVolumeLabel);
GetVolumeNameForVolumeMountPoint(szVolumeLabel, szVolumeGUID, ARRAYSIZE(szVolumeGUID));
if (!GetVolumeInformation(szVolumeGUID, szVolumeLabel, ARRAYSIZE(szVolumeLabel),
NULL, NULL, NULL, pfpsp->szFileSys, ARRAYSIZE(pfpsp->szFileSys)))
{
EnableWindow(GetDlgItem(pfpsp->hDlg, IDC_DRV_PROPERTIES), FALSE);
*szVolumeLabel = 0;
}
if (!(*szVolumeLabel))
LoadString(HINST_THISDLL, IDS_UNLABELEDVOLUME, szVolumeLabel, ARRAYSIZE(szVolumeLabel));
SetDlgItemText(pfpsp->hDlg, IDC_DRV_TARGET, szVolumeLabel);
}
else
{
SetDlgItemText(pfpsp->hDlg, IDD_FILETYPE, sfi.szTypeName);
}
// save off the initial short filename, and set the "Name" edit box
lstrcpyn(pfpsp->szInitialName, sfi.szDisplayName, ARRAYSIZE(pfpsp->szInitialName));
SetDlgItemText(pfpsp->hDlg, IDD_NAMEEDIT, sfi.szDisplayName);
// use a strcmp to see if we are showing the extension
if (lstrcmpi(sfi.szDisplayName, PathFindFileName(pfpsp->szPath)) == 0)
{
// since the strings are the same, we must be showing the extension
pfpsp->fShowExtension = TRUE;
}
TCHAR szBuffer[MAX_PATH];
lstrcpyn(szBuffer, pfpsp->szPath, ARRAYSIZE(szBuffer));
PathRemoveFileSpec(szBuffer);
UINT cchMax;
GetCCHMaxFromPath(pfpsp->szPath, &cchMax, pfpsp->fShowExtension);
Edit_LimitText(GetDlgItem(pfpsp->hDlg, IDD_NAMEEDIT), cchMax);
// apply the limit input code for the item
pfpsp->pidl = ILCreateFromPath(pfpsp->szPath);
if (pfpsp->pidl)
{
IShellFolder *psf;
if (SUCCEEDED(SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, pfpsp->pidl, &psf))))
{
SHLimitInputEdit(GetDlgItem(pfpsp->hDlg, IDD_NAMEEDIT), psf);
psf->Release();
}
}
// Are we a folder shortcut?
if (pfpsp->fFolderShortcut)
{
// Yes; Then we need to populate folder shortcut specific controls.
if (pfpsp->pidl)
{
IShellLink *psl;
if (SUCCEEDED(SHGetUIObjectFromFullPIDL(pfpsp->pidl, NULL, IID_PPV_ARG(IShellLink, &psl))))
{
// Populate the Target
if (SUCCEEDED(psl->GetIDList(&pfpsp->pidlTarget)))
{
if (SetPidlToWindow(pfpsp->hDlg, IDD_TARGET, pfpsp->pidlTarget))
{
pfpsp->fValidateEdit = FALSE; // Set this to false because we already have a pidl
// and don't need to validate.
}
}
// And description
TCHAR sz[INFOTIPSIZE];
if (SUCCEEDED(psl->GetDescription(sz, ARRAYSIZE(sz))))
{
SetDlgItemText(pfpsp->hDlg, IDD_COMMENT, sz);
}
psl->Release();
}
}
SetDateTimeText(pfpsp->hDlg, IDD_CREATED, &pfpsp->fd.ftCreationTime);
}
else
{
// set the initial attributes
SetInitialFileAttribs(pfpsp, pfpsp->fd.dwFileAttributes, pfpsp->fd.dwFileAttributes);
// special case for folders, we don't apply the read only bit to folders
// and to indicate that in the UI we make the inital state of the check
// box tri-state. this allows the read only bit to be applied to files in
// this folder, but not the folders themselves.
if (pfpsp->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
pfpsp->asInitial.fReadOnly = BST_INDETERMINATE;
SendDlgItemMessage(pfpsp->hDlg, IDD_READONLY, BM_SETSTYLE, BS_AUTO3STATE, 0);
}
// set the current attributes to the same as the initial
pfpsp->asCurrent = pfpsp->asInitial;
CheckDlgButton(pfpsp->hDlg, IDD_READONLY, pfpsp->asInitial.fReadOnly);
CheckDlgButton(pfpsp->hDlg, IDD_HIDDEN, pfpsp->asInitial.fHidden);
// Disable renaming the file if requested
if (pfpsp->fDisableRename)
{
EnableWindow(GetDlgItem(pfpsp->hDlg, IDD_NAMEEDIT), FALSE);
}
if (pfpsp->fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
{
// to avoid people making SYSTEM files HIDDEN (superhidden files are
// not show to the user by default) we don't let people make SYSTEM files HIDDEN
EnableWindow(GetDlgItem(pfpsp->hDlg, IDD_HIDDEN), FALSE);
}
// Archive is only on the general tab for FAT, otherwise it is under the "Advanced attributes"
// and FAT volumes dont have the "Advanced attributes" button.
if (pfpsp->pfci->fIsCompressionAvailable || pfpsp->fIsEncryptionAvailable)
{
// if compression/encryption is available, then we must be on NTFS
DestroyWindow(GetDlgItem(pfpsp->hDlg, IDD_ARCHIVE));
}
else
{
// we are on FAT/FAT32, so get rid of the "Advanced attributes" button, and set the inital Archive state
DestroyWindow(GetDlgItem(pfpsp->hDlg, IDC_ADVANCED));
CheckDlgButton(pfpsp->hDlg, IDD_ARCHIVE, pfpsp->asInitial.fArchive);
}
UpdateSizeField(pfpsp, &pfpsp->fd);
if (!(pfpsp->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
// Check to see if the target file is a lnk, because if it is a lnk then
// we need to display the type information for the target, not the lnk itself.
if (PathIsShortcut(pfpsp->szPath, pfpsp->fd.dwFileAttributes))
{
pfpsp->fIsLink = TRUE;
}
if (!(GetFileAttributes(pfpsp->szPath) & FILE_ATTRIBUTE_OFFLINE))
{
UpdateOpensWithInfo(pfpsp);
}
else
{
EnableWindow(GetDlgItem(pfpsp->hDlg, IDC_FT_PROP_CHANGEOPENSWITH), FALSE);
}
}
// get the full path to the folder that contains this file.
lstrcpyn(szBuffer, pfpsp->szPath, ARRAYSIZE(szBuffer));
PathRemoveFileSpec(szBuffer);
// Keep Functionality same as NT4 by avoiding PathCompactPath.
SetDlgItemTextWithToolTip(pfpsp->hDlg, IDD_LOCATION, szBuffer, &pfpsp->hwndTip);
}
return TRUE;
}
STDAPI_(BOOL) ShowMountedVolumeProperties(LPCTSTR pszMountedVolume, HWND hwndParent)
{
IMountedVolume* pMountedVolume;
HRESULT hr = SHCoCreateInstance(NULL, &CLSID_MountedVolume, NULL, IID_PPV_ARG(IMountedVolume, &pMountedVolume));
if (SUCCEEDED(hr))
{
TCHAR szPathSlash[MAX_PATH];
lstrcpyn(szPathSlash, pszMountedVolume, ARRAYSIZE(szPathSlash));
PathAddBackslash(szPathSlash);
hr = pMountedVolume->Initialize(szPathSlash);
if (SUCCEEDED(hr))
{
IDataObject* pDataObj;
hr = pMountedVolume->QueryInterface(IID_PPV_ARG(IDataObject, &pDataObj));
if (SUCCEEDED(hr))
{
PROPSTUFF *pps = (PROPSTUFF *)LocalAlloc(LPTR, sizeof(*pps));
if (pps)
{
pps->lpStartAddress = DrivesPropertiesThreadProc;
pps->pdtobj = pDataObj;
EnableWindow(hwndParent, FALSE);
DrivesPropertiesThreadProc(pps);
EnableWindow(hwndParent, TRUE);
LocalFree(pps);
}
pDataObj->Release();
}
pMountedVolume->Release();
}
}
return SUCCEEDED(hr);
}
#ifdef FOLDERSHORTCUT_EDITABLETARGET
BOOL SetFolderShortcutInfo(HWND hDlg, FILEPROPSHEETPAGE* pfpsp)
{
ASSERT(pfpsp->pidl);
BOOL fSuccess = FALSE;
IShellLink* psl;
if (SUCCEEDED(SHGetUIObjectFromFullPIDL(pfpsp->pidl, NULL, IID_PPV_ARG(IShellLink, &psl))))
{
TCHAR sz[INFOTIPSIZE];
Edit_GetText(GetDlgItem(pfpsp->hDlg, IDD_COMMENT), sz, ARRAYSIZE(sz));
psl->SetDescription(sz);
if (pfpsp->fValidateEdit)
{
IShellFolder* psf;
if (SUCCEEDED(SHGetDesktopFolder(&psf)))
{
TCHAR szPath[MAX_PATH];
Edit_GetText(GetDlgItem(pfpsp->hDlg, IDD_TARGET), sz, ARRAYSIZE(sz));
if (PathCanonicalize(szPath, sz))
{
LPITEMIDLIST pidlDest;
DWORD dwAttrib = SFGAO_FOLDER | SFGAO_VALIDATE;
ULONG chEat = 0;
if (SUCCEEDED(psf->ParseDisplayName(NULL, NULL, szPath, &chEat, &pidlDest, &dwAttrib)))
{
if ((dwAttrib & SFGAO_FOLDER) == SFGAO_FOLDER)
{
ILFree(pfpsp->pidlTarget);
pfpsp->pidlTarget = pidlDest;
fSuccess = TRUE;
}
else
{
ILFree(pidlDest);
}
}
}
psf->Release();
}
}
else
{
fSuccess = TRUE;
}
if (fSuccess)
{
psl->SetIDList(pfpsp->pidlTarget);
IPersistFile* ppf;
if (SUCCEEDED(psl->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf))))
{
fSuccess = (S_OK == ppf->Save(pfpsp->szPath, TRUE));
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, pfpsp->szPath, NULL);
ppf->Release();
}
}
psl->Release();
}
return fSuccess;
}
#endif
//
// Descriptions:
// This is the dialog procedure for the "general" page of a property sheet.
//
BOOL_PTR CALLBACK SingleFilePrshtDlgProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
FILEPROPSHEETPAGE* pfpsp = (FILEPROPSHEETPAGE *)GetWindowLongPtr(hDlg, DWLP_USER);
switch (uMessage)
{
case WM_INITDIALOG:
// REVIEW, we should store more state info here, for example
// the hIcon being displayed and the FILEINFO pointer, not just
// the file name ptr
SetWindowLongPtr(hDlg, DWLP_USER, lParam);
pfpsp = (FILEPROPSHEETPAGE *)lParam;
pfpsp->hDlg = hDlg;
pfpsp->pfci->hDlg = hDlg;
InitSingleFilePrsht(pfpsp);
// We set this to signal that we are done processing the WM_INITDIALOG.
// This is needed because we set the text of the "Name" edit box and unless
// he knows that this is being set for the first time, he thinks that someone is doing a rename.
pfpsp->fWMInitFinshed = TRUE;
break;
case WM_TIMER:
if (!pfpsp->fMountedDrive)
UpdateSizeCount(pfpsp);
break;
case WM_HELP:
WinHelp((HWND)((LPHELPINFO)lParam)->hItemHandle, NULL, HELP_WM_HELP, (ULONG_PTR)(void *)(pfpsp->fIsDirectory ? aFolderGeneralHelpIds : aFileGeneralHelpIds));
break;
case WM_CONTEXTMENU:
if ((int)SendMessage(hDlg, WM_NCHITTEST, 0, lParam) != HTCLIENT)
{
// not in our client area, so don't process it
return FALSE;
}
WinHelp((HWND)wParam, NULL, HELP_CONTEXTMENU, (ULONG_PTR)(void *)(pfpsp->fIsDirectory ? aFolderGeneralHelpIds : aFileGeneralHelpIds));
break;
case WM_COMMAND:
switch (GET_WM_COMMAND_ID(wParam, lParam))
{
case IDD_READONLY:
case IDD_HIDDEN:
case IDD_ARCHIVE:
break;
#ifdef FOLDERSHORTCUT_EDITABLETARGET
case IDD_TARGET:
if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_CHANGE)
{
// someone typed in the target, enable apply button
SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
// Do a verification on apply.
pfpsp->fValidateEdit = TRUE;
}
break;
case IDD_COMMENT:
if (GET_WM_COMMAND_CMD(wParam, lParam) == EN_CHANGE)
{
// Set the apply.
SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
}
break;
#endif
case IDD_NAMEEDIT:
// we need to check the pfpsp->fWMInitFinshed to make sure that we are done processing the WM_INITDIALOG,
// because during init we set the initial IDD_NAMEEDIT text which generates a EN_CHANGE message.
if ((GET_WM_COMMAND_CMD(wParam, lParam) == EN_CHANGE) && !pfpsp->fRename && pfpsp->fWMInitFinshed)
{
pfpsp->fRename = TRUE;
//
// disable "Apply" even though the edit field has changed (reinerf)
//
// We only allow "ok" or "cancel" after the name has changed to be sure we
// don't rename the file out from under other property sheet extensions that
// cache the original name away
PropSheet_DisableApply(GetParent(pfpsp->hDlg));
}
break;
case IDC_CHANGEFILETYPE:
{
// Bring up the "Open With" dialog
OPENASINFO oai;
if (pfpsp->fIsLink && pfpsp->szLinkTarget[0])
{
// if we have a link we want to re-associate the link target, NOT .lnk files!
oai.pcszFile = pfpsp->szLinkTarget;
}
else
{
#ifdef DEBUG
LPTSTR pszExt = PathFindExtension(pfpsp->szPath);
// reality check...
ASSERT((lstrcmpi(pszExt, TEXT(".exe")) != 0) &&
(lstrcmpi(pszExt, TEXT(".lnk")) != 0));
#endif // DEBUG
oai.pcszFile = pfpsp->szPath;
}
oai.pcszClass = NULL;
oai.dwInFlags = (OAIF_REGISTER_EXT | OAIF_FORCE_REGISTRATION); // we want the association to be made
if (SUCCEEDED(OpenAsDialog(GetParent(hDlg), &oai)))
{
// we changed the association so update the "Opens with:" text. Clear out szLinkTarget to force
// the update to happen
pfpsp->szLinkTarget[0] = 0;
UpdateOpensWithInfo(pfpsp);
}
}
break;
case IDC_ADVANCED:
// the dialog box returns fase if the user hit cancel, and true if they hit ok,
// so if they cancelled, return immediately and don't send the PSM_CHANGED message
// because nothing actually changed
if (!DialogBoxParam(HINST_THISDLL,
MAKEINTRESOURCE(pfpsp->fIsDirectory ? DLG_FOLDERATTRIBS : DLG_FILEATTRIBS),
hDlg,
AdvancedFileAttribsDlgProc,
(LPARAM)pfpsp))
{
// the user has canceled
return TRUE;
}
break;
case IDC_DRV_PROPERTIES:
ASSERT(pfpsp->fMountedDrive);
ShowMountedVolumeProperties(pfpsp->szPath, hDlg);
break;
#ifdef FOLDERSHORTCUT_EDITABLETARGET
case IDD_BROWSE:
{
// Display the BrowseForFolder dialog.
// FEATURE(lamadio): Implement a filter to filter things we can create folder
// shortcuts to. Not enough time for this rev 6.5.99
TCHAR szTitle[MAX_PATH];
LoadString(HINST_THISDLL, IDS_BROWSEFORFS, szTitle, ARRAYSIZE(szTitle));
TCHAR szAltPath[MAX_PATH];
BROWSEINFO bi = {0};
bi.hwndOwner = hDlg;
bi.pidlRoot = NULL;
bi.pszDisplayName = szAltPath;
bi.lpszTitle = szTitle;
bi.ulFlags = BIF_USENEWUI | BIF_EDITBOX;
LPITEMIDLIST pidlFull = SHBrowseForFolder(&bi);
if (pidlFull)
{
ILFree(pfpsp->pidlTarget);
pfpsp->pidlTarget = pidlFull;
if (SetPidlToWindow(hDlg, IDD_TARGET, pfpsp->pidlTarget))
{
SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
pfpsp->fValidateEdit = FALSE;
}
}
}
break;
#endif
default:
return TRUE;
}
// check to see if we need to enable the Apply button
if (GET_WM_COMMAND_CMD(wParam, lParam) == BN_CLICKED)
{
SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
}
break;
case WM_DESTROY:
Free_DlgDependentFilePropSheetPage(pfpsp);
break;
case WM_NOTIFY:
switch (((NMHDR *)lParam)->code)
{
case PSN_APPLY:
// check to see if we could apply the name change. Note that this
// does not actually apply the change until PSN_LASTCHANCEAPPLY
pfpsp->fCanRename = TRUE;
if (pfpsp->fRename && !ApplyRename(pfpsp, FALSE))
{
// can't change the name so don't let the dialog close
pfpsp->fCanRename = FALSE;
SetWindowLongPtr(hDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
return TRUE;
}
if (pfpsp->fFolderShortcut)
{
#ifdef FOLDERSHORTCUT_EDITABLETARGET
if (!SetFolderShortcutInfo(hDlg, pfpsp))
{
// Display that we could not create because blah, blah, blah
ShellMessageBox(HINST_THISDLL,
hDlg,
MAKEINTRESOURCE(IDS_FOLDERSHORTCUT_ERR),
MAKEINTRESOURCE(IDS_FOLDERSHORTCUT_ERR_TITLE),
MB_OK | MB_ICONSTOP);
// Reset the Folder info.
SetPidlToWindow(hDlg, IDD_TARGET, pfpsp->pidlTarget);
// Don't close the dialog.
SetWindowLongPtr(hDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
return TRUE;
}
#endif
}
else
{
UINT uReadonlyState = IsDlgButtonChecked(hDlg, IDD_READONLY);
switch (uReadonlyState)
{
case BST_CHECKED:
pfpsp->asCurrent.fReadOnly = TRUE;
break;
case BST_UNCHECKED:
pfpsp->asCurrent.fReadOnly = FALSE;
break;
case BST_INDETERMINATE:
// read-only checkbox is initaially set to BST_INDETERMINATE for folders
ASSERT(pfpsp->fIsDirectory);
ASSERT(pfpsp->asInitial.fReadOnly == BST_INDETERMINATE);
pfpsp->asCurrent.fReadOnly = BST_INDETERMINATE;
break;
}
pfpsp->asCurrent.fHidden = (IsDlgButtonChecked(hDlg, IDD_HIDDEN) == BST_CHECKED);
// Archive is on the general page for FAT volumes
if (!pfpsp->pfci->fIsCompressionAvailable)
{
pfpsp->asCurrent.fArchive = (IsDlgButtonChecked(hDlg, IDD_ARCHIVE) == BST_CHECKED);
}
// check to see if the user actually changed something, if they didnt, then
// we dont have to apply anything
if (memcmp(&pfpsp->asInitial, &pfpsp->asCurrent, sizeof(pfpsp->asInitial)) != 0)
{
BOOL bRet = TRUE;
// Check to see if the user wants to apply the attribs recursively or not. If the
// directory is empty, dont bother to ask, since there is nothing to recurse into
if (pfpsp->fIsDirectory && !PathIsDirectoryEmpty(pfpsp->szPath))
{
bRet = (int)DialogBoxParam(HINST_THISDLL,
MAKEINTRESOURCE(DLG_ATTRIBS_RECURSIVE),
hDlg,
RecursivePromptDlgProc,
(LPARAM)pfpsp);
}
if (bRet)
{
bRet = ApplySingleFileAttributes(pfpsp);
}
if (!bRet)
{
// the user hit cancel, so we return true to prevent the property sheet from closing
SetWindowLongPtr(hDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
return TRUE;
}
else
{
// update the size / last accessed time
UpdateSizeField(pfpsp, NULL);
}
}
}
break;
case PSN_SETACTIVE:
if (pfpsp->fIsLink)
{
// If this is a link, each time we get set active we need to check to see
// if the user applied changes on the link tab that would affect the
// "Opens With:" info.
UpdateOpensWithInfo(pfpsp);
}
break;
case PSN_QUERYINITIALFOCUS:
// Special hack: We do not want initial focus on the "Rename" or "Change" controls, since
// if the user hit something by accident they would start renaming/modifying the assoc. So
// we set the focus to the "Read-only" control since it is present on all dialogs that use
// this wndproc (file, folder, and mounted drive)
SetWindowLongPtr(hDlg, DWLP_MSGRESULT, (LPARAM)GetDlgItem(hDlg, IDD_READONLY));
return TRUE;
case PSN_LASTCHANCEAPPLY:
//
// HACKHACK (reinerf)
//
// I hacked PSN_LASTCHANCEAPPLY into the prsht code so we can get a notification after
// every other app has applied, then we can go and do the rename.
//
// strangely, PSN_LASTCHANCEAPPLY is called even if PSN_APPY returns TRUE.
//
// we can now safely rename the file, since all the other tabs have
// applied their stuff.
if (pfpsp->fRename && pfpsp->fCanRename)
{
// dont bother to check the return value since this is the last-chance,
// so the dialog is ending shortly after this
ApplyRename(pfpsp, TRUE);
}
break;
default:
return FALSE;
}
break;
default:
return FALSE;
}
return TRUE;
}
//
// This function consists of code that does some
// Initialization for the file property sheet dialog.
STDAPI InitCommonPrsht(FILEPROPSHEETPAGE * pfpsp)
{
pfpsp->psp.dwSize = sizeof(FILEPROPSHEETPAGE); // extra data
pfpsp->psp.dwFlags = PSP_USECALLBACK;
pfpsp->psp.hInstance = HINST_THISDLL;
pfpsp->psp.pfnCallback = NULL; //FilePrshtCallback;
pfpsp->pfci->bContinue = TRUE;
// Do basic init for file system props
if (HIDA_GetCount(pfpsp->pfci->hida) == 1) // single file?
{
// get most of the data we will need (the date/time stuff is not filled in)
if (HIDA_FillFindData(pfpsp->pfci->hida, 0, pfpsp->szPath, &(pfpsp->pfci->fd), FALSE))
{
pfpsp->fd = pfpsp->pfci->fd;
LPITEMIDLIST pidl = HIDA_ILClone(pfpsp->pfci->hida, 0);
if (pidl)
{
// disable renaming here.
DWORD dwAttrs = SFGAO_CANRENAME;
if (SUCCEEDED(SHGetNameAndFlags(pidl, 0, NULL, 0, &dwAttrs)) && !(dwAttrs & SFGAO_CANRENAME))
{
pfpsp->fDisableRename = TRUE;
}
ILFree(pidl);
}
if (pfpsp->pfci->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
pfpsp->fIsDirectory = TRUE;
// check for HostFolder folder mounting a volume)
//GetVolumeNameFromMountPoint Succeeds then the give path is a mount point
//Make sure the path ends with a backslash. otherwise the following api wont work
TCHAR szPathSlash[MAX_PATH];
lstrcpyn(szPathSlash, pfpsp->szPath, ARRAYSIZE(szPathSlash));
PathAddBackslash(szPathSlash);
// Is this a mounted volume at this folder?
// this fct will return FALSE if not on NT5 and higher
TCHAR szVolumeName[MAX_PATH];
if (GetVolumeNameForVolumeMountPoint(szPathSlash, szVolumeName, ARRAYSIZE(szVolumeName)))
{
// Yes; show the Mounted Drive Propertysheet instead of normal
// folder property sheet
// fpsp.fMountedDrive also means NT5 or higher, because this fct will fail otherwise
pfpsp->fMountedDrive = TRUE;
}
// check to see if it's a folder shortcut
if (!(pfpsp->fMountedDrive))
{
// Folder and a shortcut? Must be a folder shortcut!
if (PathIsShortcut(pfpsp->szPath, pfpsp->pfci->fd.dwFileAttributes))
{
pfpsp->fFolderShortcut = TRUE;
}
}
}
{
DWORD dwVolumeFlags = GetVolumeFlags(pfpsp->szPath,
pfpsp->szFileSys,
ARRAYSIZE(pfpsp->szFileSys));
// test for file-based compression.
if (dwVolumeFlags & FS_FILE_COMPRESSION)
{
// filesystem supports compression
pfpsp->pfci->fIsCompressionAvailable = TRUE;
}
// test for file-based encryption.
if ((dwVolumeFlags & FS_FILE_ENCRYPTION) && !SHRestricted(REST_NOENCRYPTION))
{
// filesystem supports encryption
pfpsp->fIsEncryptionAvailable = TRUE;
}
//
// HACKHACK (reinerf) - we dont have a FS_SUPPORTS_INDEXING so we
// use the FILE_SUPPORTS_SPARSE_FILES flag, because native index support
// appeared first on NTFS5 volumes, at the same time sparse file support
// was implemented.
//
if (dwVolumeFlags & FILE_SUPPORTS_SPARSE_FILES)
{
// yup, we are on NTFS5 or greater
pfpsp->fIsIndexAvailable = TRUE;
}
// check to see if we have a .exe and we need to prompt for user logon
pfpsp->fIsExe = PathIsBinaryExe(pfpsp->szPath);
}
}
}
else
{
// we have multiple files
pfpsp->pfci->fMultipleFiles = TRUE;
}
return S_OK;
}
//
// Descriptions:
// This function creates a property sheet object for the "general" page
// which shows file system attributes.
//
// Arguments:
// hDrop -- specifies the file(s)
// pfnAddPage -- Specifies the callback function.
// lParam -- Specifies the lParam to be passed to the callback.
//
// Returns:
// TRUE if it added any pages
//
// History:
// 12-31-92 SatoNa Created
//
STDAPI FileSystem_AddPages(IDataObject *pdtobj, LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
{
FILEPROPSHEETPAGE fpsp = {0};
HRESULT hr = S_OK;
fpsp.pfci = Create_FolderContentsInfo();
if (fpsp.pfci)
{
hr = DataObj_CopyHIDA(pdtobj, &fpsp.pfci->hida);
if (SUCCEEDED(hr))
{
hr = InitCommonPrsht(&fpsp);
if (SUCCEEDED(hr))
{
fpsp.psp.pfnCallback = FilePrshtCallback;
UINT uRes;
if (!fpsp.pfci->fMultipleFiles)
{
fpsp.psp.pfnDlgProc = SingleFilePrshtDlgProc;
if (fpsp.fIsDirectory)
{
if (fpsp.fMountedDrive)
{
uRes = DLG_MOUNTEDDRV_GENERAL;
}
else if (fpsp.fFolderShortcut)
{
uRes = DLG_FOLDERSHORTCUTPROP;
}
else
{
uRes = DLG_FOLDERPROP;
}
}
else
{
//Files
uRes = DLG_FILEPROP;
}
}
else
{
// Multiple Files / Folders.
fpsp.psp.pfnDlgProc = MultiplePrshtDlgProc;
uRes = DLG_FILEMULTPROP;
}
fpsp.psp.pszTemplate = MAKEINTRESOURCE(uRes);
}
}
if (SUCCEEDED(hr))
{
HPROPSHEETPAGE hpage = CreatePropertySheetPage(&fpsp.psp);
if (hpage)
{
if (pfnAddPage(hpage, lParam))
{
hr = S_OK;
if (!fpsp.pfci->fMultipleFiles)
{
if (AddLinkPage(fpsp.szPath, pfnAddPage, lParam))
{
// set second page default!
hr = ResultFromShort(2);
}
AddVersionPage(fpsp.szPath, pfnAddPage, lParam);
}
}
else
{
DestroyPropertySheetPage(hpage);
}
}
}
}
else
{
hr = E_OUTOFMEMORY;
}
if (FAILED(hr))
{
Free_DlgIndepFilePropSheetPage(&fpsp);
}
return hr;
}