Bug 661991 - Cleanup win widget nsFilePicker. r=neil, a=ehsan

This commit is contained in:
Jim Mathies 2011-12-14 15:22:16 -06:00
parent 13a6f5f387
commit 4b15d87e29
2 changed files with 452 additions and 346 deletions

View File

@ -23,6 +23,7 @@
* Contributor(s):
* Stuart Parmenter <pavlov@netscape.com>
* Seth Spitzer <sspitzer@netscape.com>
* Jim Mathies <jmathies@mozilla.com>
*
* 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<nsWindow *>(aWidget)) {
SuppressWidgetEvents(true);
}
~AutoSuppressEvents() {
SuppressWidgetEvents(false);
}
private:
void SuppressWidgetEvents(bool aFlag) {
if (mWindow) {
mWindow->SuppressBlurEvents(aFlag);
}
}
nsRefPtr<nsWindow> 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<PRUnichar> 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<nsWindow *>(aWidget)) {
PickerState(true);
}
~AutoWidgetPickerState() {
PickerState(false);
}
private:
void PickerState(bool aFlag) {
if (mWindow) {
if (aFlag)
mWindow->PickerOpen();
else
mWindow->PickerClosed();
}
}
nsRefPtr<nsWindow> 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<nsWindow*>(mParentWidget.get());
if (win) {
win->PickerOpen();
nsAutoArrayPtr<PRUnichar> 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<PRUnichar> 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<nsWindow *>(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<PRUnichar> 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<nsIPrivateBrowsingService> 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<nsILocalFile> 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<nsILocalFile> 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<nsILocalFile> 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<nsIFile> 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<nsILocalFile> 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<nsILocalFile> 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<nsILocalFile> 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<nsWindow *>(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<nsILocalFile> 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<nsILocalFile> 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<nsIFile> 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<nsIPrivateBrowsingService> 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;
}

View File

@ -23,6 +23,7 @@
* Contributor(s):
* Stuart Parmenter <pavlov@netscape.com>
* Seth Spitzer <sspitzer@netscape.com>
* Jim Mathies <jmathies@mozilla.com>
*
* 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<nsIWidget> 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<PRUnichar> mWorkingPath;
};
#endif // nsFilePicker_h__