michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsFilePicker.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "mozilla/WindowsVersion.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsWindow.h" michael@0: #include "nsILoadContext.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsEnumeratorUtils.h" michael@0: #include "nsCRT.h" michael@0: #include "nsString.h" michael@0: #include "nsToolkit.h" michael@0: #include "WinUtils.h" michael@0: #include "nsPIDOMWindow.h" michael@0: michael@0: using mozilla::IsVistaOrLater; michael@0: using namespace mozilla::widget; michael@0: michael@0: char16_t *nsFilePicker::mLastUsedUnicodeDirectory; michael@0: char nsFilePicker::mLastUsedDirectory[MAX_PATH+1] = { 0 }; michael@0: michael@0: static const wchar_t kDialogPtrProp[] = L"DialogPtrProperty"; michael@0: static const DWORD kDialogTimerID = 9999; michael@0: static const unsigned long kDialogTimerTimeout = 300; michael@0: michael@0: #define MAX_EXTENSION_LENGTH 10 michael@0: #define FILE_BUFFER_SIZE 4096 michael@0: michael@0: typedef DWORD FILEOPENDIALOGOPTIONS; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // Helper classes michael@0: michael@0: // Manages matching SuppressBlurEvents calls on the parent widget. michael@0: class AutoSuppressEvents michael@0: { michael@0: public: michael@0: explicit AutoSuppressEvents(nsIWidget* aWidget) : michael@0: mWindow(static_cast(aWidget)) { michael@0: SuppressWidgetEvents(true); michael@0: } michael@0: michael@0: ~AutoSuppressEvents() { michael@0: SuppressWidgetEvents(false); michael@0: } michael@0: private: michael@0: void SuppressWidgetEvents(bool aFlag) { michael@0: if (mWindow) { michael@0: mWindow->SuppressBlurEvents(aFlag); michael@0: } michael@0: } michael@0: nsRefPtr mWindow; michael@0: }; michael@0: michael@0: // Manages the current working path. michael@0: class AutoRestoreWorkingPath michael@0: { michael@0: public: michael@0: AutoRestoreWorkingPath() { michael@0: DWORD bufferLength = GetCurrentDirectoryW(0, nullptr); michael@0: mWorkingPath = new wchar_t[bufferLength]; michael@0: if (GetCurrentDirectoryW(bufferLength, mWorkingPath) == 0) { michael@0: mWorkingPath = nullptr; michael@0: } michael@0: } michael@0: michael@0: ~AutoRestoreWorkingPath() { michael@0: if (HasWorkingPath()) { michael@0: ::SetCurrentDirectoryW(mWorkingPath); michael@0: } michael@0: } michael@0: michael@0: inline bool HasWorkingPath() const { michael@0: return mWorkingPath != nullptr; michael@0: } michael@0: private: michael@0: nsAutoArrayPtr mWorkingPath; michael@0: }; michael@0: michael@0: // Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are michael@0: // temporary child windows of mParentWidget created to address RTL issues michael@0: // in picker dialogs. We are responsible for destroying these. michael@0: class AutoDestroyTmpWindow michael@0: { michael@0: public: michael@0: explicit AutoDestroyTmpWindow(HWND aTmpWnd) : michael@0: mWnd(aTmpWnd) { michael@0: } michael@0: michael@0: ~AutoDestroyTmpWindow() { michael@0: if (mWnd) michael@0: DestroyWindow(mWnd); michael@0: } michael@0: michael@0: inline HWND get() const { return mWnd; } michael@0: private: michael@0: HWND mWnd; michael@0: }; michael@0: michael@0: // Manages matching PickerOpen/PickerClosed calls on the parent widget. michael@0: class AutoWidgetPickerState michael@0: { michael@0: public: michael@0: explicit AutoWidgetPickerState(nsIWidget* aWidget) : michael@0: mWindow(static_cast(aWidget)) { michael@0: PickerState(true); michael@0: } michael@0: michael@0: ~AutoWidgetPickerState() { michael@0: PickerState(false); michael@0: } michael@0: private: michael@0: void PickerState(bool aFlag) { michael@0: if (mWindow) { michael@0: if (aFlag) michael@0: mWindow->PickerOpen(); michael@0: else michael@0: mWindow->PickerClosed(); michael@0: } michael@0: } michael@0: nsRefPtr mWindow; michael@0: }; michael@0: michael@0: // Manages a simple callback timer michael@0: class AutoTimerCallbackCancel michael@0: { michael@0: public: michael@0: AutoTimerCallbackCancel(nsFilePicker* aTarget, michael@0: nsTimerCallbackFunc aCallbackFunc) { michael@0: Init(aTarget, aCallbackFunc); michael@0: } michael@0: michael@0: ~AutoTimerCallbackCancel() { michael@0: if (mPickerCallbackTimer) { michael@0: mPickerCallbackTimer->Cancel(); michael@0: } michael@0: } michael@0: michael@0: private: michael@0: void Init(nsFilePicker* aTarget, michael@0: nsTimerCallbackFunc aCallbackFunc) { michael@0: mPickerCallbackTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: if (!mPickerCallbackTimer) { michael@0: NS_WARNING("do_CreateInstance for timer failed??"); michael@0: return; michael@0: } michael@0: mPickerCallbackTimer->InitWithFuncCallback(aCallbackFunc, michael@0: aTarget, michael@0: kDialogTimerTimeout, michael@0: nsITimer::TYPE_REPEATING_SLACK); michael@0: } michael@0: nsCOMPtr mPickerCallbackTimer; michael@0: michael@0: }; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // nsIFilePicker michael@0: michael@0: nsFilePicker::nsFilePicker() : michael@0: mSelectedType(1) michael@0: , mDlgWnd(nullptr) michael@0: , mFDECookie(0) michael@0: { michael@0: CoInitialize(nullptr); michael@0: } michael@0: michael@0: nsFilePicker::~nsFilePicker() michael@0: { michael@0: if (mLastUsedUnicodeDirectory) { michael@0: NS_Free(mLastUsedUnicodeDirectory); michael@0: mLastUsedUnicodeDirectory = nullptr; michael@0: } michael@0: CoUninitialize(); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker) michael@0: michael@0: NS_IMETHODIMP nsFilePicker::Init(nsIDOMWindow *aParent, const nsAString& aTitle, int16_t aMode) michael@0: { michael@0: nsCOMPtr window = do_QueryInterface(aParent); michael@0: nsIDocShell* docShell = window ? window->GetDocShell() : nullptr; michael@0: mLoadContext = do_QueryInterface(docShell); michael@0: michael@0: return nsBaseFilePicker::Init(aParent, aTitle, aMode); michael@0: } michael@0: michael@0: STDMETHODIMP nsFilePicker::QueryInterface(REFIID refiid, void** ppvResult) michael@0: { michael@0: *ppvResult = nullptr; michael@0: if (IID_IUnknown == refiid || michael@0: refiid == IID_IFileDialogEvents) { michael@0: *ppvResult = this; michael@0: } michael@0: michael@0: if (nullptr != *ppvResult) { michael@0: ((LPUNKNOWN)*ppvResult)->AddRef(); michael@0: return S_OK; michael@0: } michael@0: michael@0: return E_NOINTERFACE; michael@0: } michael@0: michael@0: /* michael@0: * XP picker callbacks michael@0: */ michael@0: michael@0: // Show - Display the file dialog michael@0: int CALLBACK michael@0: BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) michael@0: { michael@0: if (uMsg == BFFM_INITIALIZED) michael@0: { michael@0: char16_t * filePath = (char16_t *) lpData; michael@0: if (filePath) michael@0: ::SendMessageW(hwnd, BFFM_SETSELECTIONW, michael@0: TRUE /* true because lpData is a path string */, michael@0: lpData); michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: static void michael@0: EnsureWindowVisible(HWND hwnd) michael@0: { michael@0: // Obtain the monitor which has the largest area of intersection michael@0: // with the window, or nullptr if there is no intersection. michael@0: HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); michael@0: if (!monitor) { michael@0: // The window is not visible, we should reposition it to the same place as its parent michael@0: HWND parentHwnd = GetParent(hwnd); michael@0: RECT parentRect; michael@0: GetWindowRect(parentHwnd, &parentRect); michael@0: SetWindowPos(hwnd, nullptr, parentRect.left, parentRect.top, 0, 0, michael@0: SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); michael@0: } michael@0: } michael@0: michael@0: // Callback hook which will ensure that the window is visible. Currently michael@0: // only in use on os <= XP. michael@0: UINT_PTR CALLBACK michael@0: nsFilePicker::FilePickerHook(HWND hwnd, michael@0: UINT msg, michael@0: WPARAM wParam, michael@0: LPARAM lParam) michael@0: { michael@0: switch(msg) { michael@0: case WM_NOTIFY: michael@0: { michael@0: LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam; michael@0: if (!lpofn || !lpofn->lpOFN) { michael@0: return 0; michael@0: } michael@0: michael@0: if (CDN_INITDONE == lpofn->hdr.code) { michael@0: // The Window will be automatically moved to the last position after michael@0: // CDN_INITDONE. We post a message to ensure the window will be visible michael@0: // so it will be done after the automatic last position window move. michael@0: PostMessage(hwnd, MOZ_WM_ENSUREVISIBLE, 0, 0); michael@0: } michael@0: } michael@0: break; michael@0: case MOZ_WM_ENSUREVISIBLE: michael@0: EnsureWindowVisible(GetParent(hwnd)); michael@0: break; michael@0: case WM_INITDIALOG: michael@0: { michael@0: OPENFILENAMEW* pofn = reinterpret_cast(lParam); michael@0: SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData); michael@0: nsFilePicker* picker = reinterpret_cast(pofn->lCustData); michael@0: if (picker) { michael@0: picker->SetDialogHandle(hwnd); michael@0: SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr); michael@0: } michael@0: } michael@0: break; michael@0: case WM_TIMER: michael@0: { michael@0: // Check to see if our parent has been torn down, if so, we close too. michael@0: if (wParam == kDialogTimerID) { michael@0: nsFilePicker* picker = michael@0: reinterpret_cast(GetProp(hwnd, kDialogPtrProp)); michael@0: if (picker && picker->ClosePickerIfNeeded(true)) { michael@0: KillTimer(hwnd, kDialogTimerID); michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: michael@0: // Callback hook which will dynamically allocate a buffer large enough michael@0: // for the file picker dialog. Currently only in use on os <= XP. michael@0: UINT_PTR CALLBACK michael@0: nsFilePicker::MultiFilePickerHook(HWND hwnd, michael@0: UINT msg, michael@0: WPARAM wParam, michael@0: LPARAM lParam) michael@0: { michael@0: switch (msg) { michael@0: case WM_INITDIALOG: michael@0: { michael@0: // Finds the child drop down of a File Picker dialog and sets the michael@0: // maximum amount of text it can hold when typed in manually. michael@0: // A wParam of 0 mean 0x7FFFFFFE characters. michael@0: HWND comboBox = FindWindowEx(GetParent(hwnd), nullptr, michael@0: L"ComboBoxEx32", nullptr ); michael@0: if(comboBox) michael@0: SendMessage(comboBox, CB_LIMITTEXT, 0, 0); michael@0: // Store our nsFilePicker ptr for future use michael@0: OPENFILENAMEW* pofn = reinterpret_cast(lParam); michael@0: SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData); michael@0: nsFilePicker* picker = michael@0: reinterpret_cast(pofn->lCustData); michael@0: if (picker) { michael@0: picker->SetDialogHandle(hwnd); michael@0: SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr); michael@0: } michael@0: } michael@0: break; michael@0: case WM_NOTIFY: michael@0: { michael@0: LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam; michael@0: if (!lpofn || !lpofn->lpOFN) { michael@0: return 0; michael@0: } michael@0: // CDN_SELCHANGE is sent when the selection in the list box of the file michael@0: // selection dialog changes michael@0: if (lpofn->hdr.code == CDN_SELCHANGE) { michael@0: HWND parentHWND = GetParent(hwnd); michael@0: michael@0: // Get the required size for the selected files buffer michael@0: UINT newBufLength = 0; michael@0: int requiredBufLength = CommDlg_OpenSave_GetSpecW(parentHWND, michael@0: nullptr, 0); michael@0: if(requiredBufLength >= 0) michael@0: newBufLength += requiredBufLength; michael@0: else michael@0: newBufLength += MAX_PATH; michael@0: michael@0: // If the user selects multiple files, the buffer contains the michael@0: // current directory followed by the file names of the selected michael@0: // files. So make room for the directory path. If the user michael@0: // selects a single file, it is no harm to add extra space. michael@0: requiredBufLength = CommDlg_OpenSave_GetFolderPathW(parentHWND, michael@0: nullptr, 0); michael@0: if(requiredBufLength >= 0) michael@0: newBufLength += requiredBufLength; michael@0: else michael@0: newBufLength += MAX_PATH; michael@0: michael@0: // Check if lpstrFile and nMaxFile are large enough michael@0: if (newBufLength > lpofn->lpOFN->nMaxFile) { michael@0: if (lpofn->lpOFN->lpstrFile) michael@0: delete[] lpofn->lpOFN->lpstrFile; michael@0: michael@0: // We allocate FILE_BUFFER_SIZE more bytes than is needed so that michael@0: // if the user selects a file and holds down shift and down to michael@0: // select additional items, we will not continuously reallocate michael@0: newBufLength += FILE_BUFFER_SIZE; michael@0: michael@0: wchar_t* filesBuffer = new wchar_t[newBufLength]; michael@0: ZeroMemory(filesBuffer, newBufLength * sizeof(wchar_t)); michael@0: michael@0: lpofn->lpOFN->lpstrFile = filesBuffer; michael@0: lpofn->lpOFN->nMaxFile = newBufLength; michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: case WM_TIMER: michael@0: { michael@0: // Check to see if our parent has been torn down, if so, we close too. michael@0: if (wParam == kDialogTimerID) { michael@0: nsFilePicker* picker = michael@0: reinterpret_cast(GetProp(hwnd, kDialogPtrProp)); michael@0: if (picker && picker->ClosePickerIfNeeded(true)) { michael@0: KillTimer(hwnd, kDialogTimerID); michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: michael@0: return FilePickerHook(hwnd, msg, wParam, lParam); michael@0: } michael@0: michael@0: /* michael@0: * Vista+ callbacks michael@0: */ michael@0: michael@0: HRESULT michael@0: nsFilePicker::OnFileOk(IFileDialog *pfd) michael@0: { michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT michael@0: nsFilePicker::OnFolderChanging(IFileDialog *pfd, michael@0: IShellItem *psiFolder) michael@0: { michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT michael@0: nsFilePicker::OnFolderChange(IFileDialog *pfd) michael@0: { michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT michael@0: nsFilePicker::OnSelectionChange(IFileDialog *pfd) michael@0: { michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT michael@0: nsFilePicker::OnShareViolation(IFileDialog *pfd, michael@0: IShellItem *psi, michael@0: FDE_SHAREVIOLATION_RESPONSE *pResponse) michael@0: { michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT michael@0: nsFilePicker::OnTypeChange(IFileDialog *pfd) michael@0: { michael@0: // Failures here result in errors due to security concerns. michael@0: nsRefPtr win; michael@0: pfd->QueryInterface(IID_IOleWindow, getter_AddRefs(win)); michael@0: if (!win) { michael@0: NS_ERROR("Could not retrieve the IOleWindow interface for IFileDialog."); michael@0: return S_OK; michael@0: } michael@0: HWND hwnd = nullptr; michael@0: win->GetWindow(&hwnd); michael@0: if (!hwnd) { michael@0: NS_ERROR("Could not retrieve the HWND for IFileDialog."); michael@0: return S_OK; michael@0: } michael@0: michael@0: SetDialogHandle(hwnd); michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT michael@0: nsFilePicker::OnOverwrite(IFileDialog *pfd, michael@0: IShellItem *psi, michael@0: FDE_OVERWRITE_RESPONSE *pResponse) michael@0: { michael@0: return S_OK; michael@0: } michael@0: michael@0: /* michael@0: * Close on parent close logic michael@0: */ michael@0: michael@0: bool michael@0: nsFilePicker::ClosePickerIfNeeded(bool aIsXPDialog) michael@0: { michael@0: if (!mParentWidget || !mDlgWnd) michael@0: return false; michael@0: michael@0: nsWindow *win = static_cast(mParentWidget.get()); michael@0: // Note, the xp callbacks hand us an inner window, so we have to step up michael@0: // one to get the actual dialog. michael@0: HWND dlgWnd; michael@0: if (aIsXPDialog) michael@0: dlgWnd = GetParent(mDlgWnd); michael@0: else michael@0: dlgWnd = mDlgWnd; michael@0: if (IsWindow(dlgWnd) && IsWindowVisible(dlgWnd) && win->DestroyCalled()) { michael@0: wchar_t className[64]; michael@0: // Make sure we have the right window michael@0: if (GetClassNameW(dlgWnd, className, mozilla::ArrayLength(className)) && michael@0: !wcscmp(className, L"#32770") && michael@0: DestroyWindow(dlgWnd)) { michael@0: mDlgWnd = nullptr; michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsFilePicker::PickerCallbackTimerFunc(nsITimer *aTimer, void *aCtx) michael@0: { michael@0: nsFilePicker* picker = (nsFilePicker*)aCtx; michael@0: if (picker->ClosePickerIfNeeded(false)) { michael@0: aTimer->Cancel(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsFilePicker::SetDialogHandle(HWND aWnd) michael@0: { michael@0: if (!aWnd || mDlgWnd) michael@0: return; michael@0: mDlgWnd = aWnd; michael@0: } michael@0: michael@0: /* michael@0: * Folder picker invocation michael@0: */ michael@0: michael@0: // Open the older XP style folder picker dialog. We end up in this call michael@0: // on XP systems or when platform is built without the longhorn SDK. michael@0: bool michael@0: nsFilePicker::ShowXPFolderPicker(const nsString& aInitialDir) michael@0: { michael@0: bool result = false; michael@0: michael@0: nsAutoArrayPtr dirBuffer(new wchar_t[FILE_BUFFER_SIZE]); michael@0: wcsncpy(dirBuffer, aInitialDir.get(), FILE_BUFFER_SIZE); michael@0: dirBuffer[FILE_BUFFER_SIZE-1] = '\0'; michael@0: michael@0: AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? michael@0: mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); michael@0: michael@0: BROWSEINFOW browserInfo = {0}; michael@0: browserInfo.pidlRoot = nullptr; michael@0: browserInfo.pszDisplayName = dirBuffer; michael@0: browserInfo.lpszTitle = mTitle.get(); michael@0: browserInfo.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; michael@0: browserInfo.hwndOwner = adtw.get(); michael@0: browserInfo.iImage = 0; michael@0: browserInfo.lParam = reinterpret_cast(this); michael@0: michael@0: if (!aInitialDir.IsEmpty()) { michael@0: // the dialog is modal so that |initialDir.get()| will be valid in michael@0: // BrowserCallbackProc. Thus, we don't need to clone it. michael@0: browserInfo.lParam = (LPARAM) aInitialDir.get(); michael@0: browserInfo.lpfn = &BrowseCallbackProc; michael@0: } else { michael@0: browserInfo.lParam = 0; michael@0: browserInfo.lpfn = nullptr; michael@0: } michael@0: michael@0: LPITEMIDLIST list = ::SHBrowseForFolderW(&browserInfo); michael@0: if (list) { michael@0: result = ::SHGetPathFromIDListW(list, dirBuffer); michael@0: if (result) michael@0: mUnicodeFile.Assign(static_cast(dirBuffer)); michael@0: // free PIDL michael@0: CoTaskMemFree(list); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: /* michael@0: * Show a folder picker post Windows XP michael@0: * michael@0: * @param aInitialDir The initial directory, the last used directory will be michael@0: * used if left blank. michael@0: * @param aWasInitError Out parameter will hold true if there was an error michael@0: * before the folder picker is shown. michael@0: * @return true if a file was selected successfully. michael@0: */ michael@0: bool michael@0: nsFilePicker::ShowFolderPicker(const nsString& aInitialDir, bool &aWasInitError) michael@0: { michael@0: nsRefPtr dialog; michael@0: if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC, michael@0: IID_IFileOpenDialog, michael@0: getter_AddRefs(dialog)))) { michael@0: aWasInitError = true; michael@0: return false; michael@0: } michael@0: aWasInitError = false; michael@0: michael@0: // hook up event callbacks michael@0: dialog->Advise(this, &mFDECookie); michael@0: michael@0: // options michael@0: FILEOPENDIALOGOPTIONS fos = FOS_PICKFOLDERS; michael@0: dialog->SetOptions(fos); michael@0: michael@0: // initial strings michael@0: dialog->SetTitle(mTitle.get()); michael@0: if (!aInitialDir.IsEmpty()) { michael@0: nsRefPtr folder; michael@0: if (SUCCEEDED( michael@0: WinUtils::SHCreateItemFromParsingName(aInitialDir.get(), nullptr, michael@0: IID_IShellItem, michael@0: getter_AddRefs(folder)))) { michael@0: dialog->SetFolder(folder); michael@0: } michael@0: } michael@0: michael@0: AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? michael@0: mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); michael@0: michael@0: // display michael@0: nsRefPtr item; michael@0: if (FAILED(dialog->Show(adtw.get())) || michael@0: FAILED(dialog->GetResult(getter_AddRefs(item))) || michael@0: !item) { michael@0: dialog->Unadvise(mFDECookie); michael@0: return false; michael@0: } michael@0: dialog->Unadvise(mFDECookie); michael@0: michael@0: // results michael@0: michael@0: // If the user chose a Win7 Library, resolve to the library's michael@0: // default save folder. michael@0: nsRefPtr folderPath; michael@0: nsRefPtr shellLib; michael@0: CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC, michael@0: IID_IShellLibrary, getter_AddRefs(shellLib)); michael@0: if (shellLib && michael@0: SUCCEEDED(shellLib->LoadLibraryFromItem(item, STGM_READ)) && michael@0: SUCCEEDED(shellLib->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem, michael@0: getter_AddRefs(folderPath)))) { michael@0: item.swap(folderPath); michael@0: } michael@0: michael@0: // get the folder's file system path michael@0: return WinUtils::GetShellItemPath(item, mUnicodeFile); michael@0: } michael@0: michael@0: /* michael@0: * File open and save picker invocation michael@0: */ michael@0: michael@0: bool michael@0: nsFilePicker::FilePickerWrapper(OPENFILENAMEW* ofn, PickerType aType) michael@0: { michael@0: if (!ofn) michael@0: return false; michael@0: michael@0: bool result = false; michael@0: AutoWidgetPickerState awps(mParentWidget); michael@0: MOZ_SEH_TRY { michael@0: if (aType == PICKER_TYPE_OPEN) michael@0: result = ::GetOpenFileNameW(ofn); michael@0: else if (aType == PICKER_TYPE_SAVE) michael@0: result = ::GetSaveFileNameW(ofn); michael@0: } MOZ_SEH_EXCEPT(true) { michael@0: NS_ERROR("nsFilePicker GetFileName win32 call generated an exception! This is bad!"); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: bool michael@0: nsFilePicker::ShowXPFilePicker(const nsString& aInitialDir) michael@0: { michael@0: OPENFILENAMEW ofn = {0}; michael@0: ofn.lStructSize = sizeof(ofn); michael@0: nsString filterBuffer = mFilterList; michael@0: michael@0: nsAutoArrayPtr fileBuffer(new wchar_t[FILE_BUFFER_SIZE]); michael@0: wcsncpy(fileBuffer, mDefaultFilePath.get(), FILE_BUFFER_SIZE); michael@0: fileBuffer[FILE_BUFFER_SIZE-1] = '\0'; // null terminate in case copy truncated michael@0: michael@0: if (!aInitialDir.IsEmpty()) { michael@0: ofn.lpstrInitialDir = aInitialDir.get(); michael@0: } michael@0: michael@0: AutoDestroyTmpWindow adtw((HWND) (mParentWidget.get() ? michael@0: mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); michael@0: michael@0: ofn.lpstrTitle = (LPCWSTR)mTitle.get(); michael@0: ofn.lpstrFilter = (LPCWSTR)filterBuffer.get(); michael@0: ofn.nFilterIndex = mSelectedType; michael@0: ofn.lpstrFile = fileBuffer; michael@0: ofn.nMaxFile = FILE_BUFFER_SIZE; michael@0: ofn.hwndOwner = adtw.get(); michael@0: ofn.lCustData = reinterpret_cast(this); michael@0: ofn.Flags = OFN_SHAREAWARE | OFN_LONGNAMES | OFN_OVERWRITEPROMPT | michael@0: OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_ENABLESIZING | michael@0: OFN_EXPLORER; michael@0: michael@0: // Windows Vista and up won't allow you to use the new looking dialogs with michael@0: // a hook procedure. The hook procedure fixes a problem on XP dialogs for michael@0: // file picker visibility. Vista and up automatically ensures the file michael@0: // picker is always visible. michael@0: if (!IsVistaOrLater()) { michael@0: ofn.lpfnHook = FilePickerHook; michael@0: ofn.Flags |= OFN_ENABLEHOOK; michael@0: } michael@0: michael@0: // Handle add to recent docs settings michael@0: if (IsPrivacyModeEnabled() || !mAddToRecentDocs) { michael@0: ofn.Flags |= OFN_DONTADDTORECENT; michael@0: } michael@0: michael@0: NS_NAMED_LITERAL_STRING(htmExt, "html"); michael@0: michael@0: if (!mDefaultExtension.IsEmpty()) { michael@0: ofn.lpstrDefExt = mDefaultExtension.get(); michael@0: } else if (IsDefaultPathHtml()) { michael@0: // Get file extension from suggested filename to detect if we are michael@0: // saving an html file. michael@0: // This is supposed to append ".htm" if user doesn't supply an michael@0: // extension but the behavior is sort of weird: michael@0: // - Often appends ".html" even if you have an extension michael@0: // - It obeys your extension if you put quotes around name michael@0: ofn.lpstrDefExt = htmExt.get(); michael@0: } michael@0: michael@0: // When possible, instead of using OFN_NOCHANGEDIR to ensure the current michael@0: // working directory will not change from this call, we will retrieve the michael@0: // current working directory before the call and restore it after the michael@0: // call. This flag causes problems on Windows XP for paths that are michael@0: // selected like C:test.txt where the user is currently at C:\somepath michael@0: // In which case expected result should be C:\somepath\test.txt michael@0: AutoRestoreWorkingPath restoreWorkingPath; michael@0: // If we can't get the current working directory, the best case is to michael@0: // use the OFN_NOCHANGEDIR flag michael@0: if (!restoreWorkingPath.HasWorkingPath()) { michael@0: ofn.Flags |= OFN_NOCHANGEDIR; michael@0: } michael@0: michael@0: bool result = false; michael@0: michael@0: switch(mMode) { michael@0: case modeOpen: michael@0: // FILE MUST EXIST! michael@0: ofn.Flags |= OFN_FILEMUSTEXIST; michael@0: result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); michael@0: break; michael@0: michael@0: case modeOpenMultiple: michael@0: ofn.Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT; michael@0: michael@0: // The hook set here ensures that the buffer returned will always be michael@0: // large enough to hold all selected files. The hook may modify the michael@0: // value of ofn.lpstrFile and deallocate the old buffer that it pointed michael@0: // to (fileBuffer). The hook assumes that the passed in value is heap michael@0: // allocated and that the returned value should be freed by the caller. michael@0: // If the hook changes the buffer, it will deallocate the old buffer. michael@0: // This fix would be nice to have in Vista and up, but it would force michael@0: // the file picker to use the old style dialogs because hooks are not michael@0: // allowed in the new file picker UI. We need to eventually move to michael@0: // the new Common File Dialogs for Vista and up. michael@0: if (!IsVistaOrLater()) { michael@0: ofn.lpfnHook = MultiFilePickerHook; michael@0: fileBuffer.forget(); michael@0: result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); michael@0: fileBuffer = ofn.lpstrFile; michael@0: } else { michael@0: result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); michael@0: } michael@0: break; michael@0: michael@0: case modeSave: michael@0: { michael@0: ofn.Flags |= OFN_NOREADONLYRETURN; michael@0: michael@0: // Don't follow shortcuts when saving a shortcut, this can be used michael@0: // to trick users (bug 271732) michael@0: if (IsDefaultPathLink()) michael@0: ofn.Flags |= OFN_NODEREFERENCELINKS; michael@0: michael@0: result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE); michael@0: if (!result) { michael@0: // Error, find out what kind. michael@0: if (GetLastError() == ERROR_INVALID_PARAMETER || michael@0: CommDlgExtendedError() == FNERR_INVALIDFILENAME) { michael@0: // Probably the default file name is too long or contains illegal michael@0: // characters. Try again, without a starting file name. michael@0: ofn.lpstrFile[0] = L'\0'; michael@0: result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE); michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: NS_NOTREACHED("unsupported file picker mode"); michael@0: return false; michael@0: } michael@0: michael@0: if (!result) michael@0: return false; michael@0: michael@0: // Remember what filter type the user selected michael@0: mSelectedType = (int16_t)ofn.nFilterIndex; michael@0: michael@0: // Single file selection, we're done michael@0: if (mMode != modeOpenMultiple) { michael@0: GetQualifiedPath(fileBuffer, mUnicodeFile); michael@0: return true; michael@0: } michael@0: michael@0: // Set user-selected location of file or directory. From msdn's "Open and michael@0: // Save As Dialog Boxes" section: michael@0: // If you specify OFN_EXPLORER, the directory and file name strings are '\0' michael@0: // separated, with an extra '\0' character after the last file name. This michael@0: // format enables the Explorer-style dialog boxes to return long file names michael@0: // that include spaces. michael@0: wchar_t *current = fileBuffer; michael@0: michael@0: nsAutoString dirName(current); michael@0: // Sometimes dirName contains a trailing slash and sometimes it doesn't: michael@0: if (current[dirName.Length() - 1] != '\\') michael@0: dirName.Append((char16_t)'\\'); michael@0: michael@0: while (current && *current && *(current + wcslen(current) + 1)) { michael@0: current = current + wcslen(current) + 1; michael@0: michael@0: nsCOMPtr file = do_CreateInstance("@mozilla.org/file/local;1"); michael@0: NS_ENSURE_TRUE(file, false); michael@0: michael@0: // Only prepend the directory if the path specified is a relative path michael@0: nsAutoString path; michael@0: if (PathIsRelativeW(current)) { michael@0: path = dirName + nsDependentString(current); michael@0: } else { michael@0: path = current; michael@0: } michael@0: michael@0: nsAutoString canonicalizedPath; michael@0: GetQualifiedPath(path.get(), canonicalizedPath); michael@0: if (NS_FAILED(file->InitWithPath(canonicalizedPath)) || michael@0: !mFiles.AppendObject(file)) michael@0: return false; michael@0: } michael@0: michael@0: // Handle the case where the user selected just one file. From msdn: If you michael@0: // specify OFN_ALLOWMULTISELECT and the user selects only one file the michael@0: // lpstrFile string does not have a separator between the path and file name. michael@0: if (current && *current && (current == fileBuffer)) { michael@0: nsCOMPtr file = do_CreateInstance("@mozilla.org/file/local;1"); michael@0: NS_ENSURE_TRUE(file, false); michael@0: michael@0: nsAutoString canonicalizedPath; michael@0: GetQualifiedPath(current, canonicalizedPath); michael@0: if (NS_FAILED(file->InitWithPath(canonicalizedPath)) || michael@0: !mFiles.AppendObject(file)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Show a file picker post Windows XP michael@0: * michael@0: * @param aInitialDir The initial directory, the last used directory will be michael@0: * used if left blank. michael@0: * @param aWasInitError Out parameter will hold true if there was an error michael@0: * before the file picker is shown. michael@0: * @return true if a file was selected successfully. michael@0: */ michael@0: bool michael@0: nsFilePicker::ShowFilePicker(const nsString& aInitialDir, bool &aWasInitError) michael@0: { michael@0: nsRefPtr dialog; michael@0: if (mMode != modeSave) { michael@0: if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC, michael@0: IID_IFileOpenDialog, michael@0: getter_AddRefs(dialog)))) { michael@0: aWasInitError = true; michael@0: return false; michael@0: } michael@0: } else { michael@0: if (FAILED(CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC, michael@0: IID_IFileSaveDialog, michael@0: getter_AddRefs(dialog)))) { michael@0: aWasInitError = true; michael@0: return false; michael@0: } michael@0: } michael@0: aWasInitError = false; michael@0: michael@0: // hook up event callbacks michael@0: dialog->Advise(this, &mFDECookie); michael@0: michael@0: // options michael@0: michael@0: FILEOPENDIALOGOPTIONS fos = 0; michael@0: fos |= FOS_SHAREAWARE | FOS_OVERWRITEPROMPT | michael@0: FOS_FORCEFILESYSTEM; michael@0: michael@0: // Handle add to recent docs settings michael@0: if (IsPrivacyModeEnabled() || !mAddToRecentDocs) { michael@0: fos |= FOS_DONTADDTORECENT; michael@0: } michael@0: michael@0: // Msdn claims FOS_NOCHANGEDIR is not needed. We'll add this michael@0: // just in case. michael@0: AutoRestoreWorkingPath arw; michael@0: michael@0: // mode specific michael@0: switch(mMode) { michael@0: case modeOpen: michael@0: fos |= FOS_FILEMUSTEXIST; michael@0: break; michael@0: michael@0: case modeOpenMultiple: michael@0: fos |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT; michael@0: break; michael@0: michael@0: case modeSave: michael@0: fos |= FOS_NOREADONLYRETURN; michael@0: // Don't follow shortcuts when saving a shortcut, this can be used michael@0: // to trick users (bug 271732) michael@0: if (IsDefaultPathLink()) michael@0: fos |= FOS_NODEREFERENCELINKS; michael@0: break; michael@0: } michael@0: michael@0: dialog->SetOptions(fos); michael@0: michael@0: // initial strings michael@0: michael@0: // title michael@0: dialog->SetTitle(mTitle.get()); michael@0: michael@0: // default filename michael@0: if (!mDefaultFilename.IsEmpty()) { michael@0: dialog->SetFileName(mDefaultFilename.get()); michael@0: } michael@0: michael@0: NS_NAMED_LITERAL_STRING(htmExt, "html"); michael@0: michael@0: // default extension to append to new files michael@0: if (!mDefaultExtension.IsEmpty()) { michael@0: dialog->SetDefaultExtension(mDefaultExtension.get()); michael@0: } else if (IsDefaultPathHtml()) { michael@0: dialog->SetDefaultExtension(htmExt.get()); michael@0: } michael@0: michael@0: // initial location michael@0: if (!aInitialDir.IsEmpty()) { michael@0: nsRefPtr folder; michael@0: if (SUCCEEDED( michael@0: WinUtils::SHCreateItemFromParsingName(aInitialDir.get(), nullptr, michael@0: IID_IShellItem, michael@0: getter_AddRefs(folder)))) { michael@0: dialog->SetFolder(folder); michael@0: } michael@0: } michael@0: michael@0: // filter types and the default index michael@0: if (!mComFilterList.IsEmpty()) { michael@0: dialog->SetFileTypes(mComFilterList.Length(), mComFilterList.get()); michael@0: dialog->SetFileTypeIndex(mSelectedType); michael@0: } michael@0: michael@0: // display michael@0: michael@0: { michael@0: AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? michael@0: mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); michael@0: AutoTimerCallbackCancel atcc(this, PickerCallbackTimerFunc); michael@0: AutoWidgetPickerState awps(mParentWidget); michael@0: michael@0: if (FAILED(dialog->Show(adtw.get()))) { michael@0: dialog->Unadvise(mFDECookie); michael@0: return false; michael@0: } michael@0: dialog->Unadvise(mFDECookie); michael@0: } michael@0: michael@0: // results michael@0: michael@0: // Remember what filter type the user selected michael@0: UINT filterIdxResult; michael@0: if (SUCCEEDED(dialog->GetFileTypeIndex(&filterIdxResult))) { michael@0: mSelectedType = (int16_t)filterIdxResult; michael@0: } michael@0: michael@0: // single selection michael@0: if (mMode != modeOpenMultiple) { michael@0: nsRefPtr item; michael@0: if (FAILED(dialog->GetResult(getter_AddRefs(item))) || !item) michael@0: return false; michael@0: return WinUtils::GetShellItemPath(item, mUnicodeFile); michael@0: } michael@0: michael@0: // multiple selection michael@0: nsRefPtr openDlg; michael@0: dialog->QueryInterface(IID_IFileOpenDialog, getter_AddRefs(openDlg)); michael@0: if (!openDlg) { michael@0: // should not happen michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr items; michael@0: if (FAILED(openDlg->GetResults(getter_AddRefs(items))) || !items) { michael@0: return false; michael@0: } michael@0: michael@0: DWORD count = 0; michael@0: items->GetCount(&count); michael@0: for (unsigned int idx = 0; idx < count; idx++) { michael@0: nsRefPtr item; michael@0: nsAutoString str; michael@0: if (SUCCEEDED(items->GetItemAt(idx, getter_AddRefs(item)))) { michael@0: if (!WinUtils::GetShellItemPath(item, str)) michael@0: continue; michael@0: nsCOMPtr file = do_CreateInstance("@mozilla.org/file/local;1"); michael@0: if (file && NS_SUCCEEDED(file->InitWithPath(str))) michael@0: mFiles.AppendObject(file); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // nsIFilePicker impl. michael@0: michael@0: NS_IMETHODIMP michael@0: nsFilePicker::ShowW(int16_t *aReturnVal) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aReturnVal); michael@0: michael@0: *aReturnVal = returnCancel; michael@0: michael@0: AutoSuppressEvents supress(mParentWidget); michael@0: michael@0: nsAutoString initialDir; michael@0: if (mDisplayDirectory) michael@0: mDisplayDirectory->GetPath(initialDir); michael@0: michael@0: // If no display directory, re-use the last one. michael@0: if(initialDir.IsEmpty()) { michael@0: // Allocate copy of last used dir. michael@0: initialDir = mLastUsedUnicodeDirectory; michael@0: } michael@0: michael@0: // Clear previous file selections michael@0: mUnicodeFile.Truncate(); michael@0: mFiles.Clear(); michael@0: michael@0: // Launch the XP file/folder picker on XP and as a fallback on Vista+. michael@0: // The CoCreateInstance call to CLSID_FileOpenDialog fails with "(0x80040111) michael@0: // ClassFactory cannot supply requested class" when the checkbox for michael@0: // Disable Visual Themes is on in the compatability tab within the shortcut michael@0: // properties. michael@0: bool result = false, wasInitError = true; michael@0: if (mMode == modeGetFolder) { michael@0: if (IsVistaOrLater()) michael@0: result = ShowFolderPicker(initialDir, wasInitError); michael@0: if (!result && wasInitError) michael@0: result = ShowXPFolderPicker(initialDir); michael@0: } else { michael@0: if (IsVistaOrLater()) michael@0: result = ShowFilePicker(initialDir, wasInitError); michael@0: if (!result && wasInitError) michael@0: result = ShowXPFilePicker(initialDir); michael@0: } michael@0: michael@0: // exit, and return returnCancel in aReturnVal michael@0: if (!result) michael@0: return NS_OK; michael@0: michael@0: RememberLastUsedDirectory(); michael@0: michael@0: int16_t retValue = returnOK; michael@0: if (mMode == modeSave) { michael@0: // Windows does not return resultReplace, we must check if file michael@0: // already exists. michael@0: nsCOMPtr file(do_CreateInstance("@mozilla.org/file/local;1")); michael@0: bool flag = false; michael@0: if (file && NS_SUCCEEDED(file->InitWithPath(mUnicodeFile)) && michael@0: NS_SUCCEEDED(file->Exists(&flag)) && flag) { michael@0: retValue = returnReplace; michael@0: } michael@0: } michael@0: michael@0: *aReturnVal = retValue; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFilePicker::Show(int16_t *aReturnVal) michael@0: { michael@0: return ShowW(aReturnVal); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFilePicker::GetFile(nsIFile **aFile) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aFile); michael@0: *aFile = nullptr; michael@0: michael@0: if (mUnicodeFile.IsEmpty()) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr file(do_CreateInstance("@mozilla.org/file/local;1")); michael@0: michael@0: NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); michael@0: michael@0: file->InitWithPath(mUnicodeFile); michael@0: michael@0: NS_ADDREF(*aFile = file); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFilePicker::GetFileURL(nsIURI **aFileURL) michael@0: { michael@0: *aFileURL = nullptr; michael@0: nsCOMPtr file; michael@0: nsresult rv = GetFile(getter_AddRefs(file)); michael@0: if (!file) michael@0: return rv; michael@0: michael@0: return NS_NewFileURI(aFileURL, file); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aFiles); michael@0: return NS_NewArrayEnumerator(aFiles, mFiles); michael@0: } michael@0: michael@0: // Get the file + path michael@0: NS_IMETHODIMP michael@0: nsBaseWinFilePicker::SetDefaultString(const nsAString& aString) michael@0: { michael@0: mDefaultFilePath = aString; michael@0: michael@0: // First, make sure the file name is not too long. michael@0: int32_t nameLength; michael@0: int32_t nameIndex = mDefaultFilePath.RFind("\\"); michael@0: if (nameIndex == kNotFound) michael@0: nameIndex = 0; michael@0: else michael@0: nameIndex ++; michael@0: nameLength = mDefaultFilePath.Length() - nameIndex; michael@0: mDefaultFilename.Assign(Substring(mDefaultFilePath, nameIndex)); michael@0: michael@0: if (nameLength > MAX_PATH) { michael@0: int32_t extIndex = mDefaultFilePath.RFind("."); michael@0: if (extIndex == kNotFound) michael@0: extIndex = mDefaultFilePath.Length(); michael@0: michael@0: // Let's try to shave the needed characters from the name part. michael@0: int32_t charsToRemove = nameLength - MAX_PATH; michael@0: if (extIndex - nameIndex >= charsToRemove) { michael@0: mDefaultFilePath.Cut(extIndex - charsToRemove, charsToRemove); michael@0: } michael@0: } michael@0: michael@0: // Then, we need to replace illegal characters. At this stage, we cannot michael@0: // replace the backslash as the string might represent a file path. michael@0: mDefaultFilePath.ReplaceChar(FILE_ILLEGAL_CHARACTERS, '-'); michael@0: mDefaultFilename.ReplaceChar(FILE_ILLEGAL_CHARACTERS, '-'); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsBaseWinFilePicker::GetDefaultString(nsAString& aString) michael@0: { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // The default extension to use for files michael@0: NS_IMETHODIMP michael@0: nsBaseWinFilePicker::GetDefaultExtension(nsAString& aExtension) michael@0: { michael@0: aExtension = mDefaultExtension; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsBaseWinFilePicker::SetDefaultExtension(const nsAString& aExtension) michael@0: { michael@0: mDefaultExtension = aExtension; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Set the filter index michael@0: NS_IMETHODIMP michael@0: nsFilePicker::GetFilterIndex(int32_t *aFilterIndex) michael@0: { michael@0: // Windows' filter index is 1-based, we use a 0-based system. michael@0: *aFilterIndex = mSelectedType - 1; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFilePicker::SetFilterIndex(int32_t aFilterIndex) michael@0: { michael@0: // Windows' filter index is 1-based, we use a 0-based system. michael@0: mSelectedType = aFilterIndex + 1; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFilePicker::InitNative(nsIWidget *aParent, michael@0: const nsAString& aTitle) michael@0: { michael@0: mParentWidget = aParent; michael@0: mTitle.Assign(aTitle); michael@0: } michael@0: michael@0: void michael@0: nsFilePicker::GetQualifiedPath(const wchar_t *aInPath, nsString &aOutPath) michael@0: { michael@0: // Prefer a qualified path over a non qualified path. michael@0: // Things like c:file.txt would be accepted in Win XP but would later michael@0: // fail to open from the download manager. michael@0: wchar_t qualifiedFileBuffer[MAX_PATH]; michael@0: if (PathSearchAndQualifyW(aInPath, qualifiedFileBuffer, MAX_PATH)) { michael@0: aOutPath.Assign(qualifiedFileBuffer); michael@0: } else { michael@0: aOutPath.Assign(aInPath); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsFilePicker::AppendXPFilter(const nsAString& aTitle, const nsAString& aFilter) michael@0: { michael@0: mFilterList.Append(aTitle); michael@0: mFilterList.Append(char16_t('\0')); michael@0: michael@0: if (aFilter.EqualsLiteral("..apps")) michael@0: mFilterList.AppendLiteral("*.exe;*.com"); michael@0: else michael@0: { michael@0: nsAutoString filter(aFilter); michael@0: filter.StripWhitespace(); michael@0: if (filter.EqualsLiteral("*")) michael@0: filter.AppendLiteral(".*"); michael@0: mFilterList.Append(filter); michael@0: } michael@0: michael@0: mFilterList.Append(char16_t('\0')); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) michael@0: { michael@0: if (IsVistaOrLater()) { michael@0: mComFilterList.Append(aTitle, aFilter); michael@0: } else { michael@0: AppendXPFilter(aTitle, aFilter); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFilePicker::RememberLastUsedDirectory() michael@0: { michael@0: nsCOMPtr file(do_CreateInstance("@mozilla.org/file/local;1")); michael@0: if (!file || NS_FAILED(file->InitWithPath(mUnicodeFile))) { michael@0: NS_WARNING("RememberLastUsedDirectory failed to init file path."); michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr dir; michael@0: nsAutoString newDir; michael@0: if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) || michael@0: !(mDisplayDirectory = do_QueryInterface(dir)) || michael@0: NS_FAILED(mDisplayDirectory->GetPath(newDir)) || michael@0: newDir.IsEmpty()) { michael@0: NS_WARNING("RememberLastUsedDirectory failed to get parent directory."); michael@0: return; michael@0: } michael@0: michael@0: if (mLastUsedUnicodeDirectory) { michael@0: NS_Free(mLastUsedUnicodeDirectory); michael@0: mLastUsedUnicodeDirectory = nullptr; michael@0: } michael@0: mLastUsedUnicodeDirectory = ToNewUnicode(newDir); michael@0: } michael@0: michael@0: bool michael@0: nsFilePicker::IsPrivacyModeEnabled() michael@0: { michael@0: return mLoadContext && mLoadContext->UsePrivateBrowsing(); michael@0: } michael@0: michael@0: bool michael@0: nsFilePicker::IsDefaultPathLink() michael@0: { michael@0: NS_ConvertUTF16toUTF8 ext(mDefaultFilePath); michael@0: ext.Trim(" .", false, true); // watch out for trailing space and dots michael@0: ToLowerCase(ext); michael@0: if (StringEndsWith(ext, NS_LITERAL_CSTRING(".lnk")) || michael@0: StringEndsWith(ext, NS_LITERAL_CSTRING(".pif")) || michael@0: StringEndsWith(ext, NS_LITERAL_CSTRING(".url"))) michael@0: return true; michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsFilePicker::IsDefaultPathHtml() michael@0: { michael@0: int32_t extIndex = mDefaultFilePath.RFind("."); michael@0: if (extIndex >= 0) { michael@0: nsAutoString ext; michael@0: mDefaultFilePath.Right(ext, mDefaultFilePath.Length() - extIndex); michael@0: if (ext.LowerCaseEqualsLiteral(".htm") || michael@0: ext.LowerCaseEqualsLiteral(".html") || michael@0: ext.LowerCaseEqualsLiteral(".shtml")) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsFilePicker::ComDlgFilterSpec::Append(const nsAString& aTitle, const nsAString& aFilter) michael@0: { michael@0: COMDLG_FILTERSPEC* pSpecForward = mSpecList.AppendElement(); michael@0: if (!pSpecForward) { michael@0: NS_WARNING("mSpecList realloc failed."); michael@0: return; michael@0: } michael@0: memset(pSpecForward, 0, sizeof(*pSpecForward)); michael@0: nsString* pStr = mStrings.AppendElement(aTitle); michael@0: if (!pStr) { michael@0: NS_WARNING("mStrings.AppendElement failed."); michael@0: return; michael@0: } michael@0: pSpecForward->pszName = pStr->get(); michael@0: pStr = mStrings.AppendElement(aFilter); michael@0: if (!pStr) { michael@0: NS_WARNING("mStrings.AppendElement failed."); michael@0: return; michael@0: } michael@0: if (aFilter.EqualsLiteral("..apps")) michael@0: pStr->AssignLiteral("*.exe;*.com"); michael@0: else { michael@0: pStr->StripWhitespace(); michael@0: if (pStr->EqualsLiteral("*")) michael@0: pStr->AppendLiteral(".*"); michael@0: } michael@0: pSpecForward->pszSpec = pStr->get(); michael@0: }