From 4b15d87e2990d8827a50058c4d8625d50994b6b0 Mon Sep 17 00:00:00 2001 From: Jim Mathies Date: Wed, 14 Dec 2011 15:22:16 -0600 Subject: [PATCH] Bug 661991 - Cleanup win widget nsFilePicker. r=neil, a=ehsan --- widget/src/windows/nsFilePicker.cpp | 773 ++++++++++++++++------------ widget/src/windows/nsFilePicker.h | 25 +- 2 files changed, 452 insertions(+), 346 deletions(-) diff --git a/widget/src/windows/nsFilePicker.cpp b/widget/src/windows/nsFilePicker.cpp index 90326133fc75..f935c08d92ea 100644 --- a/widget/src/windows/nsFilePicker.cpp +++ b/widget/src/windows/nsFilePicker.cpp @@ -23,6 +23,7 @@ * Contributor(s): * Stuart Parmenter * Seth Spitzer + * Jim Mathies * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -61,13 +62,109 @@ #include "nsString.h" #include "nsToolkit.h" -NS_IMPL_ISUPPORTS1(nsFilePicker, nsIFilePicker) - PRUnichar *nsFilePicker::mLastUsedUnicodeDirectory; char nsFilePicker::mLastUsedDirectory[MAX_PATH+1] = { 0 }; #define MAX_EXTENSION_LENGTH 10 +/////////////////////////////////////////////////////////////////////////////// +// Helper classes + +// Manages matching SuppressBlurEvents calls on the parent widget. +class AutoSuppressEvents +{ +public: + explicit AutoSuppressEvents(nsIWidget* aWidget) : + mWindow(static_cast(aWidget)) { + SuppressWidgetEvents(true); + } + + ~AutoSuppressEvents() { + SuppressWidgetEvents(false); + } +private: + void SuppressWidgetEvents(bool aFlag) { + if (mWindow) { + mWindow->SuppressBlurEvents(aFlag); + } + } + nsRefPtr mWindow; +}; + +// Manages the current working path. +class AutoRestoreWorkingPath +{ +public: + AutoRestoreWorkingPath() { + DWORD bufferLength = GetCurrentDirectoryW(0, NULL); + mWorkingPath = new PRUnichar[bufferLength]; + if (GetCurrentDirectoryW(bufferLength, mWorkingPath) == 0) { + mWorkingPath = NULL; + } + } + + ~AutoRestoreWorkingPath() { + if (HasWorkingPath()) { + ::SetCurrentDirectoryW(mWorkingPath); + } + } + + inline bool HasWorkingPath() const { + return mWorkingPath != NULL; + } +private: + nsAutoArrayPtr mWorkingPath; +}; + +// Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are +// temporary child windows of mParentWidget created to address RTL issues +// in picker dialogs. We are responsible for destroying these. +class AutoDestroyTmpWindow +{ +public: + explicit AutoDestroyTmpWindow(HWND aTmpWnd) : + mWnd(aTmpWnd) { + } + + ~AutoDestroyTmpWindow() { + if (mWnd) + DestroyWindow(mWnd); + } + + inline HWND get() const { return mWnd; } +private: + HWND mWnd; +}; + +// Manages matching PickerOpen/PickerClosed calls on the parent widget. +class AutoWidgetPickerState +{ +public: + explicit AutoWidgetPickerState(nsIWidget* aWidget) : + mWindow(static_cast(aWidget)) { + PickerState(true); + } + + ~AutoWidgetPickerState() { + PickerState(false); + } +private: + void PickerState(bool aFlag) { + if (mWindow) { + if (aFlag) + mWindow->PickerOpen(); + else + mWindow->PickerClosed(); + } + } + nsRefPtr mWindow; +}; + +/////////////////////////////////////////////////////////////////////////////// +// nsIFilePicker + +NS_IMPL_ISUPPORTS1(nsFilePicker, nsIFilePicker) + nsFilePicker::nsFilePicker() { mSelectedType = 1; @@ -82,7 +179,8 @@ nsFilePicker::~nsFilePicker() } // Show - Display the file dialog -int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) +int CALLBACK +BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) { if (uMsg == BFFM_INITIALIZED) { @@ -95,7 +193,8 @@ int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpDa return 0; } -static void EnsureWindowVisible(HWND hwnd) +static void +EnsureWindowVisible(HWND hwnd) { // Obtain the monitor which has the largest area of intersection // with the window, or NULL if there is no intersection. @@ -114,8 +213,8 @@ static void EnsureWindowVisible(HWND hwnd) // Callback hook which will ensure that the window is visible. Currently // only in use on os <= XP. -static UINT_PTR CALLBACK FilePickerHook(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +static UINT_PTR CALLBACK +FilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_NOTIFY) { LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam; @@ -138,8 +237,8 @@ static UINT_PTR CALLBACK FilePickerHook(HWND hwnd, UINT msg, // Callback hook which will dynamically allocate a buffer large enough // for the file picker dialog. Currently only in use on os <= XP. -static UINT_PTR CALLBACK MultiFilePickerHook(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +static UINT_PTR CALLBACK +MultiFilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: @@ -207,16 +306,57 @@ static UINT_PTR CALLBACK MultiFilePickerHook(HWND hwnd, UINT msg, return FilePickerHook(hwnd, msg, wParam, lParam); } - -bool nsFilePicker::GetFileName(OPENFILENAMEW* ofn, PickerType aType) + +bool +nsFilePicker::ShowFolderPicker(const nsString& aInitialDir) { - NS_ENSURE_ARG_POINTER(ofn); bool result = false; - // This should be safe, we have mParentWidget ref'd - nsWindow* win = static_cast(mParentWidget.get()); - if (win) { - win->PickerOpen(); + + nsAutoArrayPtr dirBuffer(new PRUnichar[FILE_BUFFER_SIZE]); + wcsncpy(dirBuffer, aInitialDir.get(), FILE_BUFFER_SIZE); + dirBuffer[FILE_BUFFER_SIZE-1] = '\0'; + + AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? + mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : NULL)); + + BROWSEINFOW browserInfo = {0}; + browserInfo.pidlRoot = nsnull; + browserInfo.pszDisplayName = (LPWSTR)dirBuffer; + browserInfo.lpszTitle = mTitle.get(); + browserInfo.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; + browserInfo.hwndOwner = adtw.get(); + browserInfo.iImage = nsnull; + + if (!aInitialDir.IsEmpty()) { + // the dialog is modal so that |initialDir.get()| will be valid in + // BrowserCallbackProc. Thus, we don't need to clone it. + browserInfo.lParam = (LPARAM) aInitialDir.get(); + browserInfo.lpfn = &BrowseCallbackProc; + } else { + browserInfo.lParam = nsnull; + browserInfo.lpfn = nsnull; } + + LPITEMIDLIST list = ::SHBrowseForFolderW(&browserInfo); + if (list) { + result = ::SHGetPathFromIDListW(list, (LPWSTR)dirBuffer); + if (result) + mUnicodeFile.Assign(dirBuffer); + // free PIDL + CoTaskMemFree(list); + } + + return result; +} + +bool +nsFilePicker::FilePickerWrapper(OPENFILENAMEW* ofn, PickerType aType) +{ + if (!ofn) + return false; + + bool result = false; + AutoWidgetPickerState awps(mParentWidget); MOZ_SEH_TRY { if (aType == PICKER_TYPE_OPEN) result = ::GetOpenFileNameW(ofn); @@ -225,167 +365,90 @@ bool nsFilePicker::GetFileName(OPENFILENAMEW* ofn, PickerType aType) } MOZ_SEH_EXCEPT(true) { NS_ERROR("nsFilePicker GetFileName win32 call generated an exception! This is bad!"); } - if (win) { - win->PickerClosed(); - } return result; } -NS_IMETHODIMP nsFilePicker::ShowW(PRInt16 *aReturnVal) +bool +nsFilePicker::ShowFilePicker(const nsString& aInitialDir) { - NS_ENSURE_ARG_POINTER(aReturnVal); + OPENFILENAMEW ofn = {0}; + ofn.lStructSize = sizeof(ofn); + nsString filterBuffer = mFilterList; + + nsAutoArrayPtr fileBuffer(new PRUnichar[FILE_BUFFER_SIZE]); + wcsncpy(fileBuffer, mDefault.get(), FILE_BUFFER_SIZE); + fileBuffer[FILE_BUFFER_SIZE-1] = '\0'; // null terminate in case copy truncated - // suppress blur events - if (mParentWidget) { - nsIWidget *tmp = mParentWidget; - nsWindow *parent = static_cast(tmp); - parent->SuppressBlurEvents(true); + if (!aInitialDir.IsEmpty()) { + ofn.lpstrInitialDir = aInitialDir.get(); + } + + AutoDestroyTmpWindow adtw((HWND) (mParentWidget.get() ? + mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : NULL)); + + ofn.lpstrTitle = (LPCWSTR)mTitle.get(); + ofn.lpstrFilter = (LPCWSTR)filterBuffer.get(); + ofn.nFilterIndex = mSelectedType; + ofn.lpstrFile = fileBuffer; + ofn.nMaxFile = FILE_BUFFER_SIZE; + ofn.hwndOwner = adtw.get(); + ofn.Flags = OFN_SHAREAWARE | OFN_LONGNAMES | OFN_OVERWRITEPROMPT | + OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_ENABLESIZING | + OFN_EXPLORER; + + // Windows Vista and up won't allow you to use the new looking dialogs with + // a hook procedure. The hook procedure fixes a problem on XP dialogs for + // file picker visibility. Vista and up automatically ensures the file + // picker is always visible. + if (nsWindow::GetWindowsVersion() < VISTA_VERSION) { + ofn.lpfnHook = FilePickerHook; + ofn.Flags |= OFN_ENABLEHOOK; + } + + // Handle add to recent docs settings + if (IsPrivacyModeEnabled() || !mAddToRecentDocs) { + ofn.Flags |= OFN_DONTADDTORECENT; + } + + NS_NAMED_LITERAL_STRING(htmExt, "html"); + + if (!mDefaultExtension.IsEmpty()) { + ofn.lpstrDefExt = mDefaultExtension.get(); + } else { + // Get file extension from suggested filename to detect if we are + // saving an html file. + if (IsDefaultPathHtml()) { + // This is supposed to append ".htm" if user doesn't supply an + // extension but the behavior is sort of weird: + // - Often appends ".html" even if you have an extension + // - It obeys your extension if you put quotes around name + ofn.lpstrDefExt = htmExt.get(); + } + } + + // When possible, instead of using OFN_NOCHANGEDIR to ensure the current + // working directory will not change from this call, we will retrieve the + // current working directory before the call and restore it after the + // call. This flag causes problems on Windows XP for paths that are + // selected like C:test.txt where the user is currently at C:\somepath + // In which case expected result should be C:\somepath\test.txt + AutoRestoreWorkingPath restoreWorkingPath; + // If we can't get the current working directory, the best case is to + // use the OFN_NOCHANGEDIR flag + if (!restoreWorkingPath.HasWorkingPath()) { + ofn.Flags |= OFN_NOCHANGEDIR; } bool result = false; - nsAutoArrayPtr fileBuffer(new PRUnichar[FILE_BUFFER_SIZE+1]); - - wcsncpy(fileBuffer, mDefault.get(), FILE_BUFFER_SIZE); - fileBuffer[FILE_BUFFER_SIZE] = '\0'; // null terminate in case copy truncated - NS_NAMED_LITERAL_STRING(htmExt, "html"); - nsAutoString initialDir; - if (mDisplayDirectory) - mDisplayDirectory->GetPath(initialDir); - - // If no display directory, re-use the last one. - if(initialDir.IsEmpty()) { - // Allocate copy of last used dir. - initialDir = mLastUsedUnicodeDirectory; - } - - mUnicodeFile.Truncate(); - - if (mMode == modeGetFolder) { - PRUnichar dirBuffer[MAX_PATH+1]; - wcsncpy(dirBuffer, initialDir.get(), MAX_PATH); - - BROWSEINFOW browserInfo; - browserInfo.hwndOwner = (HWND) - (mParentWidget.get() ? mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : 0); - browserInfo.pidlRoot = nsnull; - browserInfo.pszDisplayName = (LPWSTR)dirBuffer; - browserInfo.lpszTitle = mTitle.get(); - browserInfo.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; - if (initialDir.Length()) { - // the dialog is modal so that |initialDir.get()| will be valid in - // BrowserCallbackProc. Thus, we don't need to clone it. - browserInfo.lParam = (LPARAM) initialDir.get(); - browserInfo.lpfn = &BrowseCallbackProc; - } else { - browserInfo.lParam = nsnull; - browserInfo.lpfn = nsnull; - } - browserInfo.iImage = nsnull; - - LPITEMIDLIST list = ::SHBrowseForFolderW(&browserInfo); - if (list != NULL) { - result = ::SHGetPathFromIDListW(list, (LPWSTR)fileBuffer); - if (result) { - mUnicodeFile.Assign(fileBuffer); - } - - // free PIDL - CoTaskMemFree(list); - } - } else { - - OPENFILENAMEW ofn; - memset(&ofn, 0, sizeof(ofn)); - ofn.lStructSize = sizeof(ofn); - nsString filterBuffer = mFilterList; - - if (!initialDir.IsEmpty()) { - // The initial directory - ofn.lpstrInitialDir = initialDir.get(); - } - - // A string to be placed in the title bar of the dialog box - ofn.lpstrTitle = (LPCWSTR)mTitle.get(); - // A buffer containing pairs of null-terminated filter strings - ofn.lpstrFilter = (LPCWSTR)filterBuffer.get(); - // The index of the currently selected filter in the File Types control - ofn.nFilterIndex = mSelectedType; - // The file name used to initialize the File Name edit control - ofn.lpstrFile = fileBuffer; - // The max length of lpstrFile - ofn.nMaxFile = FILE_BUFFER_SIZE; - ofn.hwndOwner = (HWND) (mParentWidget.get() ? - mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : 0); - ofn.Flags = OFN_SHAREAWARE | OFN_LONGNAMES | OFN_OVERWRITEPROMPT | - OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_ENABLESIZING | - OFN_EXPLORER; - - // Windows Vista and up won't allow you to use the new looking dialogs with - // a hook procedure. The hook procedure fixes a problem on XP dialogs for - // file picker visibility. Vista and up automatically ensures the file - // picker is always visible. - if (nsWindow::GetWindowsVersion() < VISTA_VERSION) { - ofn.lpfnHook = FilePickerHook; - ofn.Flags |= OFN_ENABLEHOOK; - } - - // Handle add to recent docs settings - nsCOMPtr pbs = - do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID); - bool privacyModeEnabled = false; - if (pbs) { - pbs->GetPrivateBrowsingEnabled(&privacyModeEnabled); - } - if (privacyModeEnabled || !mAddToRecentDocs) { - ofn.Flags |= OFN_DONTADDTORECENT; - } - - if (!mDefaultExtension.IsEmpty()) { - ofn.lpstrDefExt = mDefaultExtension.get(); - } - else { - // Get file extension from suggested filename - // to detect if we are saving an html file - //XXX: nsIFile SHOULD HAVE A GetExtension() METHOD! - PRInt32 extIndex = mDefault.RFind("."); - if ( extIndex >= 0) { - nsAutoString ext; - mDefault.Right(ext, mDefault.Length() - extIndex); - // Should we test for ".cgi", ".asp", ".jsp" and other - // "generated" html pages? - - if ( ext.LowerCaseEqualsLiteral(".htm") || - ext.LowerCaseEqualsLiteral(".html") || - ext.LowerCaseEqualsLiteral(".shtml") ) { - // This is supposed to append ".htm" if user doesn't supply an extension - //XXX Actually, behavior is sort of weird: - // often appends ".html" even if you have an extension - // It obeys your extension if you put quotes around name - ofn.lpstrDefExt = htmExt.get(); - } - } - } - - // When possible, instead of using OFN_NOCHANGEDIR to ensure the current - // working directory will not change from this call, we will retrieve the - // current working directory before the call and restore it after the - // call. This flag causes problems on Windows XP for paths that are - // selected like C:test.txt where the user is currently at C:\somepath - // In which case expected result should be C:\somepath\test.txt - AutoRestoreWorkingPath restoreWorkingPath; - // If we can't get the current working directory, the best case is to - // use the OFN_NOCHANGEDIR flag - if (!restoreWorkingPath.HasWorkingPath()) { - ofn.Flags |= OFN_NOCHANGEDIR; - } - - if (mMode == modeOpen) { + switch(mMode) { + case modeOpen: // FILE MUST EXIST! ofn.Flags |= OFN_FILEMUSTEXIST; - result = GetFileName(&ofn, PICKER_TYPE_OPEN); - } - else if (mMode == modeOpenMultiple) { + result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); + break; + + case modeOpenMultiple: ofn.Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT; // The hook set here ensures that the buffer returned will always be @@ -401,172 +464,165 @@ NS_IMETHODIMP nsFilePicker::ShowW(PRInt16 *aReturnVal) if (nsWindow::GetWindowsVersion() < VISTA_VERSION) { ofn.lpfnHook = MultiFilePickerHook; fileBuffer.forget(); - result = GetFileName(&ofn, PICKER_TYPE_OPEN); + result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); fileBuffer = ofn.lpstrFile; - } - else { - result = GetFileName(&ofn, PICKER_TYPE_OPEN); - } - } - else if (mMode == modeSave) { - ofn.Flags |= OFN_NOREADONLYRETURN; - - // Don't follow shortcuts when saving a shortcut, this can be used - // to trick users (bug 271732) - NS_ConvertUTF16toUTF8 ext(mDefault); - ext.Trim(" .", false, true); // watch out for trailing space and dots - ToLowerCase(ext); - if (StringEndsWith(ext, NS_LITERAL_CSTRING(".lnk")) || - StringEndsWith(ext, NS_LITERAL_CSTRING(".pif")) || - StringEndsWith(ext, NS_LITERAL_CSTRING(".url"))) - ofn.Flags |= OFN_NODEREFERENCELINKS; - - result = GetFileName(&ofn, PICKER_TYPE_SAVE); - if (!result) { - // Error, find out what kind. - if (::GetLastError() == ERROR_INVALID_PARAMETER - || ::CommDlgExtendedError() == FNERR_INVALIDFILENAME - ) { - // probably the default file name is too long or contains illegal characters! - // Try again, without a starting file name. - ofn.lpstrFile[0] = 0; - result = GetFileName(&ofn, PICKER_TYPE_SAVE); - } - } - } - else { - NS_ERROR("unsupported mode"); - } - - if (result) { - // Remember what filter type the user selected - mSelectedType = (PRInt16)ofn.nFilterIndex; - - // Clear out any files from previous Show calls - mFiles.Clear(); - - // Set user-selected location of file or directory - if (mMode == modeOpenMultiple) { - - // from msdn.microsoft.com, "Open and Save As Dialog Boxes" section: - // If you specify OFN_EXPLORER, - // The directory and file name strings are NULL separated, - // with an extra NULL character after the last file name. - // This format enables the Explorer-style dialog boxes - // to return long file names that include spaces. - PRUnichar *current = fileBuffer; - - nsAutoString dirName(current); - // sometimes dirName contains a trailing slash - // and sometimes it doesn't. - if (current[dirName.Length() - 1] != '\\') - dirName.Append((PRUnichar)'\\'); - - nsresult rv; - while (current && *current && *(current + nsCRT::strlen(current) + 1)) { - current = current + nsCRT::strlen(current) + 1; - - nsCOMPtr file = do_CreateInstance("@mozilla.org/file/local;1", &rv); - NS_ENSURE_SUCCESS(rv,rv); - - // Only prepend the directory if the path specified is a relative path - nsAutoString path; - if (PathIsRelativeW(current)) { - path = dirName + nsDependentString(current); - } else { - path = current; - } - - nsAutoString canonicalizedPath; - GetQualifiedPath(path.get(), canonicalizedPath); - - rv = file->InitWithPath(canonicalizedPath); - NS_ENSURE_SUCCESS(rv,rv); - - rv = mFiles.AppendObject(file); - NS_ENSURE_SUCCESS(rv,rv); - } - - // handle the case where the user selected just one - // file. according to msdn.microsoft.com: - // If you specify OFN_ALLOWMULTISELECT and the user selects - // only one file, the lpstrFile string does not have - // a separator between the path and file name. - if (current && *current && (current == fileBuffer)) { - nsCOMPtr file = do_CreateInstance("@mozilla.org/file/local;1", &rv); - NS_ENSURE_SUCCESS(rv,rv); - - nsAutoString canonicalizedPath; - GetQualifiedPath(current, canonicalizedPath); - rv = file->InitWithPath(canonicalizedPath); - NS_ENSURE_SUCCESS(rv,rv); - - rv = mFiles.AppendObject(file); - NS_ENSURE_SUCCESS(rv,rv); - } } else { - GetQualifiedPath(fileBuffer, mUnicodeFile); + result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); } - } - if (ofn.hwndOwner) { - ::DestroyWindow(ofn.hwndOwner); - } - } + break; - if (result) { - PRInt16 returnOKorReplace = returnOK; + case modeSave: + { + ofn.Flags |= OFN_NOREADONLYRETURN; - // Remember last used directory. - nsCOMPtr file(do_CreateInstance("@mozilla.org/file/local;1")); - NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); + // Don't follow shortcuts when saving a shortcut, this can be used + // to trick users (bug 271732) + if (IsDefaultPathLink()) + ofn.Flags |= OFN_NODEREFERENCELINKS; - // XXX InitWithPath() will convert UCS2 to FS path !!! corrupts unicode - file->InitWithPath(mUnicodeFile); - nsCOMPtr dir; - if (NS_SUCCEEDED(file->GetParent(getter_AddRefs(dir)))) { - mDisplayDirectory = do_QueryInterface(dir); - if (mDisplayDirectory) { - if (mLastUsedUnicodeDirectory) { - NS_Free(mLastUsedUnicodeDirectory); - mLastUsedUnicodeDirectory = nsnull; + result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE); + if (!result) { + // Error, find out what kind. + if (GetLastError() == ERROR_INVALID_PARAMETER || + CommDlgExtendedError() == FNERR_INVALIDFILENAME) { + // Probably the default file name is too long or contains illegal + // characters. Try again, without a starting file name. + ofn.lpstrFile[0] = nsnull; + result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE); + } } - - nsAutoString newDir; - mDisplayDirectory->GetPath(newDir); - if(!newDir.IsEmpty()) - mLastUsedUnicodeDirectory = ToNewUnicode(newDir); } + break; + + default: + NS_ERROR("unsupported file picker mode"); + return false; + } + + if (!result) + return false; + + // Remember what filter type the user selected + mSelectedType = (PRInt16)ofn.nFilterIndex; + + // Single file selection, we're done + if (mMode != modeOpenMultiple) { + GetQualifiedPath(fileBuffer, mUnicodeFile); + return true; + } + + // Clear previous file selection list + mFiles.Clear(); + + // Set user-selected location of file or directory. From msdn's "Open and + // Save As Dialog Boxes" section: + // If you specify OFN_EXPLORER, the directory and file name strings are NULL + // separated, with an extra NULL character after the last file name. This + // format enables the Explorer-style dialog boxes to return long file names + // that include spaces. + PRUnichar *current = fileBuffer; + + nsAutoString dirName(current); + // Sometimes dirName contains a trailing slash and sometimes it doesn't: + if (current[dirName.Length() - 1] != '\\') + dirName.Append((PRUnichar)'\\'); + + while (current && *current && *(current + nsCRT::strlen(current) + 1)) { + current = current + nsCRT::strlen(current) + 1; + + nsCOMPtr file = do_CreateInstance("@mozilla.org/file/local;1"); + NS_ENSURE_TRUE(file, false); + + // Only prepend the directory if the path specified is a relative path + nsAutoString path; + if (PathIsRelativeW(current)) { + path = dirName + nsDependentString(current); + } else { + path = current; } - if (mMode == modeSave) { - // Windows does not return resultReplace, - // we must check if file already exists - bool exists = false; - file->Exists(&exists); + nsAutoString canonicalizedPath; + GetQualifiedPath(path.get(), canonicalizedPath); + if (NS_FAILED(file->InitWithPath(canonicalizedPath)) || + !mFiles.AppendObject(file)) + return false; + } + + // Handle the case where the user selected just one file. From msdn: If you + // specify OFN_ALLOWMULTISELECT and the user selects only one file the + // lpstrFile string does not have a separator between the path and file name. + if (current && *current && (current == fileBuffer)) { + nsCOMPtr file = do_CreateInstance("@mozilla.org/file/local;1"); + NS_ENSURE_TRUE(file, false); + + nsAutoString canonicalizedPath; + GetQualifiedPath(current, canonicalizedPath); + if (NS_FAILED(file->InitWithPath(canonicalizedPath)) || + !mFiles.AppendObject(file)) + return false; + } - if (exists) - returnOKorReplace = returnReplace; + return true; +} + +NS_IMETHODIMP +nsFilePicker::ShowW(PRInt16 *aReturnVal) +{ + NS_ENSURE_ARG_POINTER(aReturnVal); + + *aReturnVal = returnCancel; + + AutoSuppressEvents supress(mParentWidget); + + nsAutoString initialDir; + if (mDisplayDirectory) + mDisplayDirectory->GetPath(initialDir); + + // If no display directory, re-use the last one. + if(initialDir.IsEmpty()) { + // Allocate copy of last used dir. + initialDir = mLastUsedUnicodeDirectory; + } + + mUnicodeFile.Truncate(); + + bool result = false; + if (mMode == modeGetFolder) { + result = ShowFolderPicker(initialDir); + } else { + result = ShowFilePicker(initialDir); + } + + // exit, and return returnCancel in aReturnVal + if (!result) + return NS_OK; + + RememberLastUsedDirectory(); + + PRInt16 retValue = returnOK; + if (mMode == modeSave) { + // Windows does not return resultReplace, we must check if file + // already exists. + nsCOMPtr file(do_CreateInstance("@mozilla.org/file/local;1")); + bool flag = false; + if (file && NS_SUCCEEDED(file->InitWithPath(mUnicodeFile)) && + NS_SUCCEEDED(file->Exists(&flag)) && flag) { + retValue = returnReplace; } - *aReturnVal = returnOKorReplace; - } - else { - *aReturnVal = returnCancel; - } - if (mParentWidget) { - nsIWidget *tmp = mParentWidget; - nsWindow *parent = static_cast(tmp); - parent->SuppressBlurEvents(false); } + *aReturnVal = retValue; return NS_OK; } -NS_IMETHODIMP nsFilePicker::Show(PRInt16 *aReturnVal) +NS_IMETHODIMP +nsFilePicker::Show(PRInt16 *aReturnVal) { return ShowW(aReturnVal); } -NS_IMETHODIMP nsFilePicker::GetFile(nsILocalFile **aFile) +NS_IMETHODIMP +nsFilePicker::GetFile(nsILocalFile **aFile) { NS_ENSURE_ARG_POINTER(aFile); *aFile = nsnull; @@ -585,7 +641,8 @@ NS_IMETHODIMP nsFilePicker::GetFile(nsILocalFile **aFile) return NS_OK; } -NS_IMETHODIMP nsFilePicker::GetFileURL(nsIURI **aFileURL) +NS_IMETHODIMP +nsFilePicker::GetFileURL(nsIURI **aFileURL) { *aFileURL = nsnull; nsCOMPtr file; @@ -596,14 +653,16 @@ NS_IMETHODIMP nsFilePicker::GetFileURL(nsIURI **aFileURL) return NS_NewFileURI(aFileURL, file); } -NS_IMETHODIMP nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles) +NS_IMETHODIMP +nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles) { NS_ENSURE_ARG_POINTER(aFiles); return NS_NewArrayEnumerator(aFiles, mFiles); } // Get the file + path -NS_IMETHODIMP nsFilePicker::SetDefaultString(const nsAString& aString) +NS_IMETHODIMP +nsFilePicker::SetDefaultString(const nsAString& aString) { mDefault = aString; @@ -635,42 +694,48 @@ NS_IMETHODIMP nsFilePicker::SetDefaultString(const nsAString& aString) return NS_OK; } -NS_IMETHODIMP nsFilePicker::GetDefaultString(nsAString& aString) +NS_IMETHODIMP +nsFilePicker::GetDefaultString(nsAString& aString) { return NS_ERROR_FAILURE; } // The default extension to use for files -NS_IMETHODIMP nsFilePicker::GetDefaultExtension(nsAString& aExtension) +NS_IMETHODIMP +nsFilePicker::GetDefaultExtension(nsAString& aExtension) { aExtension = mDefaultExtension; return NS_OK; } -NS_IMETHODIMP nsFilePicker::SetDefaultExtension(const nsAString& aExtension) +NS_IMETHODIMP +nsFilePicker::SetDefaultExtension(const nsAString& aExtension) { mDefaultExtension = aExtension; return NS_OK; } // Set the filter index -NS_IMETHODIMP nsFilePicker::GetFilterIndex(PRInt32 *aFilterIndex) +NS_IMETHODIMP +nsFilePicker::GetFilterIndex(PRInt32 *aFilterIndex) { // Windows' filter index is 1-based, we use a 0-based system. *aFilterIndex = mSelectedType - 1; return NS_OK; } -NS_IMETHODIMP nsFilePicker::SetFilterIndex(PRInt32 aFilterIndex) +NS_IMETHODIMP +nsFilePicker::SetFilterIndex(PRInt32 aFilterIndex) { // Windows' filter index is 1-based, we use a 0-based system. mSelectedType = aFilterIndex + 1; return NS_OK; } -void nsFilePicker::InitNative(nsIWidget *aParent, - const nsAString& aTitle, - PRInt16 aMode) +void +nsFilePicker::InitNative(nsIWidget *aParent, + const nsAString& aTitle, + PRInt16 aMode) { mParentWidget = aParent; mTitle.Assign(aTitle); @@ -713,19 +778,69 @@ nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) return NS_OK; } -AutoRestoreWorkingPath::AutoRestoreWorkingPath() +void +nsFilePicker::RememberLastUsedDirectory() { - DWORD bufferLength = GetCurrentDirectoryW(0, NULL); - mWorkingPath = new PRUnichar[bufferLength]; - if (GetCurrentDirectoryW(bufferLength, mWorkingPath) == 0) { - mWorkingPath = NULL; + nsCOMPtr file(do_CreateInstance("@mozilla.org/file/local;1")); + if (!file || NS_FAILED(file->InitWithPath(mUnicodeFile))) { + NS_WARNING("RememberLastUsedDirectory failed to init file path."); + return; } + + nsCOMPtr dir; + nsAutoString newDir; + if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) || + !(mDisplayDirectory = do_QueryInterface(dir)) || + NS_FAILED(mDisplayDirectory->GetPath(newDir)) || + newDir.IsEmpty()) { + NS_WARNING("RememberLastUsedDirectory failed to get parent directory."); + return; + } + + if (mLastUsedUnicodeDirectory) { + NS_Free(mLastUsedUnicodeDirectory); + mLastUsedUnicodeDirectory = nsnull; + } + mLastUsedUnicodeDirectory = ToNewUnicode(newDir); } -AutoRestoreWorkingPath::~AutoRestoreWorkingPath() +bool +nsFilePicker::IsPrivacyModeEnabled() { - if (HasWorkingPath()) { - ::SetCurrentDirectoryW(mWorkingPath); + // Handle add to recent docs settings + nsCOMPtr pbs = + do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID); + bool privacyModeEnabled = false; + if (pbs) { + pbs->GetPrivateBrowsingEnabled(&privacyModeEnabled); } + return privacyModeEnabled; } +bool +nsFilePicker::IsDefaultPathLink() +{ + NS_ConvertUTF16toUTF8 ext(mDefault); + ext.Trim(" .", false, true); // watch out for trailing space and dots + ToLowerCase(ext); + if (StringEndsWith(ext, NS_LITERAL_CSTRING(".lnk")) || + StringEndsWith(ext, NS_LITERAL_CSTRING(".pif")) || + StringEndsWith(ext, NS_LITERAL_CSTRING(".url"))) + return true; + return false; +} + +bool +nsFilePicker::IsDefaultPathHtml() +{ + PRInt32 extIndex = mDefault.RFind("."); + if (extIndex >= 0) { + nsAutoString ext; + mDefault.Right(ext, mDefault.Length() - extIndex); + if (ext.LowerCaseEqualsLiteral(".htm") || + ext.LowerCaseEqualsLiteral(".html") || + ext.LowerCaseEqualsLiteral(".shtml")) + return true; + } + return false; +} diff --git a/widget/src/windows/nsFilePicker.h b/widget/src/windows/nsFilePicker.h index 8448113c68da..cf503974737e 100644 --- a/widget/src/windows/nsFilePicker.h +++ b/widget/src/windows/nsFilePicker.h @@ -23,6 +23,7 @@ * Contributor(s): * Stuart Parmenter * Seth Spitzer + * Jim Mathies * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -90,7 +91,13 @@ protected: PRInt16 aMode); static void GetQualifiedPath(const PRUnichar *aInPath, nsString &aOutPath); void GetFilterListArray(nsString& aFilterList); - bool GetFileName(OPENFILENAMEW* ofn, PickerType aType); + bool FilePickerWrapper(OPENFILENAMEW* ofn, PickerType aType); + bool ShowFilePicker(const nsString& aInitialDir); + bool ShowFolderPicker(const nsString& aInitialDir); + void RememberLastUsedDirectory(); + bool IsPrivacyModeEnabled(); + bool IsDefaultPathLink(); + bool IsDefaultPathHtml(); nsCOMPtr mParentWidget; nsString mTitle; @@ -106,20 +113,4 @@ protected: static PRUnichar *mLastUsedUnicodeDirectory; }; -// The constructor will obtain the working path, the destructor -// will set the working path back to what it used to be. -class AutoRestoreWorkingPath -{ -public: - AutoRestoreWorkingPath(); - ~AutoRestoreWorkingPath(); - inline bool HasWorkingPath() const - { - return mWorkingPath != NULL; - } - -private: - nsAutoArrayPtr mWorkingPath; -}; - #endif // nsFilePicker_h__