#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;
}