widget/windows/nsFilePicker.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2 *
michael@0 3 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "nsFilePicker.h"
michael@0 8
michael@0 9 #include <shlobj.h>
michael@0 10 #include <shlwapi.h>
michael@0 11 #include <cderr.h>
michael@0 12
michael@0 13 #include "mozilla/WindowsVersion.h"
michael@0 14 #include "nsReadableUtils.h"
michael@0 15 #include "nsNetUtil.h"
michael@0 16 #include "nsWindow.h"
michael@0 17 #include "nsILoadContext.h"
michael@0 18 #include "nsIServiceManager.h"
michael@0 19 #include "nsIURL.h"
michael@0 20 #include "nsIStringBundle.h"
michael@0 21 #include "nsEnumeratorUtils.h"
michael@0 22 #include "nsCRT.h"
michael@0 23 #include "nsString.h"
michael@0 24 #include "nsToolkit.h"
michael@0 25 #include "WinUtils.h"
michael@0 26 #include "nsPIDOMWindow.h"
michael@0 27
michael@0 28 using mozilla::IsVistaOrLater;
michael@0 29 using namespace mozilla::widget;
michael@0 30
michael@0 31 char16_t *nsFilePicker::mLastUsedUnicodeDirectory;
michael@0 32 char nsFilePicker::mLastUsedDirectory[MAX_PATH+1] = { 0 };
michael@0 33
michael@0 34 static const wchar_t kDialogPtrProp[] = L"DialogPtrProperty";
michael@0 35 static const DWORD kDialogTimerID = 9999;
michael@0 36 static const unsigned long kDialogTimerTimeout = 300;
michael@0 37
michael@0 38 #define MAX_EXTENSION_LENGTH 10
michael@0 39 #define FILE_BUFFER_SIZE 4096
michael@0 40
michael@0 41 typedef DWORD FILEOPENDIALOGOPTIONS;
michael@0 42
michael@0 43 ///////////////////////////////////////////////////////////////////////////////
michael@0 44 // Helper classes
michael@0 45
michael@0 46 // Manages matching SuppressBlurEvents calls on the parent widget.
michael@0 47 class AutoSuppressEvents
michael@0 48 {
michael@0 49 public:
michael@0 50 explicit AutoSuppressEvents(nsIWidget* aWidget) :
michael@0 51 mWindow(static_cast<nsWindow *>(aWidget)) {
michael@0 52 SuppressWidgetEvents(true);
michael@0 53 }
michael@0 54
michael@0 55 ~AutoSuppressEvents() {
michael@0 56 SuppressWidgetEvents(false);
michael@0 57 }
michael@0 58 private:
michael@0 59 void SuppressWidgetEvents(bool aFlag) {
michael@0 60 if (mWindow) {
michael@0 61 mWindow->SuppressBlurEvents(aFlag);
michael@0 62 }
michael@0 63 }
michael@0 64 nsRefPtr<nsWindow> mWindow;
michael@0 65 };
michael@0 66
michael@0 67 // Manages the current working path.
michael@0 68 class AutoRestoreWorkingPath
michael@0 69 {
michael@0 70 public:
michael@0 71 AutoRestoreWorkingPath() {
michael@0 72 DWORD bufferLength = GetCurrentDirectoryW(0, nullptr);
michael@0 73 mWorkingPath = new wchar_t[bufferLength];
michael@0 74 if (GetCurrentDirectoryW(bufferLength, mWorkingPath) == 0) {
michael@0 75 mWorkingPath = nullptr;
michael@0 76 }
michael@0 77 }
michael@0 78
michael@0 79 ~AutoRestoreWorkingPath() {
michael@0 80 if (HasWorkingPath()) {
michael@0 81 ::SetCurrentDirectoryW(mWorkingPath);
michael@0 82 }
michael@0 83 }
michael@0 84
michael@0 85 inline bool HasWorkingPath() const {
michael@0 86 return mWorkingPath != nullptr;
michael@0 87 }
michael@0 88 private:
michael@0 89 nsAutoArrayPtr<wchar_t> mWorkingPath;
michael@0 90 };
michael@0 91
michael@0 92 // Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are
michael@0 93 // temporary child windows of mParentWidget created to address RTL issues
michael@0 94 // in picker dialogs. We are responsible for destroying these.
michael@0 95 class AutoDestroyTmpWindow
michael@0 96 {
michael@0 97 public:
michael@0 98 explicit AutoDestroyTmpWindow(HWND aTmpWnd) :
michael@0 99 mWnd(aTmpWnd) {
michael@0 100 }
michael@0 101
michael@0 102 ~AutoDestroyTmpWindow() {
michael@0 103 if (mWnd)
michael@0 104 DestroyWindow(mWnd);
michael@0 105 }
michael@0 106
michael@0 107 inline HWND get() const { return mWnd; }
michael@0 108 private:
michael@0 109 HWND mWnd;
michael@0 110 };
michael@0 111
michael@0 112 // Manages matching PickerOpen/PickerClosed calls on the parent widget.
michael@0 113 class AutoWidgetPickerState
michael@0 114 {
michael@0 115 public:
michael@0 116 explicit AutoWidgetPickerState(nsIWidget* aWidget) :
michael@0 117 mWindow(static_cast<nsWindow *>(aWidget)) {
michael@0 118 PickerState(true);
michael@0 119 }
michael@0 120
michael@0 121 ~AutoWidgetPickerState() {
michael@0 122 PickerState(false);
michael@0 123 }
michael@0 124 private:
michael@0 125 void PickerState(bool aFlag) {
michael@0 126 if (mWindow) {
michael@0 127 if (aFlag)
michael@0 128 mWindow->PickerOpen();
michael@0 129 else
michael@0 130 mWindow->PickerClosed();
michael@0 131 }
michael@0 132 }
michael@0 133 nsRefPtr<nsWindow> mWindow;
michael@0 134 };
michael@0 135
michael@0 136 // Manages a simple callback timer
michael@0 137 class AutoTimerCallbackCancel
michael@0 138 {
michael@0 139 public:
michael@0 140 AutoTimerCallbackCancel(nsFilePicker* aTarget,
michael@0 141 nsTimerCallbackFunc aCallbackFunc) {
michael@0 142 Init(aTarget, aCallbackFunc);
michael@0 143 }
michael@0 144
michael@0 145 ~AutoTimerCallbackCancel() {
michael@0 146 if (mPickerCallbackTimer) {
michael@0 147 mPickerCallbackTimer->Cancel();
michael@0 148 }
michael@0 149 }
michael@0 150
michael@0 151 private:
michael@0 152 void Init(nsFilePicker* aTarget,
michael@0 153 nsTimerCallbackFunc aCallbackFunc) {
michael@0 154 mPickerCallbackTimer = do_CreateInstance("@mozilla.org/timer;1");
michael@0 155 if (!mPickerCallbackTimer) {
michael@0 156 NS_WARNING("do_CreateInstance for timer failed??");
michael@0 157 return;
michael@0 158 }
michael@0 159 mPickerCallbackTimer->InitWithFuncCallback(aCallbackFunc,
michael@0 160 aTarget,
michael@0 161 kDialogTimerTimeout,
michael@0 162 nsITimer::TYPE_REPEATING_SLACK);
michael@0 163 }
michael@0 164 nsCOMPtr<nsITimer> mPickerCallbackTimer;
michael@0 165
michael@0 166 };
michael@0 167
michael@0 168 ///////////////////////////////////////////////////////////////////////////////
michael@0 169 // nsIFilePicker
michael@0 170
michael@0 171 nsFilePicker::nsFilePicker() :
michael@0 172 mSelectedType(1)
michael@0 173 , mDlgWnd(nullptr)
michael@0 174 , mFDECookie(0)
michael@0 175 {
michael@0 176 CoInitialize(nullptr);
michael@0 177 }
michael@0 178
michael@0 179 nsFilePicker::~nsFilePicker()
michael@0 180 {
michael@0 181 if (mLastUsedUnicodeDirectory) {
michael@0 182 NS_Free(mLastUsedUnicodeDirectory);
michael@0 183 mLastUsedUnicodeDirectory = nullptr;
michael@0 184 }
michael@0 185 CoUninitialize();
michael@0 186 }
michael@0 187
michael@0 188 NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
michael@0 189
michael@0 190 NS_IMETHODIMP nsFilePicker::Init(nsIDOMWindow *aParent, const nsAString& aTitle, int16_t aMode)
michael@0 191 {
michael@0 192 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aParent);
michael@0 193 nsIDocShell* docShell = window ? window->GetDocShell() : nullptr;
michael@0 194 mLoadContext = do_QueryInterface(docShell);
michael@0 195
michael@0 196 return nsBaseFilePicker::Init(aParent, aTitle, aMode);
michael@0 197 }
michael@0 198
michael@0 199 STDMETHODIMP nsFilePicker::QueryInterface(REFIID refiid, void** ppvResult)
michael@0 200 {
michael@0 201 *ppvResult = nullptr;
michael@0 202 if (IID_IUnknown == refiid ||
michael@0 203 refiid == IID_IFileDialogEvents) {
michael@0 204 *ppvResult = this;
michael@0 205 }
michael@0 206
michael@0 207 if (nullptr != *ppvResult) {
michael@0 208 ((LPUNKNOWN)*ppvResult)->AddRef();
michael@0 209 return S_OK;
michael@0 210 }
michael@0 211
michael@0 212 return E_NOINTERFACE;
michael@0 213 }
michael@0 214
michael@0 215 /*
michael@0 216 * XP picker callbacks
michael@0 217 */
michael@0 218
michael@0 219 // Show - Display the file dialog
michael@0 220 int CALLBACK
michael@0 221 BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
michael@0 222 {
michael@0 223 if (uMsg == BFFM_INITIALIZED)
michael@0 224 {
michael@0 225 char16_t * filePath = (char16_t *) lpData;
michael@0 226 if (filePath)
michael@0 227 ::SendMessageW(hwnd, BFFM_SETSELECTIONW,
michael@0 228 TRUE /* true because lpData is a path string */,
michael@0 229 lpData);
michael@0 230 }
michael@0 231 return 0;
michael@0 232 }
michael@0 233
michael@0 234 static void
michael@0 235 EnsureWindowVisible(HWND hwnd)
michael@0 236 {
michael@0 237 // Obtain the monitor which has the largest area of intersection
michael@0 238 // with the window, or nullptr if there is no intersection.
michael@0 239 HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
michael@0 240 if (!monitor) {
michael@0 241 // The window is not visible, we should reposition it to the same place as its parent
michael@0 242 HWND parentHwnd = GetParent(hwnd);
michael@0 243 RECT parentRect;
michael@0 244 GetWindowRect(parentHwnd, &parentRect);
michael@0 245 SetWindowPos(hwnd, nullptr, parentRect.left, parentRect.top, 0, 0,
michael@0 246 SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
michael@0 247 }
michael@0 248 }
michael@0 249
michael@0 250 // Callback hook which will ensure that the window is visible. Currently
michael@0 251 // only in use on os <= XP.
michael@0 252 UINT_PTR CALLBACK
michael@0 253 nsFilePicker::FilePickerHook(HWND hwnd,
michael@0 254 UINT msg,
michael@0 255 WPARAM wParam,
michael@0 256 LPARAM lParam)
michael@0 257 {
michael@0 258 switch(msg) {
michael@0 259 case WM_NOTIFY:
michael@0 260 {
michael@0 261 LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam;
michael@0 262 if (!lpofn || !lpofn->lpOFN) {
michael@0 263 return 0;
michael@0 264 }
michael@0 265
michael@0 266 if (CDN_INITDONE == lpofn->hdr.code) {
michael@0 267 // The Window will be automatically moved to the last position after
michael@0 268 // CDN_INITDONE. We post a message to ensure the window will be visible
michael@0 269 // so it will be done after the automatic last position window move.
michael@0 270 PostMessage(hwnd, MOZ_WM_ENSUREVISIBLE, 0, 0);
michael@0 271 }
michael@0 272 }
michael@0 273 break;
michael@0 274 case MOZ_WM_ENSUREVISIBLE:
michael@0 275 EnsureWindowVisible(GetParent(hwnd));
michael@0 276 break;
michael@0 277 case WM_INITDIALOG:
michael@0 278 {
michael@0 279 OPENFILENAMEW* pofn = reinterpret_cast<OPENFILENAMEW*>(lParam);
michael@0 280 SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData);
michael@0 281 nsFilePicker* picker = reinterpret_cast<nsFilePicker*>(pofn->lCustData);
michael@0 282 if (picker) {
michael@0 283 picker->SetDialogHandle(hwnd);
michael@0 284 SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr);
michael@0 285 }
michael@0 286 }
michael@0 287 break;
michael@0 288 case WM_TIMER:
michael@0 289 {
michael@0 290 // Check to see if our parent has been torn down, if so, we close too.
michael@0 291 if (wParam == kDialogTimerID) {
michael@0 292 nsFilePicker* picker =
michael@0 293 reinterpret_cast<nsFilePicker*>(GetProp(hwnd, kDialogPtrProp));
michael@0 294 if (picker && picker->ClosePickerIfNeeded(true)) {
michael@0 295 KillTimer(hwnd, kDialogTimerID);
michael@0 296 }
michael@0 297 }
michael@0 298 }
michael@0 299 break;
michael@0 300 }
michael@0 301 return 0;
michael@0 302 }
michael@0 303
michael@0 304
michael@0 305 // Callback hook which will dynamically allocate a buffer large enough
michael@0 306 // for the file picker dialog. Currently only in use on os <= XP.
michael@0 307 UINT_PTR CALLBACK
michael@0 308 nsFilePicker::MultiFilePickerHook(HWND hwnd,
michael@0 309 UINT msg,
michael@0 310 WPARAM wParam,
michael@0 311 LPARAM lParam)
michael@0 312 {
michael@0 313 switch (msg) {
michael@0 314 case WM_INITDIALOG:
michael@0 315 {
michael@0 316 // Finds the child drop down of a File Picker dialog and sets the
michael@0 317 // maximum amount of text it can hold when typed in manually.
michael@0 318 // A wParam of 0 mean 0x7FFFFFFE characters.
michael@0 319 HWND comboBox = FindWindowEx(GetParent(hwnd), nullptr,
michael@0 320 L"ComboBoxEx32", nullptr );
michael@0 321 if(comboBox)
michael@0 322 SendMessage(comboBox, CB_LIMITTEXT, 0, 0);
michael@0 323 // Store our nsFilePicker ptr for future use
michael@0 324 OPENFILENAMEW* pofn = reinterpret_cast<OPENFILENAMEW*>(lParam);
michael@0 325 SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData);
michael@0 326 nsFilePicker* picker =
michael@0 327 reinterpret_cast<nsFilePicker*>(pofn->lCustData);
michael@0 328 if (picker) {
michael@0 329 picker->SetDialogHandle(hwnd);
michael@0 330 SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr);
michael@0 331 }
michael@0 332 }
michael@0 333 break;
michael@0 334 case WM_NOTIFY:
michael@0 335 {
michael@0 336 LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam;
michael@0 337 if (!lpofn || !lpofn->lpOFN) {
michael@0 338 return 0;
michael@0 339 }
michael@0 340 // CDN_SELCHANGE is sent when the selection in the list box of the file
michael@0 341 // selection dialog changes
michael@0 342 if (lpofn->hdr.code == CDN_SELCHANGE) {
michael@0 343 HWND parentHWND = GetParent(hwnd);
michael@0 344
michael@0 345 // Get the required size for the selected files buffer
michael@0 346 UINT newBufLength = 0;
michael@0 347 int requiredBufLength = CommDlg_OpenSave_GetSpecW(parentHWND,
michael@0 348 nullptr, 0);
michael@0 349 if(requiredBufLength >= 0)
michael@0 350 newBufLength += requiredBufLength;
michael@0 351 else
michael@0 352 newBufLength += MAX_PATH;
michael@0 353
michael@0 354 // If the user selects multiple files, the buffer contains the
michael@0 355 // current directory followed by the file names of the selected
michael@0 356 // files. So make room for the directory path. If the user
michael@0 357 // selects a single file, it is no harm to add extra space.
michael@0 358 requiredBufLength = CommDlg_OpenSave_GetFolderPathW(parentHWND,
michael@0 359 nullptr, 0);
michael@0 360 if(requiredBufLength >= 0)
michael@0 361 newBufLength += requiredBufLength;
michael@0 362 else
michael@0 363 newBufLength += MAX_PATH;
michael@0 364
michael@0 365 // Check if lpstrFile and nMaxFile are large enough
michael@0 366 if (newBufLength > lpofn->lpOFN->nMaxFile) {
michael@0 367 if (lpofn->lpOFN->lpstrFile)
michael@0 368 delete[] lpofn->lpOFN->lpstrFile;
michael@0 369
michael@0 370 // We allocate FILE_BUFFER_SIZE more bytes than is needed so that
michael@0 371 // if the user selects a file and holds down shift and down to
michael@0 372 // select additional items, we will not continuously reallocate
michael@0 373 newBufLength += FILE_BUFFER_SIZE;
michael@0 374
michael@0 375 wchar_t* filesBuffer = new wchar_t[newBufLength];
michael@0 376 ZeroMemory(filesBuffer, newBufLength * sizeof(wchar_t));
michael@0 377
michael@0 378 lpofn->lpOFN->lpstrFile = filesBuffer;
michael@0 379 lpofn->lpOFN->nMaxFile = newBufLength;
michael@0 380 }
michael@0 381 }
michael@0 382 }
michael@0 383 break;
michael@0 384 case WM_TIMER:
michael@0 385 {
michael@0 386 // Check to see if our parent has been torn down, if so, we close too.
michael@0 387 if (wParam == kDialogTimerID) {
michael@0 388 nsFilePicker* picker =
michael@0 389 reinterpret_cast<nsFilePicker*>(GetProp(hwnd, kDialogPtrProp));
michael@0 390 if (picker && picker->ClosePickerIfNeeded(true)) {
michael@0 391 KillTimer(hwnd, kDialogTimerID);
michael@0 392 }
michael@0 393 }
michael@0 394 }
michael@0 395 break;
michael@0 396 }
michael@0 397
michael@0 398 return FilePickerHook(hwnd, msg, wParam, lParam);
michael@0 399 }
michael@0 400
michael@0 401 /*
michael@0 402 * Vista+ callbacks
michael@0 403 */
michael@0 404
michael@0 405 HRESULT
michael@0 406 nsFilePicker::OnFileOk(IFileDialog *pfd)
michael@0 407 {
michael@0 408 return S_OK;
michael@0 409 }
michael@0 410
michael@0 411 HRESULT
michael@0 412 nsFilePicker::OnFolderChanging(IFileDialog *pfd,
michael@0 413 IShellItem *psiFolder)
michael@0 414 {
michael@0 415 return S_OK;
michael@0 416 }
michael@0 417
michael@0 418 HRESULT
michael@0 419 nsFilePicker::OnFolderChange(IFileDialog *pfd)
michael@0 420 {
michael@0 421 return S_OK;
michael@0 422 }
michael@0 423
michael@0 424 HRESULT
michael@0 425 nsFilePicker::OnSelectionChange(IFileDialog *pfd)
michael@0 426 {
michael@0 427 return S_OK;
michael@0 428 }
michael@0 429
michael@0 430 HRESULT
michael@0 431 nsFilePicker::OnShareViolation(IFileDialog *pfd,
michael@0 432 IShellItem *psi,
michael@0 433 FDE_SHAREVIOLATION_RESPONSE *pResponse)
michael@0 434 {
michael@0 435 return S_OK;
michael@0 436 }
michael@0 437
michael@0 438 HRESULT
michael@0 439 nsFilePicker::OnTypeChange(IFileDialog *pfd)
michael@0 440 {
michael@0 441 // Failures here result in errors due to security concerns.
michael@0 442 nsRefPtr<IOleWindow> win;
michael@0 443 pfd->QueryInterface(IID_IOleWindow, getter_AddRefs(win));
michael@0 444 if (!win) {
michael@0 445 NS_ERROR("Could not retrieve the IOleWindow interface for IFileDialog.");
michael@0 446 return S_OK;
michael@0 447 }
michael@0 448 HWND hwnd = nullptr;
michael@0 449 win->GetWindow(&hwnd);
michael@0 450 if (!hwnd) {
michael@0 451 NS_ERROR("Could not retrieve the HWND for IFileDialog.");
michael@0 452 return S_OK;
michael@0 453 }
michael@0 454
michael@0 455 SetDialogHandle(hwnd);
michael@0 456 return S_OK;
michael@0 457 }
michael@0 458
michael@0 459 HRESULT
michael@0 460 nsFilePicker::OnOverwrite(IFileDialog *pfd,
michael@0 461 IShellItem *psi,
michael@0 462 FDE_OVERWRITE_RESPONSE *pResponse)
michael@0 463 {
michael@0 464 return S_OK;
michael@0 465 }
michael@0 466
michael@0 467 /*
michael@0 468 * Close on parent close logic
michael@0 469 */
michael@0 470
michael@0 471 bool
michael@0 472 nsFilePicker::ClosePickerIfNeeded(bool aIsXPDialog)
michael@0 473 {
michael@0 474 if (!mParentWidget || !mDlgWnd)
michael@0 475 return false;
michael@0 476
michael@0 477 nsWindow *win = static_cast<nsWindow *>(mParentWidget.get());
michael@0 478 // Note, the xp callbacks hand us an inner window, so we have to step up
michael@0 479 // one to get the actual dialog.
michael@0 480 HWND dlgWnd;
michael@0 481 if (aIsXPDialog)
michael@0 482 dlgWnd = GetParent(mDlgWnd);
michael@0 483 else
michael@0 484 dlgWnd = mDlgWnd;
michael@0 485 if (IsWindow(dlgWnd) && IsWindowVisible(dlgWnd) && win->DestroyCalled()) {
michael@0 486 wchar_t className[64];
michael@0 487 // Make sure we have the right window
michael@0 488 if (GetClassNameW(dlgWnd, className, mozilla::ArrayLength(className)) &&
michael@0 489 !wcscmp(className, L"#32770") &&
michael@0 490 DestroyWindow(dlgWnd)) {
michael@0 491 mDlgWnd = nullptr;
michael@0 492 return true;
michael@0 493 }
michael@0 494 }
michael@0 495 return false;
michael@0 496 }
michael@0 497
michael@0 498 void
michael@0 499 nsFilePicker::PickerCallbackTimerFunc(nsITimer *aTimer, void *aCtx)
michael@0 500 {
michael@0 501 nsFilePicker* picker = (nsFilePicker*)aCtx;
michael@0 502 if (picker->ClosePickerIfNeeded(false)) {
michael@0 503 aTimer->Cancel();
michael@0 504 }
michael@0 505 }
michael@0 506
michael@0 507 void
michael@0 508 nsFilePicker::SetDialogHandle(HWND aWnd)
michael@0 509 {
michael@0 510 if (!aWnd || mDlgWnd)
michael@0 511 return;
michael@0 512 mDlgWnd = aWnd;
michael@0 513 }
michael@0 514
michael@0 515 /*
michael@0 516 * Folder picker invocation
michael@0 517 */
michael@0 518
michael@0 519 // Open the older XP style folder picker dialog. We end up in this call
michael@0 520 // on XP systems or when platform is built without the longhorn SDK.
michael@0 521 bool
michael@0 522 nsFilePicker::ShowXPFolderPicker(const nsString& aInitialDir)
michael@0 523 {
michael@0 524 bool result = false;
michael@0 525
michael@0 526 nsAutoArrayPtr<wchar_t> dirBuffer(new wchar_t[FILE_BUFFER_SIZE]);
michael@0 527 wcsncpy(dirBuffer, aInitialDir.get(), FILE_BUFFER_SIZE);
michael@0 528 dirBuffer[FILE_BUFFER_SIZE-1] = '\0';
michael@0 529
michael@0 530 AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ?
michael@0 531 mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
michael@0 532
michael@0 533 BROWSEINFOW browserInfo = {0};
michael@0 534 browserInfo.pidlRoot = nullptr;
michael@0 535 browserInfo.pszDisplayName = dirBuffer;
michael@0 536 browserInfo.lpszTitle = mTitle.get();
michael@0 537 browserInfo.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS;
michael@0 538 browserInfo.hwndOwner = adtw.get();
michael@0 539 browserInfo.iImage = 0;
michael@0 540 browserInfo.lParam = reinterpret_cast<LPARAM>(this);
michael@0 541
michael@0 542 if (!aInitialDir.IsEmpty()) {
michael@0 543 // the dialog is modal so that |initialDir.get()| will be valid in
michael@0 544 // BrowserCallbackProc. Thus, we don't need to clone it.
michael@0 545 browserInfo.lParam = (LPARAM) aInitialDir.get();
michael@0 546 browserInfo.lpfn = &BrowseCallbackProc;
michael@0 547 } else {
michael@0 548 browserInfo.lParam = 0;
michael@0 549 browserInfo.lpfn = nullptr;
michael@0 550 }
michael@0 551
michael@0 552 LPITEMIDLIST list = ::SHBrowseForFolderW(&browserInfo);
michael@0 553 if (list) {
michael@0 554 result = ::SHGetPathFromIDListW(list, dirBuffer);
michael@0 555 if (result)
michael@0 556 mUnicodeFile.Assign(static_cast<const wchar_t*>(dirBuffer));
michael@0 557 // free PIDL
michael@0 558 CoTaskMemFree(list);
michael@0 559 }
michael@0 560
michael@0 561 return result;
michael@0 562 }
michael@0 563
michael@0 564 /*
michael@0 565 * Show a folder picker post Windows XP
michael@0 566 *
michael@0 567 * @param aInitialDir The initial directory, the last used directory will be
michael@0 568 * used if left blank.
michael@0 569 * @param aWasInitError Out parameter will hold true if there was an error
michael@0 570 * before the folder picker is shown.
michael@0 571 * @return true if a file was selected successfully.
michael@0 572 */
michael@0 573 bool
michael@0 574 nsFilePicker::ShowFolderPicker(const nsString& aInitialDir, bool &aWasInitError)
michael@0 575 {
michael@0 576 nsRefPtr<IFileOpenDialog> dialog;
michael@0 577 if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC,
michael@0 578 IID_IFileOpenDialog,
michael@0 579 getter_AddRefs(dialog)))) {
michael@0 580 aWasInitError = true;
michael@0 581 return false;
michael@0 582 }
michael@0 583 aWasInitError = false;
michael@0 584
michael@0 585 // hook up event callbacks
michael@0 586 dialog->Advise(this, &mFDECookie);
michael@0 587
michael@0 588 // options
michael@0 589 FILEOPENDIALOGOPTIONS fos = FOS_PICKFOLDERS;
michael@0 590 dialog->SetOptions(fos);
michael@0 591
michael@0 592 // initial strings
michael@0 593 dialog->SetTitle(mTitle.get());
michael@0 594 if (!aInitialDir.IsEmpty()) {
michael@0 595 nsRefPtr<IShellItem> folder;
michael@0 596 if (SUCCEEDED(
michael@0 597 WinUtils::SHCreateItemFromParsingName(aInitialDir.get(), nullptr,
michael@0 598 IID_IShellItem,
michael@0 599 getter_AddRefs(folder)))) {
michael@0 600 dialog->SetFolder(folder);
michael@0 601 }
michael@0 602 }
michael@0 603
michael@0 604 AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ?
michael@0 605 mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
michael@0 606
michael@0 607 // display
michael@0 608 nsRefPtr<IShellItem> item;
michael@0 609 if (FAILED(dialog->Show(adtw.get())) ||
michael@0 610 FAILED(dialog->GetResult(getter_AddRefs(item))) ||
michael@0 611 !item) {
michael@0 612 dialog->Unadvise(mFDECookie);
michael@0 613 return false;
michael@0 614 }
michael@0 615 dialog->Unadvise(mFDECookie);
michael@0 616
michael@0 617 // results
michael@0 618
michael@0 619 // If the user chose a Win7 Library, resolve to the library's
michael@0 620 // default save folder.
michael@0 621 nsRefPtr<IShellItem> folderPath;
michael@0 622 nsRefPtr<IShellLibrary> shellLib;
michael@0 623 CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC,
michael@0 624 IID_IShellLibrary, getter_AddRefs(shellLib));
michael@0 625 if (shellLib &&
michael@0 626 SUCCEEDED(shellLib->LoadLibraryFromItem(item, STGM_READ)) &&
michael@0 627 SUCCEEDED(shellLib->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem,
michael@0 628 getter_AddRefs(folderPath)))) {
michael@0 629 item.swap(folderPath);
michael@0 630 }
michael@0 631
michael@0 632 // get the folder's file system path
michael@0 633 return WinUtils::GetShellItemPath(item, mUnicodeFile);
michael@0 634 }
michael@0 635
michael@0 636 /*
michael@0 637 * File open and save picker invocation
michael@0 638 */
michael@0 639
michael@0 640 bool
michael@0 641 nsFilePicker::FilePickerWrapper(OPENFILENAMEW* ofn, PickerType aType)
michael@0 642 {
michael@0 643 if (!ofn)
michael@0 644 return false;
michael@0 645
michael@0 646 bool result = false;
michael@0 647 AutoWidgetPickerState awps(mParentWidget);
michael@0 648 MOZ_SEH_TRY {
michael@0 649 if (aType == PICKER_TYPE_OPEN)
michael@0 650 result = ::GetOpenFileNameW(ofn);
michael@0 651 else if (aType == PICKER_TYPE_SAVE)
michael@0 652 result = ::GetSaveFileNameW(ofn);
michael@0 653 } MOZ_SEH_EXCEPT(true) {
michael@0 654 NS_ERROR("nsFilePicker GetFileName win32 call generated an exception! This is bad!");
michael@0 655 }
michael@0 656 return result;
michael@0 657 }
michael@0 658
michael@0 659 bool
michael@0 660 nsFilePicker::ShowXPFilePicker(const nsString& aInitialDir)
michael@0 661 {
michael@0 662 OPENFILENAMEW ofn = {0};
michael@0 663 ofn.lStructSize = sizeof(ofn);
michael@0 664 nsString filterBuffer = mFilterList;
michael@0 665
michael@0 666 nsAutoArrayPtr<wchar_t> fileBuffer(new wchar_t[FILE_BUFFER_SIZE]);
michael@0 667 wcsncpy(fileBuffer, mDefaultFilePath.get(), FILE_BUFFER_SIZE);
michael@0 668 fileBuffer[FILE_BUFFER_SIZE-1] = '\0'; // null terminate in case copy truncated
michael@0 669
michael@0 670 if (!aInitialDir.IsEmpty()) {
michael@0 671 ofn.lpstrInitialDir = aInitialDir.get();
michael@0 672 }
michael@0 673
michael@0 674 AutoDestroyTmpWindow adtw((HWND) (mParentWidget.get() ?
michael@0 675 mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
michael@0 676
michael@0 677 ofn.lpstrTitle = (LPCWSTR)mTitle.get();
michael@0 678 ofn.lpstrFilter = (LPCWSTR)filterBuffer.get();
michael@0 679 ofn.nFilterIndex = mSelectedType;
michael@0 680 ofn.lpstrFile = fileBuffer;
michael@0 681 ofn.nMaxFile = FILE_BUFFER_SIZE;
michael@0 682 ofn.hwndOwner = adtw.get();
michael@0 683 ofn.lCustData = reinterpret_cast<LPARAM>(this);
michael@0 684 ofn.Flags = OFN_SHAREAWARE | OFN_LONGNAMES | OFN_OVERWRITEPROMPT |
michael@0 685 OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_ENABLESIZING |
michael@0 686 OFN_EXPLORER;
michael@0 687
michael@0 688 // Windows Vista and up won't allow you to use the new looking dialogs with
michael@0 689 // a hook procedure. The hook procedure fixes a problem on XP dialogs for
michael@0 690 // file picker visibility. Vista and up automatically ensures the file
michael@0 691 // picker is always visible.
michael@0 692 if (!IsVistaOrLater()) {
michael@0 693 ofn.lpfnHook = FilePickerHook;
michael@0 694 ofn.Flags |= OFN_ENABLEHOOK;
michael@0 695 }
michael@0 696
michael@0 697 // Handle add to recent docs settings
michael@0 698 if (IsPrivacyModeEnabled() || !mAddToRecentDocs) {
michael@0 699 ofn.Flags |= OFN_DONTADDTORECENT;
michael@0 700 }
michael@0 701
michael@0 702 NS_NAMED_LITERAL_STRING(htmExt, "html");
michael@0 703
michael@0 704 if (!mDefaultExtension.IsEmpty()) {
michael@0 705 ofn.lpstrDefExt = mDefaultExtension.get();
michael@0 706 } else if (IsDefaultPathHtml()) {
michael@0 707 // Get file extension from suggested filename to detect if we are
michael@0 708 // saving an html file.
michael@0 709 // This is supposed to append ".htm" if user doesn't supply an
michael@0 710 // extension but the behavior is sort of weird:
michael@0 711 // - Often appends ".html" even if you have an extension
michael@0 712 // - It obeys your extension if you put quotes around name
michael@0 713 ofn.lpstrDefExt = htmExt.get();
michael@0 714 }
michael@0 715
michael@0 716 // When possible, instead of using OFN_NOCHANGEDIR to ensure the current
michael@0 717 // working directory will not change from this call, we will retrieve the
michael@0 718 // current working directory before the call and restore it after the
michael@0 719 // call. This flag causes problems on Windows XP for paths that are
michael@0 720 // selected like C:test.txt where the user is currently at C:\somepath
michael@0 721 // In which case expected result should be C:\somepath\test.txt
michael@0 722 AutoRestoreWorkingPath restoreWorkingPath;
michael@0 723 // If we can't get the current working directory, the best case is to
michael@0 724 // use the OFN_NOCHANGEDIR flag
michael@0 725 if (!restoreWorkingPath.HasWorkingPath()) {
michael@0 726 ofn.Flags |= OFN_NOCHANGEDIR;
michael@0 727 }
michael@0 728
michael@0 729 bool result = false;
michael@0 730
michael@0 731 switch(mMode) {
michael@0 732 case modeOpen:
michael@0 733 // FILE MUST EXIST!
michael@0 734 ofn.Flags |= OFN_FILEMUSTEXIST;
michael@0 735 result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN);
michael@0 736 break;
michael@0 737
michael@0 738 case modeOpenMultiple:
michael@0 739 ofn.Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT;
michael@0 740
michael@0 741 // The hook set here ensures that the buffer returned will always be
michael@0 742 // large enough to hold all selected files. The hook may modify the
michael@0 743 // value of ofn.lpstrFile and deallocate the old buffer that it pointed
michael@0 744 // to (fileBuffer). The hook assumes that the passed in value is heap
michael@0 745 // allocated and that the returned value should be freed by the caller.
michael@0 746 // If the hook changes the buffer, it will deallocate the old buffer.
michael@0 747 // This fix would be nice to have in Vista and up, but it would force
michael@0 748 // the file picker to use the old style dialogs because hooks are not
michael@0 749 // allowed in the new file picker UI. We need to eventually move to
michael@0 750 // the new Common File Dialogs for Vista and up.
michael@0 751 if (!IsVistaOrLater()) {
michael@0 752 ofn.lpfnHook = MultiFilePickerHook;
michael@0 753 fileBuffer.forget();
michael@0 754 result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN);
michael@0 755 fileBuffer = ofn.lpstrFile;
michael@0 756 } else {
michael@0 757 result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN);
michael@0 758 }
michael@0 759 break;
michael@0 760
michael@0 761 case modeSave:
michael@0 762 {
michael@0 763 ofn.Flags |= OFN_NOREADONLYRETURN;
michael@0 764
michael@0 765 // Don't follow shortcuts when saving a shortcut, this can be used
michael@0 766 // to trick users (bug 271732)
michael@0 767 if (IsDefaultPathLink())
michael@0 768 ofn.Flags |= OFN_NODEREFERENCELINKS;
michael@0 769
michael@0 770 result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE);
michael@0 771 if (!result) {
michael@0 772 // Error, find out what kind.
michael@0 773 if (GetLastError() == ERROR_INVALID_PARAMETER ||
michael@0 774 CommDlgExtendedError() == FNERR_INVALIDFILENAME) {
michael@0 775 // Probably the default file name is too long or contains illegal
michael@0 776 // characters. Try again, without a starting file name.
michael@0 777 ofn.lpstrFile[0] = L'\0';
michael@0 778 result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE);
michael@0 779 }
michael@0 780 }
michael@0 781 }
michael@0 782 break;
michael@0 783
michael@0 784 default:
michael@0 785 NS_NOTREACHED("unsupported file picker mode");
michael@0 786 return false;
michael@0 787 }
michael@0 788
michael@0 789 if (!result)
michael@0 790 return false;
michael@0 791
michael@0 792 // Remember what filter type the user selected
michael@0 793 mSelectedType = (int16_t)ofn.nFilterIndex;
michael@0 794
michael@0 795 // Single file selection, we're done
michael@0 796 if (mMode != modeOpenMultiple) {
michael@0 797 GetQualifiedPath(fileBuffer, mUnicodeFile);
michael@0 798 return true;
michael@0 799 }
michael@0 800
michael@0 801 // Set user-selected location of file or directory. From msdn's "Open and
michael@0 802 // Save As Dialog Boxes" section:
michael@0 803 // If you specify OFN_EXPLORER, the directory and file name strings are '\0'
michael@0 804 // separated, with an extra '\0' character after the last file name. This
michael@0 805 // format enables the Explorer-style dialog boxes to return long file names
michael@0 806 // that include spaces.
michael@0 807 wchar_t *current = fileBuffer;
michael@0 808
michael@0 809 nsAutoString dirName(current);
michael@0 810 // Sometimes dirName contains a trailing slash and sometimes it doesn't:
michael@0 811 if (current[dirName.Length() - 1] != '\\')
michael@0 812 dirName.Append((char16_t)'\\');
michael@0 813
michael@0 814 while (current && *current && *(current + wcslen(current) + 1)) {
michael@0 815 current = current + wcslen(current) + 1;
michael@0 816
michael@0 817 nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1");
michael@0 818 NS_ENSURE_TRUE(file, false);
michael@0 819
michael@0 820 // Only prepend the directory if the path specified is a relative path
michael@0 821 nsAutoString path;
michael@0 822 if (PathIsRelativeW(current)) {
michael@0 823 path = dirName + nsDependentString(current);
michael@0 824 } else {
michael@0 825 path = current;
michael@0 826 }
michael@0 827
michael@0 828 nsAutoString canonicalizedPath;
michael@0 829 GetQualifiedPath(path.get(), canonicalizedPath);
michael@0 830 if (NS_FAILED(file->InitWithPath(canonicalizedPath)) ||
michael@0 831 !mFiles.AppendObject(file))
michael@0 832 return false;
michael@0 833 }
michael@0 834
michael@0 835 // Handle the case where the user selected just one file. From msdn: If you
michael@0 836 // specify OFN_ALLOWMULTISELECT and the user selects only one file the
michael@0 837 // lpstrFile string does not have a separator between the path and file name.
michael@0 838 if (current && *current && (current == fileBuffer)) {
michael@0 839 nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1");
michael@0 840 NS_ENSURE_TRUE(file, false);
michael@0 841
michael@0 842 nsAutoString canonicalizedPath;
michael@0 843 GetQualifiedPath(current, canonicalizedPath);
michael@0 844 if (NS_FAILED(file->InitWithPath(canonicalizedPath)) ||
michael@0 845 !mFiles.AppendObject(file))
michael@0 846 return false;
michael@0 847 }
michael@0 848
michael@0 849 return true;
michael@0 850 }
michael@0 851
michael@0 852 /*
michael@0 853 * Show a file picker post Windows XP
michael@0 854 *
michael@0 855 * @param aInitialDir The initial directory, the last used directory will be
michael@0 856 * used if left blank.
michael@0 857 * @param aWasInitError Out parameter will hold true if there was an error
michael@0 858 * before the file picker is shown.
michael@0 859 * @return true if a file was selected successfully.
michael@0 860 */
michael@0 861 bool
michael@0 862 nsFilePicker::ShowFilePicker(const nsString& aInitialDir, bool &aWasInitError)
michael@0 863 {
michael@0 864 nsRefPtr<IFileDialog> dialog;
michael@0 865 if (mMode != modeSave) {
michael@0 866 if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC,
michael@0 867 IID_IFileOpenDialog,
michael@0 868 getter_AddRefs(dialog)))) {
michael@0 869 aWasInitError = true;
michael@0 870 return false;
michael@0 871 }
michael@0 872 } else {
michael@0 873 if (FAILED(CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC,
michael@0 874 IID_IFileSaveDialog,
michael@0 875 getter_AddRefs(dialog)))) {
michael@0 876 aWasInitError = true;
michael@0 877 return false;
michael@0 878 }
michael@0 879 }
michael@0 880 aWasInitError = false;
michael@0 881
michael@0 882 // hook up event callbacks
michael@0 883 dialog->Advise(this, &mFDECookie);
michael@0 884
michael@0 885 // options
michael@0 886
michael@0 887 FILEOPENDIALOGOPTIONS fos = 0;
michael@0 888 fos |= FOS_SHAREAWARE | FOS_OVERWRITEPROMPT |
michael@0 889 FOS_FORCEFILESYSTEM;
michael@0 890
michael@0 891 // Handle add to recent docs settings
michael@0 892 if (IsPrivacyModeEnabled() || !mAddToRecentDocs) {
michael@0 893 fos |= FOS_DONTADDTORECENT;
michael@0 894 }
michael@0 895
michael@0 896 // Msdn claims FOS_NOCHANGEDIR is not needed. We'll add this
michael@0 897 // just in case.
michael@0 898 AutoRestoreWorkingPath arw;
michael@0 899
michael@0 900 // mode specific
michael@0 901 switch(mMode) {
michael@0 902 case modeOpen:
michael@0 903 fos |= FOS_FILEMUSTEXIST;
michael@0 904 break;
michael@0 905
michael@0 906 case modeOpenMultiple:
michael@0 907 fos |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT;
michael@0 908 break;
michael@0 909
michael@0 910 case modeSave:
michael@0 911 fos |= FOS_NOREADONLYRETURN;
michael@0 912 // Don't follow shortcuts when saving a shortcut, this can be used
michael@0 913 // to trick users (bug 271732)
michael@0 914 if (IsDefaultPathLink())
michael@0 915 fos |= FOS_NODEREFERENCELINKS;
michael@0 916 break;
michael@0 917 }
michael@0 918
michael@0 919 dialog->SetOptions(fos);
michael@0 920
michael@0 921 // initial strings
michael@0 922
michael@0 923 // title
michael@0 924 dialog->SetTitle(mTitle.get());
michael@0 925
michael@0 926 // default filename
michael@0 927 if (!mDefaultFilename.IsEmpty()) {
michael@0 928 dialog->SetFileName(mDefaultFilename.get());
michael@0 929 }
michael@0 930
michael@0 931 NS_NAMED_LITERAL_STRING(htmExt, "html");
michael@0 932
michael@0 933 // default extension to append to new files
michael@0 934 if (!mDefaultExtension.IsEmpty()) {
michael@0 935 dialog->SetDefaultExtension(mDefaultExtension.get());
michael@0 936 } else if (IsDefaultPathHtml()) {
michael@0 937 dialog->SetDefaultExtension(htmExt.get());
michael@0 938 }
michael@0 939
michael@0 940 // initial location
michael@0 941 if (!aInitialDir.IsEmpty()) {
michael@0 942 nsRefPtr<IShellItem> folder;
michael@0 943 if (SUCCEEDED(
michael@0 944 WinUtils::SHCreateItemFromParsingName(aInitialDir.get(), nullptr,
michael@0 945 IID_IShellItem,
michael@0 946 getter_AddRefs(folder)))) {
michael@0 947 dialog->SetFolder(folder);
michael@0 948 }
michael@0 949 }
michael@0 950
michael@0 951 // filter types and the default index
michael@0 952 if (!mComFilterList.IsEmpty()) {
michael@0 953 dialog->SetFileTypes(mComFilterList.Length(), mComFilterList.get());
michael@0 954 dialog->SetFileTypeIndex(mSelectedType);
michael@0 955 }
michael@0 956
michael@0 957 // display
michael@0 958
michael@0 959 {
michael@0 960 AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ?
michael@0 961 mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
michael@0 962 AutoTimerCallbackCancel atcc(this, PickerCallbackTimerFunc);
michael@0 963 AutoWidgetPickerState awps(mParentWidget);
michael@0 964
michael@0 965 if (FAILED(dialog->Show(adtw.get()))) {
michael@0 966 dialog->Unadvise(mFDECookie);
michael@0 967 return false;
michael@0 968 }
michael@0 969 dialog->Unadvise(mFDECookie);
michael@0 970 }
michael@0 971
michael@0 972 // results
michael@0 973
michael@0 974 // Remember what filter type the user selected
michael@0 975 UINT filterIdxResult;
michael@0 976 if (SUCCEEDED(dialog->GetFileTypeIndex(&filterIdxResult))) {
michael@0 977 mSelectedType = (int16_t)filterIdxResult;
michael@0 978 }
michael@0 979
michael@0 980 // single selection
michael@0 981 if (mMode != modeOpenMultiple) {
michael@0 982 nsRefPtr<IShellItem> item;
michael@0 983 if (FAILED(dialog->GetResult(getter_AddRefs(item))) || !item)
michael@0 984 return false;
michael@0 985 return WinUtils::GetShellItemPath(item, mUnicodeFile);
michael@0 986 }
michael@0 987
michael@0 988 // multiple selection
michael@0 989 nsRefPtr<IFileOpenDialog> openDlg;
michael@0 990 dialog->QueryInterface(IID_IFileOpenDialog, getter_AddRefs(openDlg));
michael@0 991 if (!openDlg) {
michael@0 992 // should not happen
michael@0 993 return false;
michael@0 994 }
michael@0 995
michael@0 996 nsRefPtr<IShellItemArray> items;
michael@0 997 if (FAILED(openDlg->GetResults(getter_AddRefs(items))) || !items) {
michael@0 998 return false;
michael@0 999 }
michael@0 1000
michael@0 1001 DWORD count = 0;
michael@0 1002 items->GetCount(&count);
michael@0 1003 for (unsigned int idx = 0; idx < count; idx++) {
michael@0 1004 nsRefPtr<IShellItem> item;
michael@0 1005 nsAutoString str;
michael@0 1006 if (SUCCEEDED(items->GetItemAt(idx, getter_AddRefs(item)))) {
michael@0 1007 if (!WinUtils::GetShellItemPath(item, str))
michael@0 1008 continue;
michael@0 1009 nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1");
michael@0 1010 if (file && NS_SUCCEEDED(file->InitWithPath(str)))
michael@0 1011 mFiles.AppendObject(file);
michael@0 1012 }
michael@0 1013 }
michael@0 1014 return true;
michael@0 1015 }
michael@0 1016
michael@0 1017 ///////////////////////////////////////////////////////////////////////////////
michael@0 1018 // nsIFilePicker impl.
michael@0 1019
michael@0 1020 NS_IMETHODIMP
michael@0 1021 nsFilePicker::ShowW(int16_t *aReturnVal)
michael@0 1022 {
michael@0 1023 NS_ENSURE_ARG_POINTER(aReturnVal);
michael@0 1024
michael@0 1025 *aReturnVal = returnCancel;
michael@0 1026
michael@0 1027 AutoSuppressEvents supress(mParentWidget);
michael@0 1028
michael@0 1029 nsAutoString initialDir;
michael@0 1030 if (mDisplayDirectory)
michael@0 1031 mDisplayDirectory->GetPath(initialDir);
michael@0 1032
michael@0 1033 // If no display directory, re-use the last one.
michael@0 1034 if(initialDir.IsEmpty()) {
michael@0 1035 // Allocate copy of last used dir.
michael@0 1036 initialDir = mLastUsedUnicodeDirectory;
michael@0 1037 }
michael@0 1038
michael@0 1039 // Clear previous file selections
michael@0 1040 mUnicodeFile.Truncate();
michael@0 1041 mFiles.Clear();
michael@0 1042
michael@0 1043 // Launch the XP file/folder picker on XP and as a fallback on Vista+.
michael@0 1044 // The CoCreateInstance call to CLSID_FileOpenDialog fails with "(0x80040111)
michael@0 1045 // ClassFactory cannot supply requested class" when the checkbox for
michael@0 1046 // Disable Visual Themes is on in the compatability tab within the shortcut
michael@0 1047 // properties.
michael@0 1048 bool result = false, wasInitError = true;
michael@0 1049 if (mMode == modeGetFolder) {
michael@0 1050 if (IsVistaOrLater())
michael@0 1051 result = ShowFolderPicker(initialDir, wasInitError);
michael@0 1052 if (!result && wasInitError)
michael@0 1053 result = ShowXPFolderPicker(initialDir);
michael@0 1054 } else {
michael@0 1055 if (IsVistaOrLater())
michael@0 1056 result = ShowFilePicker(initialDir, wasInitError);
michael@0 1057 if (!result && wasInitError)
michael@0 1058 result = ShowXPFilePicker(initialDir);
michael@0 1059 }
michael@0 1060
michael@0 1061 // exit, and return returnCancel in aReturnVal
michael@0 1062 if (!result)
michael@0 1063 return NS_OK;
michael@0 1064
michael@0 1065 RememberLastUsedDirectory();
michael@0 1066
michael@0 1067 int16_t retValue = returnOK;
michael@0 1068 if (mMode == modeSave) {
michael@0 1069 // Windows does not return resultReplace, we must check if file
michael@0 1070 // already exists.
michael@0 1071 nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
michael@0 1072 bool flag = false;
michael@0 1073 if (file && NS_SUCCEEDED(file->InitWithPath(mUnicodeFile)) &&
michael@0 1074 NS_SUCCEEDED(file->Exists(&flag)) && flag) {
michael@0 1075 retValue = returnReplace;
michael@0 1076 }
michael@0 1077 }
michael@0 1078
michael@0 1079 *aReturnVal = retValue;
michael@0 1080 return NS_OK;
michael@0 1081 }
michael@0 1082
michael@0 1083 NS_IMETHODIMP
michael@0 1084 nsFilePicker::Show(int16_t *aReturnVal)
michael@0 1085 {
michael@0 1086 return ShowW(aReturnVal);
michael@0 1087 }
michael@0 1088
michael@0 1089 NS_IMETHODIMP
michael@0 1090 nsFilePicker::GetFile(nsIFile **aFile)
michael@0 1091 {
michael@0 1092 NS_ENSURE_ARG_POINTER(aFile);
michael@0 1093 *aFile = nullptr;
michael@0 1094
michael@0 1095 if (mUnicodeFile.IsEmpty())
michael@0 1096 return NS_OK;
michael@0 1097
michael@0 1098 nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
michael@0 1099
michael@0 1100 NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
michael@0 1101
michael@0 1102 file->InitWithPath(mUnicodeFile);
michael@0 1103
michael@0 1104 NS_ADDREF(*aFile = file);
michael@0 1105
michael@0 1106 return NS_OK;
michael@0 1107 }
michael@0 1108
michael@0 1109 NS_IMETHODIMP
michael@0 1110 nsFilePicker::GetFileURL(nsIURI **aFileURL)
michael@0 1111 {
michael@0 1112 *aFileURL = nullptr;
michael@0 1113 nsCOMPtr<nsIFile> file;
michael@0 1114 nsresult rv = GetFile(getter_AddRefs(file));
michael@0 1115 if (!file)
michael@0 1116 return rv;
michael@0 1117
michael@0 1118 return NS_NewFileURI(aFileURL, file);
michael@0 1119 }
michael@0 1120
michael@0 1121 NS_IMETHODIMP
michael@0 1122 nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles)
michael@0 1123 {
michael@0 1124 NS_ENSURE_ARG_POINTER(aFiles);
michael@0 1125 return NS_NewArrayEnumerator(aFiles, mFiles);
michael@0 1126 }
michael@0 1127
michael@0 1128 // Get the file + path
michael@0 1129 NS_IMETHODIMP
michael@0 1130 nsBaseWinFilePicker::SetDefaultString(const nsAString& aString)
michael@0 1131 {
michael@0 1132 mDefaultFilePath = aString;
michael@0 1133
michael@0 1134 // First, make sure the file name is not too long.
michael@0 1135 int32_t nameLength;
michael@0 1136 int32_t nameIndex = mDefaultFilePath.RFind("\\");
michael@0 1137 if (nameIndex == kNotFound)
michael@0 1138 nameIndex = 0;
michael@0 1139 else
michael@0 1140 nameIndex ++;
michael@0 1141 nameLength = mDefaultFilePath.Length() - nameIndex;
michael@0 1142 mDefaultFilename.Assign(Substring(mDefaultFilePath, nameIndex));
michael@0 1143
michael@0 1144 if (nameLength > MAX_PATH) {
michael@0 1145 int32_t extIndex = mDefaultFilePath.RFind(".");
michael@0 1146 if (extIndex == kNotFound)
michael@0 1147 extIndex = mDefaultFilePath.Length();
michael@0 1148
michael@0 1149 // Let's try to shave the needed characters from the name part.
michael@0 1150 int32_t charsToRemove = nameLength - MAX_PATH;
michael@0 1151 if (extIndex - nameIndex >= charsToRemove) {
michael@0 1152 mDefaultFilePath.Cut(extIndex - charsToRemove, charsToRemove);
michael@0 1153 }
michael@0 1154 }
michael@0 1155
michael@0 1156 // Then, we need to replace illegal characters. At this stage, we cannot
michael@0 1157 // replace the backslash as the string might represent a file path.
michael@0 1158 mDefaultFilePath.ReplaceChar(FILE_ILLEGAL_CHARACTERS, '-');
michael@0 1159 mDefaultFilename.ReplaceChar(FILE_ILLEGAL_CHARACTERS, '-');
michael@0 1160
michael@0 1161 return NS_OK;
michael@0 1162 }
michael@0 1163
michael@0 1164 NS_IMETHODIMP
michael@0 1165 nsBaseWinFilePicker::GetDefaultString(nsAString& aString)
michael@0 1166 {
michael@0 1167 return NS_ERROR_FAILURE;
michael@0 1168 }
michael@0 1169
michael@0 1170 // The default extension to use for files
michael@0 1171 NS_IMETHODIMP
michael@0 1172 nsBaseWinFilePicker::GetDefaultExtension(nsAString& aExtension)
michael@0 1173 {
michael@0 1174 aExtension = mDefaultExtension;
michael@0 1175 return NS_OK;
michael@0 1176 }
michael@0 1177
michael@0 1178 NS_IMETHODIMP
michael@0 1179 nsBaseWinFilePicker::SetDefaultExtension(const nsAString& aExtension)
michael@0 1180 {
michael@0 1181 mDefaultExtension = aExtension;
michael@0 1182 return NS_OK;
michael@0 1183 }
michael@0 1184
michael@0 1185 // Set the filter index
michael@0 1186 NS_IMETHODIMP
michael@0 1187 nsFilePicker::GetFilterIndex(int32_t *aFilterIndex)
michael@0 1188 {
michael@0 1189 // Windows' filter index is 1-based, we use a 0-based system.
michael@0 1190 *aFilterIndex = mSelectedType - 1;
michael@0 1191 return NS_OK;
michael@0 1192 }
michael@0 1193
michael@0 1194 NS_IMETHODIMP
michael@0 1195 nsFilePicker::SetFilterIndex(int32_t aFilterIndex)
michael@0 1196 {
michael@0 1197 // Windows' filter index is 1-based, we use a 0-based system.
michael@0 1198 mSelectedType = aFilterIndex + 1;
michael@0 1199 return NS_OK;
michael@0 1200 }
michael@0 1201
michael@0 1202 void
michael@0 1203 nsFilePicker::InitNative(nsIWidget *aParent,
michael@0 1204 const nsAString& aTitle)
michael@0 1205 {
michael@0 1206 mParentWidget = aParent;
michael@0 1207 mTitle.Assign(aTitle);
michael@0 1208 }
michael@0 1209
michael@0 1210 void
michael@0 1211 nsFilePicker::GetQualifiedPath(const wchar_t *aInPath, nsString &aOutPath)
michael@0 1212 {
michael@0 1213 // Prefer a qualified path over a non qualified path.
michael@0 1214 // Things like c:file.txt would be accepted in Win XP but would later
michael@0 1215 // fail to open from the download manager.
michael@0 1216 wchar_t qualifiedFileBuffer[MAX_PATH];
michael@0 1217 if (PathSearchAndQualifyW(aInPath, qualifiedFileBuffer, MAX_PATH)) {
michael@0 1218 aOutPath.Assign(qualifiedFileBuffer);
michael@0 1219 } else {
michael@0 1220 aOutPath.Assign(aInPath);
michael@0 1221 }
michael@0 1222 }
michael@0 1223
michael@0 1224 void
michael@0 1225 nsFilePicker::AppendXPFilter(const nsAString& aTitle, const nsAString& aFilter)
michael@0 1226 {
michael@0 1227 mFilterList.Append(aTitle);
michael@0 1228 mFilterList.Append(char16_t('\0'));
michael@0 1229
michael@0 1230 if (aFilter.EqualsLiteral("..apps"))
michael@0 1231 mFilterList.AppendLiteral("*.exe;*.com");
michael@0 1232 else
michael@0 1233 {
michael@0 1234 nsAutoString filter(aFilter);
michael@0 1235 filter.StripWhitespace();
michael@0 1236 if (filter.EqualsLiteral("*"))
michael@0 1237 filter.AppendLiteral(".*");
michael@0 1238 mFilterList.Append(filter);
michael@0 1239 }
michael@0 1240
michael@0 1241 mFilterList.Append(char16_t('\0'));
michael@0 1242 }
michael@0 1243
michael@0 1244 NS_IMETHODIMP
michael@0 1245 nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter)
michael@0 1246 {
michael@0 1247 if (IsVistaOrLater()) {
michael@0 1248 mComFilterList.Append(aTitle, aFilter);
michael@0 1249 } else {
michael@0 1250 AppendXPFilter(aTitle, aFilter);
michael@0 1251 }
michael@0 1252 return NS_OK;
michael@0 1253 }
michael@0 1254
michael@0 1255 void
michael@0 1256 nsFilePicker::RememberLastUsedDirectory()
michael@0 1257 {
michael@0 1258 nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
michael@0 1259 if (!file || NS_FAILED(file->InitWithPath(mUnicodeFile))) {
michael@0 1260 NS_WARNING("RememberLastUsedDirectory failed to init file path.");
michael@0 1261 return;
michael@0 1262 }
michael@0 1263
michael@0 1264 nsCOMPtr<nsIFile> dir;
michael@0 1265 nsAutoString newDir;
michael@0 1266 if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) ||
michael@0 1267 !(mDisplayDirectory = do_QueryInterface(dir)) ||
michael@0 1268 NS_FAILED(mDisplayDirectory->GetPath(newDir)) ||
michael@0 1269 newDir.IsEmpty()) {
michael@0 1270 NS_WARNING("RememberLastUsedDirectory failed to get parent directory.");
michael@0 1271 return;
michael@0 1272 }
michael@0 1273
michael@0 1274 if (mLastUsedUnicodeDirectory) {
michael@0 1275 NS_Free(mLastUsedUnicodeDirectory);
michael@0 1276 mLastUsedUnicodeDirectory = nullptr;
michael@0 1277 }
michael@0 1278 mLastUsedUnicodeDirectory = ToNewUnicode(newDir);
michael@0 1279 }
michael@0 1280
michael@0 1281 bool
michael@0 1282 nsFilePicker::IsPrivacyModeEnabled()
michael@0 1283 {
michael@0 1284 return mLoadContext && mLoadContext->UsePrivateBrowsing();
michael@0 1285 }
michael@0 1286
michael@0 1287 bool
michael@0 1288 nsFilePicker::IsDefaultPathLink()
michael@0 1289 {
michael@0 1290 NS_ConvertUTF16toUTF8 ext(mDefaultFilePath);
michael@0 1291 ext.Trim(" .", false, true); // watch out for trailing space and dots
michael@0 1292 ToLowerCase(ext);
michael@0 1293 if (StringEndsWith(ext, NS_LITERAL_CSTRING(".lnk")) ||
michael@0 1294 StringEndsWith(ext, NS_LITERAL_CSTRING(".pif")) ||
michael@0 1295 StringEndsWith(ext, NS_LITERAL_CSTRING(".url")))
michael@0 1296 return true;
michael@0 1297 return false;
michael@0 1298 }
michael@0 1299
michael@0 1300 bool
michael@0 1301 nsFilePicker::IsDefaultPathHtml()
michael@0 1302 {
michael@0 1303 int32_t extIndex = mDefaultFilePath.RFind(".");
michael@0 1304 if (extIndex >= 0) {
michael@0 1305 nsAutoString ext;
michael@0 1306 mDefaultFilePath.Right(ext, mDefaultFilePath.Length() - extIndex);
michael@0 1307 if (ext.LowerCaseEqualsLiteral(".htm") ||
michael@0 1308 ext.LowerCaseEqualsLiteral(".html") ||
michael@0 1309 ext.LowerCaseEqualsLiteral(".shtml"))
michael@0 1310 return true;
michael@0 1311 }
michael@0 1312 return false;
michael@0 1313 }
michael@0 1314
michael@0 1315 void
michael@0 1316 nsFilePicker::ComDlgFilterSpec::Append(const nsAString& aTitle, const nsAString& aFilter)
michael@0 1317 {
michael@0 1318 COMDLG_FILTERSPEC* pSpecForward = mSpecList.AppendElement();
michael@0 1319 if (!pSpecForward) {
michael@0 1320 NS_WARNING("mSpecList realloc failed.");
michael@0 1321 return;
michael@0 1322 }
michael@0 1323 memset(pSpecForward, 0, sizeof(*pSpecForward));
michael@0 1324 nsString* pStr = mStrings.AppendElement(aTitle);
michael@0 1325 if (!pStr) {
michael@0 1326 NS_WARNING("mStrings.AppendElement failed.");
michael@0 1327 return;
michael@0 1328 }
michael@0 1329 pSpecForward->pszName = pStr->get();
michael@0 1330 pStr = mStrings.AppendElement(aFilter);
michael@0 1331 if (!pStr) {
michael@0 1332 NS_WARNING("mStrings.AppendElement failed.");
michael@0 1333 return;
michael@0 1334 }
michael@0 1335 if (aFilter.EqualsLiteral("..apps"))
michael@0 1336 pStr->AssignLiteral("*.exe;*.com");
michael@0 1337 else {
michael@0 1338 pStr->StripWhitespace();
michael@0 1339 if (pStr->EqualsLiteral("*"))
michael@0 1340 pStr->AppendLiteral(".*");
michael@0 1341 }
michael@0 1342 pSpecForward->pszSpec = pStr->get();
michael@0 1343 }

mercurial