1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ui/shell_dialogs/select_file_dialog_win.h" 6 7 #include <shlobj.h> 8 9 #include <algorithm> 10 #include <set> 11 12 #include "base/bind.h" 13 #include "base/files/file_path.h" 14 #include "base/files/file_util.h" 15 #include "base/i18n/case_conversion.h" 16 #include "base/message_loop/message_loop.h" 17 #include "base/message_loop/message_loop_proxy.h" 18 #include "base/strings/utf_string_conversions.h" 19 #include "base/threading/thread.h" 20 #include "base/tuple.h" 21 #include "base/win/registry.h" 22 #include "base/win/scoped_comptr.h" 23 #include "base/win/shortcut.h" 24 #include "ui/aura/window.h" 25 #include "ui/aura/window_event_dispatcher.h" 26 #include "ui/aura/window_tree_host.h" 27 #include "ui/base/l10n/l10n_util.h" 28 #include "ui/base/win/open_file_name_win.h" 29 #include "ui/gfx/native_widget_types.h" 30 #include "ui/shell_dialogs/base_shell_dialog_win.h" 31 #include "ui/shell_dialogs/shell_dialogs_delegate.h" 32 #include "ui/strings/grit/ui_strings.h" 33 #include "win8/viewer/metro_viewer_process_host.h" 34 35 namespace { 36 37 bool CallBuiltinGetOpenFileName(OPENFILENAME* ofn) { 38 return ::GetOpenFileName(ofn) == TRUE; 39 } 40 41 bool CallBuiltinGetSaveFileName(OPENFILENAME* ofn) { 42 return ::GetSaveFileName(ofn) == TRUE; 43 } 44 45 // Given |extension|, if it's not empty, then remove the leading dot. 46 std::wstring GetExtensionWithoutLeadingDot(const std::wstring& extension) { 47 DCHECK(extension.empty() || extension[0] == L'.'); 48 return extension.empty() ? extension : extension.substr(1); 49 } 50 51 // Distinguish directories from regular files. 52 bool IsDirectory(const base::FilePath& path) { 53 base::File::Info file_info; 54 return base::GetFileInfo(path, &file_info) ? 55 file_info.is_directory : path.EndsWithSeparator(); 56 } 57 58 // Get the file type description from the registry. This will be "Text Document" 59 // for .txt files, "JPEG Image" for .jpg files, etc. If the registry doesn't 60 // have an entry for the file type, we return false, true if the description was 61 // found. 'file_ext' must be in form ".txt". 62 static bool GetRegistryDescriptionFromExtension(const std::wstring& file_ext, 63 std::wstring* reg_description) { 64 DCHECK(reg_description); 65 base::win::RegKey reg_ext(HKEY_CLASSES_ROOT, file_ext.c_str(), KEY_READ); 66 std::wstring reg_app; 67 if (reg_ext.ReadValue(NULL, ®_app) == ERROR_SUCCESS && !reg_app.empty()) { 68 base::win::RegKey reg_link(HKEY_CLASSES_ROOT, reg_app.c_str(), KEY_READ); 69 if (reg_link.ReadValue(NULL, reg_description) == ERROR_SUCCESS) 70 return true; 71 } 72 return false; 73 } 74 75 // Set up a filter for a Save/Open dialog, which will consist of |file_ext| file 76 // extensions (internally separated by semicolons), |ext_desc| as the text 77 // descriptions of the |file_ext| types (optional), and (optionally) the default 78 // 'All Files' view. The purpose of the filter is to show only files of a 79 // particular type in a Windows Save/Open dialog box. The resulting filter is 80 // returned. The filters created here are: 81 // 1. only files that have 'file_ext' as their extension 82 // 2. all files (only added if 'include_all_files' is true) 83 // Example: 84 // file_ext: { "*.txt", "*.htm;*.html" } 85 // ext_desc: { "Text Document" } 86 // returned: "Text Document\0*.txt\0HTML Document\0*.htm;*.html\0" 87 // "All Files\0*.*\0\0" (in one big string) 88 // If a description is not provided for a file extension, it will be retrieved 89 // from the registry. If the file extension does not exist in the registry, it 90 // will be omitted from the filter, as it is likely a bogus extension. 91 std::wstring FormatFilterForExtensions( 92 const std::vector<std::wstring>& file_ext, 93 const std::vector<std::wstring>& ext_desc, 94 bool include_all_files) { 95 const std::wstring all_ext = L"*.*"; 96 const std::wstring all_desc = 97 l10n_util::GetStringUTF16(IDS_APP_SAVEAS_ALL_FILES); 98 99 DCHECK(file_ext.size() >= ext_desc.size()); 100 101 if (file_ext.empty()) 102 include_all_files = true; 103 104 std::wstring result; 105 106 for (size_t i = 0; i < file_ext.size(); ++i) { 107 std::wstring ext = file_ext[i]; 108 std::wstring desc; 109 if (i < ext_desc.size()) 110 desc = ext_desc[i]; 111 112 if (ext.empty()) { 113 // Force something reasonable to appear in the dialog box if there is no 114 // extension provided. 115 include_all_files = true; 116 continue; 117 } 118 119 if (desc.empty()) { 120 DCHECK(ext.find(L'.') != std::wstring::npos); 121 std::wstring first_extension = ext.substr(ext.find(L'.')); 122 size_t first_separator_index = first_extension.find(L';'); 123 if (first_separator_index != std::wstring::npos) 124 first_extension = first_extension.substr(0, first_separator_index); 125 126 // Find the extension name without the preceeding '.' character. 127 std::wstring ext_name = first_extension; 128 size_t ext_index = ext_name.find_first_not_of(L'.'); 129 if (ext_index != std::wstring::npos) 130 ext_name = ext_name.substr(ext_index); 131 132 if (!GetRegistryDescriptionFromExtension(first_extension, &desc)) { 133 // The extension doesn't exist in the registry. Create a description 134 // based on the unknown extension type (i.e. if the extension is .qqq, 135 // the we create a description "QQQ File (.qqq)"). 136 include_all_files = true; 137 desc = l10n_util::GetStringFUTF16( 138 IDS_APP_SAVEAS_EXTENSION_FORMAT, 139 base::i18n::ToUpper(base::WideToUTF16(ext_name)), 140 ext_name); 141 } 142 if (desc.empty()) 143 desc = L"*." + ext_name; 144 } 145 146 result.append(desc.c_str(), desc.size() + 1); // Append NULL too. 147 result.append(ext.c_str(), ext.size() + 1); 148 } 149 150 if (include_all_files) { 151 result.append(all_desc.c_str(), all_desc.size() + 1); 152 result.append(all_ext.c_str(), all_ext.size() + 1); 153 } 154 155 result.append(1, '\0'); // Double NULL required. 156 return result; 157 } 158 159 // Implementation of SelectFileDialog that shows a Windows common dialog for 160 // choosing a file or folder. 161 class SelectFileDialogImpl : public ui::SelectFileDialog, 162 public ui::BaseShellDialogImpl { 163 public: 164 SelectFileDialogImpl( 165 Listener* listener, 166 ui::SelectFilePolicy* policy, 167 const base::Callback<bool(OPENFILENAME*)>& get_open_file_name_impl, 168 const base::Callback<bool(OPENFILENAME*)>& get_save_file_name_impl); 169 170 // BaseShellDialog implementation: 171 virtual bool IsRunning(gfx::NativeWindow owning_window) const OVERRIDE; 172 virtual void ListenerDestroyed() OVERRIDE; 173 174 protected: 175 // SelectFileDialog implementation: 176 virtual void SelectFileImpl( 177 Type type, 178 const base::string16& title, 179 const base::FilePath& default_path, 180 const FileTypeInfo* file_types, 181 int file_type_index, 182 const base::FilePath::StringType& default_extension, 183 gfx::NativeWindow owning_window, 184 void* params) OVERRIDE; 185 186 private: 187 virtual ~SelectFileDialogImpl(); 188 189 // A struct for holding all the state necessary for displaying a Save dialog. 190 struct ExecuteSelectParams { 191 ExecuteSelectParams(Type type, 192 const std::wstring& title, 193 const base::FilePath& default_path, 194 const FileTypeInfo* file_types, 195 int file_type_index, 196 const std::wstring& default_extension, 197 RunState run_state, 198 HWND owner, 199 void* params) 200 : type(type), 201 title(title), 202 default_path(default_path), 203 file_type_index(file_type_index), 204 default_extension(default_extension), 205 run_state(run_state), 206 ui_proxy(base::MessageLoopForUI::current()->message_loop_proxy()), 207 owner(owner), 208 params(params) { 209 if (file_types) 210 this->file_types = *file_types; 211 } 212 SelectFileDialog::Type type; 213 std::wstring title; 214 base::FilePath default_path; 215 FileTypeInfo file_types; 216 int file_type_index; 217 std::wstring default_extension; 218 RunState run_state; 219 scoped_refptr<base::MessageLoopProxy> ui_proxy; 220 HWND owner; 221 void* params; 222 }; 223 224 // Shows the file selection dialog modal to |owner| and calls the result 225 // back on the ui thread. Run on the dialog thread. 226 void ExecuteSelectFile(const ExecuteSelectParams& params); 227 228 // Prompt the user for location to save a file. 229 // Callers should provide the filter string, and also a filter index. 230 // The parameter |index| indicates the initial index of filter description 231 // and filter pattern for the dialog box. If |index| is zero or greater than 232 // the number of total filter types, the system uses the first filter in the 233 // |filter| buffer. |index| is used to specify the initial selected extension, 234 // and when done contains the extension the user chose. The parameter 235 // |final_name| returns the file name which contains the drive designator, 236 // path, file name, and extension of the user selected file name. |def_ext| is 237 // the default extension to give to the file if the user did not enter an 238 // extension. If |ignore_suggested_ext| is true, any file extension contained 239 // in |suggested_name| will not be used to generate the file name. This is 240 // useful in the case of saving web pages, where we know the extension type 241 // already and where |suggested_name| may contain a '.' character as a valid 242 // part of the name, thus confusing our extension detection code. 243 bool SaveFileAsWithFilter(HWND owner, 244 const std::wstring& suggested_name, 245 const std::wstring& filter, 246 const std::wstring& def_ext, 247 bool ignore_suggested_ext, 248 unsigned* index, 249 std::wstring* final_name); 250 251 // Notifies the listener that a folder was chosen. Run on the ui thread. 252 void FileSelected(const base::FilePath& path, int index, 253 void* params, RunState run_state); 254 255 // Notifies listener that multiple files were chosen. Run on the ui thread. 256 void MultiFilesSelected(const std::vector<base::FilePath>& paths, 257 void* params, 258 RunState run_state); 259 260 // Notifies the listener that no file was chosen (the action was canceled). 261 // Run on the ui thread. 262 void FileNotSelected(void* params, RunState run_state); 263 264 // Runs a Folder selection dialog box, passes back the selected folder in 265 // |path| and returns true if the user clicks OK. If the user cancels the 266 // dialog box the value in |path| is not modified and returns false. |title| 267 // is the user-supplied title text to show for the dialog box. Run on the 268 // dialog thread. 269 bool RunSelectFolderDialog(const std::wstring& title, 270 HWND owner, 271 base::FilePath* path); 272 273 // Runs an Open file dialog box, with similar semantics for input paramaters 274 // as RunSelectFolderDialog. 275 bool RunOpenFileDialog(const std::wstring& title, 276 const std::wstring& filters, 277 HWND owner, 278 base::FilePath* path); 279 280 // Runs an Open file dialog box that supports multi-select, with similar 281 // semantics for input paramaters as RunOpenFileDialog. 282 bool RunOpenMultiFileDialog(const std::wstring& title, 283 const std::wstring& filter, 284 HWND owner, 285 std::vector<base::FilePath>* paths); 286 287 // The callback function for when the select folder dialog is opened. 288 static int CALLBACK BrowseCallbackProc(HWND window, UINT message, 289 LPARAM parameter, 290 LPARAM data); 291 292 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE; 293 294 // Returns the filter to be used while displaying the open/save file dialog. 295 // This is computed from the extensions for the file types being opened. 296 // |file_types| can be NULL in which case the returned filter will be empty. 297 base::string16 GetFilterForFileTypes(const FileTypeInfo* file_types); 298 299 bool has_multiple_file_type_choices_; 300 base::Callback<bool(OPENFILENAME*)> get_open_file_name_impl_; 301 base::Callback<bool(OPENFILENAME*)> get_save_file_name_impl_; 302 303 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImpl); 304 }; 305 306 SelectFileDialogImpl::SelectFileDialogImpl( 307 Listener* listener, 308 ui::SelectFilePolicy* policy, 309 const base::Callback<bool(OPENFILENAME*)>& get_open_file_name_impl, 310 const base::Callback<bool(OPENFILENAME*)>& get_save_file_name_impl) 311 : SelectFileDialog(listener, policy), 312 BaseShellDialogImpl(), 313 has_multiple_file_type_choices_(false), 314 get_open_file_name_impl_(get_open_file_name_impl), 315 get_save_file_name_impl_(get_save_file_name_impl) { 316 } 317 318 SelectFileDialogImpl::~SelectFileDialogImpl() { 319 } 320 321 void SelectFileDialogImpl::SelectFileImpl( 322 Type type, 323 const base::string16& title, 324 const base::FilePath& default_path, 325 const FileTypeInfo* file_types, 326 int file_type_index, 327 const base::FilePath::StringType& default_extension, 328 gfx::NativeWindow owning_window, 329 void* params) { 330 has_multiple_file_type_choices_ = 331 file_types ? file_types->extensions.size() > 1 : true; 332 // If the owning_window passed in is in metro then we need to forward the 333 // file open/save operations to metro. 334 if (GetShellDialogsDelegate() && 335 GetShellDialogsDelegate()->IsWindowInMetro(owning_window)) { 336 if (type == SELECT_SAVEAS_FILE) { 337 win8::MetroViewerProcessHost::HandleSaveFile( 338 title, 339 default_path, 340 GetFilterForFileTypes(file_types), 341 file_type_index, 342 default_extension, 343 base::Bind(&ui::SelectFileDialog::Listener::FileSelected, 344 base::Unretained(listener_)), 345 base::Bind(&ui::SelectFileDialog::Listener::FileSelectionCanceled, 346 base::Unretained(listener_))); 347 return; 348 } else if (type == SELECT_OPEN_FILE) { 349 win8::MetroViewerProcessHost::HandleOpenFile( 350 title, 351 default_path, 352 GetFilterForFileTypes(file_types), 353 base::Bind(&ui::SelectFileDialog::Listener::FileSelected, 354 base::Unretained(listener_)), 355 base::Bind(&ui::SelectFileDialog::Listener::FileSelectionCanceled, 356 base::Unretained(listener_))); 357 return; 358 } else if (type == SELECT_OPEN_MULTI_FILE) { 359 win8::MetroViewerProcessHost::HandleOpenMultipleFiles( 360 title, 361 default_path, 362 GetFilterForFileTypes(file_types), 363 base::Bind(&ui::SelectFileDialog::Listener::MultiFilesSelected, 364 base::Unretained(listener_)), 365 base::Bind(&ui::SelectFileDialog::Listener::FileSelectionCanceled, 366 base::Unretained(listener_))); 367 return; 368 } else if (type == SELECT_FOLDER || type == SELECT_UPLOAD_FOLDER) { 369 base::string16 title_string = title; 370 if (type == SELECT_UPLOAD_FOLDER && title_string.empty()) { 371 // If it's for uploading don't use default dialog title to 372 // make sure we clearly tell it's for uploading. 373 title_string = l10n_util::GetStringUTF16( 374 IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE); 375 } 376 win8::MetroViewerProcessHost::HandleSelectFolder( 377 title_string, 378 base::Bind(&ui::SelectFileDialog::Listener::FileSelected, 379 base::Unretained(listener_)), 380 base::Bind(&ui::SelectFileDialog::Listener::FileSelectionCanceled, 381 base::Unretained(listener_))); 382 return; 383 } 384 } 385 HWND owner = owning_window && owning_window->GetRootWindow() 386 ? owning_window->GetHost()->GetAcceleratedWidget() : NULL; 387 388 ExecuteSelectParams execute_params(type, title, 389 default_path, file_types, file_type_index, 390 default_extension, BeginRun(owner), 391 owner, params); 392 execute_params.run_state.dialog_thread->message_loop()->PostTask( 393 FROM_HERE, 394 base::Bind(&SelectFileDialogImpl::ExecuteSelectFile, this, 395 execute_params)); 396 } 397 398 bool SelectFileDialogImpl::HasMultipleFileTypeChoicesImpl() { 399 return has_multiple_file_type_choices_; 400 } 401 402 bool SelectFileDialogImpl::IsRunning(gfx::NativeWindow owning_window) const { 403 if (!owning_window->GetRootWindow()) 404 return false; 405 HWND owner = owning_window->GetHost()->GetAcceleratedWidget(); 406 return listener_ && IsRunningDialogForOwner(owner); 407 } 408 409 void SelectFileDialogImpl::ListenerDestroyed() { 410 // Our associated listener has gone away, so we shouldn't call back to it if 411 // our worker thread returns after the listener is dead. 412 listener_ = NULL; 413 } 414 415 void SelectFileDialogImpl::ExecuteSelectFile( 416 const ExecuteSelectParams& params) { 417 base::string16 filter = GetFilterForFileTypes(¶ms.file_types); 418 419 base::FilePath path = params.default_path; 420 bool success = false; 421 unsigned filter_index = params.file_type_index; 422 if (params.type == SELECT_FOLDER || params.type == SELECT_UPLOAD_FOLDER) { 423 std::wstring title = params.title; 424 if (title.empty() && params.type == SELECT_UPLOAD_FOLDER) { 425 // If it's for uploading don't use default dialog title to 426 // make sure we clearly tell it's for uploading. 427 title = l10n_util::GetStringUTF16(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE); 428 } 429 success = RunSelectFolderDialog(title, 430 params.run_state.owner, 431 &path); 432 } else if (params.type == SELECT_SAVEAS_FILE) { 433 std::wstring path_as_wstring = path.value(); 434 success = SaveFileAsWithFilter(params.run_state.owner, 435 params.default_path.value(), filter, 436 params.default_extension, false, &filter_index, &path_as_wstring); 437 if (success) 438 path = base::FilePath(path_as_wstring); 439 DisableOwner(params.run_state.owner); 440 } else if (params.type == SELECT_OPEN_FILE) { 441 success = RunOpenFileDialog(params.title, filter, 442 params.run_state.owner, &path); 443 } else if (params.type == SELECT_OPEN_MULTI_FILE) { 444 std::vector<base::FilePath> paths; 445 if (RunOpenMultiFileDialog(params.title, filter, 446 params.run_state.owner, &paths)) { 447 params.ui_proxy->PostTask( 448 FROM_HERE, 449 base::Bind(&SelectFileDialogImpl::MultiFilesSelected, this, paths, 450 params.params, params.run_state)); 451 return; 452 } 453 } 454 455 if (success) { 456 params.ui_proxy->PostTask( 457 FROM_HERE, 458 base::Bind(&SelectFileDialogImpl::FileSelected, this, path, 459 filter_index, params.params, params.run_state)); 460 } else { 461 params.ui_proxy->PostTask( 462 FROM_HERE, 463 base::Bind(&SelectFileDialogImpl::FileNotSelected, this, params.params, 464 params.run_state)); 465 } 466 } 467 468 bool SelectFileDialogImpl::SaveFileAsWithFilter( 469 HWND owner, 470 const std::wstring& suggested_name, 471 const std::wstring& filter, 472 const std::wstring& def_ext, 473 bool ignore_suggested_ext, 474 unsigned* index, 475 std::wstring* final_name) { 476 DCHECK(final_name); 477 // Having an empty filter makes for a bad user experience. We should always 478 // specify a filter when saving. 479 DCHECK(!filter.empty()); 480 481 ui::win::OpenFileName save_as(owner, 482 OFN_OVERWRITEPROMPT | OFN_EXPLORER | 483 OFN_ENABLESIZING | OFN_NOCHANGEDIR | 484 OFN_PATHMUSTEXIST); 485 486 const base::FilePath suggested_path = base::FilePath(suggested_name); 487 if (!suggested_name.empty()) { 488 base::FilePath suggested_file_name; 489 base::FilePath suggested_directory; 490 if (IsDirectory(suggested_path)) { 491 suggested_directory = suggested_path; 492 } else { 493 suggested_directory = suggested_path.DirName(); 494 suggested_file_name = suggested_path.BaseName(); 495 // If the suggested_name is a root directory, file_part will be '\', and 496 // the call to GetSaveFileName below will fail. 497 if (suggested_file_name.value() == L"\\") 498 suggested_file_name.clear(); 499 } 500 save_as.SetInitialSelection(suggested_directory, suggested_file_name); 501 } 502 503 save_as.GetOPENFILENAME()->lpstrFilter = 504 filter.empty() ? NULL : filter.c_str(); 505 save_as.GetOPENFILENAME()->nFilterIndex = *index; 506 save_as.GetOPENFILENAME()->lpstrDefExt = &def_ext[0]; 507 save_as.MaybeInstallWindowPositionHookForSaveAsOnXP(); 508 509 if (!get_save_file_name_impl_.Run(save_as.GetOPENFILENAME())) 510 return false; 511 512 // Return the user's choice. 513 final_name->assign(save_as.GetOPENFILENAME()->lpstrFile); 514 *index = save_as.GetOPENFILENAME()->nFilterIndex; 515 516 // Figure out what filter got selected. The filter index is 1-based. 517 std::wstring filter_selected; 518 if (*index > 0) { 519 std::vector<Tuple2<base::string16, base::string16> > filters = 520 ui::win::OpenFileName::GetFilters(save_as.GetOPENFILENAME()); 521 if (*index > filters.size()) 522 NOTREACHED() << "Invalid filter index."; 523 else 524 filter_selected = filters[*index - 1].b; 525 } 526 527 // Get the extension that was suggested to the user (when the Save As dialog 528 // was opened). For saving web pages, we skip this step since there may be 529 // 'extension characters' in the title of the web page. 530 std::wstring suggested_ext; 531 if (!ignore_suggested_ext) 532 suggested_ext = GetExtensionWithoutLeadingDot(suggested_path.Extension()); 533 534 // If we can't get the extension from the suggested_name, we use the default 535 // extension passed in. This is to cover cases like when saving a web page, 536 // where we get passed in a name without an extension and a default extension 537 // along with it. 538 if (suggested_ext.empty()) 539 suggested_ext = def_ext; 540 541 *final_name = 542 ui::AppendExtensionIfNeeded(*final_name, filter_selected, suggested_ext); 543 return true; 544 } 545 546 void SelectFileDialogImpl::FileSelected(const base::FilePath& selected_folder, 547 int index, 548 void* params, 549 RunState run_state) { 550 if (listener_) 551 listener_->FileSelected(selected_folder, index, params); 552 EndRun(run_state); 553 } 554 555 void SelectFileDialogImpl::MultiFilesSelected( 556 const std::vector<base::FilePath>& selected_files, 557 void* params, 558 RunState run_state) { 559 if (listener_) 560 listener_->MultiFilesSelected(selected_files, params); 561 EndRun(run_state); 562 } 563 564 void SelectFileDialogImpl::FileNotSelected(void* params, RunState run_state) { 565 if (listener_) 566 listener_->FileSelectionCanceled(params); 567 EndRun(run_state); 568 } 569 570 int CALLBACK SelectFileDialogImpl::BrowseCallbackProc(HWND window, 571 UINT message, 572 LPARAM parameter, 573 LPARAM data) { 574 if (message == BFFM_INITIALIZED) { 575 // WParam is TRUE since passing a path. 576 // data lParam member of the BROWSEINFO structure. 577 SendMessage(window, BFFM_SETSELECTION, TRUE, (LPARAM)data); 578 } 579 return 0; 580 } 581 582 bool SelectFileDialogImpl::RunSelectFolderDialog(const std::wstring& title, 583 HWND owner, 584 base::FilePath* path) { 585 DCHECK(path); 586 587 wchar_t dir_buffer[MAX_PATH + 1]; 588 589 bool result = false; 590 BROWSEINFO browse_info = {0}; 591 browse_info.hwndOwner = owner; 592 browse_info.lpszTitle = title.c_str(); 593 browse_info.pszDisplayName = dir_buffer; 594 browse_info.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; 595 596 if (path->value().length()) { 597 // Highlight the current value. 598 browse_info.lParam = (LPARAM)path->value().c_str(); 599 browse_info.lpfn = &BrowseCallbackProc; 600 } 601 602 LPITEMIDLIST list = SHBrowseForFolder(&browse_info); 603 DisableOwner(owner); 604 if (list) { 605 STRRET out_dir_buffer; 606 ZeroMemory(&out_dir_buffer, sizeof(out_dir_buffer)); 607 out_dir_buffer.uType = STRRET_WSTR; 608 base::win::ScopedComPtr<IShellFolder> shell_folder; 609 if (SHGetDesktopFolder(shell_folder.Receive()) == NOERROR) { 610 HRESULT hr = shell_folder->GetDisplayNameOf(list, SHGDN_FORPARSING, 611 &out_dir_buffer); 612 if (SUCCEEDED(hr) && out_dir_buffer.uType == STRRET_WSTR) { 613 *path = base::FilePath(out_dir_buffer.pOleStr); 614 CoTaskMemFree(out_dir_buffer.pOleStr); 615 result = true; 616 } else { 617 // Use old way if we don't get what we want. 618 wchar_t old_out_dir_buffer[MAX_PATH + 1]; 619 if (SHGetPathFromIDList(list, old_out_dir_buffer)) { 620 *path = base::FilePath(old_out_dir_buffer); 621 result = true; 622 } 623 } 624 625 // According to MSDN, win2000 will not resolve shortcuts, so we do it 626 // ourself. 627 base::win::ResolveShortcut(*path, path, NULL); 628 } 629 CoTaskMemFree(list); 630 } 631 return result; 632 } 633 634 bool SelectFileDialogImpl::RunOpenFileDialog( 635 const std::wstring& title, 636 const std::wstring& filter, 637 HWND owner, 638 base::FilePath* path) { 639 // We use OFN_NOCHANGEDIR so that the user can rename or delete the directory 640 // without having to close Chrome first. 641 ui::win::OpenFileName ofn(owner, OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR); 642 if (!path->empty()) { 643 if (IsDirectory(*path)) 644 ofn.SetInitialSelection(*path, base::FilePath()); 645 else 646 ofn.SetInitialSelection(path->DirName(), path->BaseName()); 647 } 648 649 if (!filter.empty()) 650 ofn.GetOPENFILENAME()->lpstrFilter = filter.c_str(); 651 652 bool success = get_open_file_name_impl_.Run(ofn.GetOPENFILENAME()); 653 DisableOwner(owner); 654 if (success) 655 *path = ofn.GetSingleResult(); 656 return success; 657 } 658 659 bool SelectFileDialogImpl::RunOpenMultiFileDialog( 660 const std::wstring& title, 661 const std::wstring& filter, 662 HWND owner, 663 std::vector<base::FilePath>* paths) { 664 // We use OFN_NOCHANGEDIR so that the user can rename or delete the directory 665 // without having to close Chrome first. 666 ui::win::OpenFileName ofn(owner, 667 OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | 668 OFN_EXPLORER | OFN_HIDEREADONLY | 669 OFN_ALLOWMULTISELECT | OFN_NOCHANGEDIR); 670 671 if (!filter.empty()) 672 ofn.GetOPENFILENAME()->lpstrFilter = filter.c_str(); 673 674 base::FilePath directory; 675 std::vector<base::FilePath> filenames; 676 677 if (get_open_file_name_impl_.Run(ofn.GetOPENFILENAME())) 678 ofn.GetResult(&directory, &filenames); 679 680 DisableOwner(owner); 681 682 for (std::vector<base::FilePath>::iterator it = filenames.begin(); 683 it != filenames.end(); 684 ++it) { 685 paths->push_back(directory.Append(*it)); 686 } 687 688 return !paths->empty(); 689 } 690 691 base::string16 SelectFileDialogImpl::GetFilterForFileTypes( 692 const FileTypeInfo* file_types) { 693 if (!file_types) 694 return base::string16(); 695 696 std::vector<base::string16> exts; 697 for (size_t i = 0; i < file_types->extensions.size(); ++i) { 698 const std::vector<base::string16>& inner_exts = file_types->extensions[i]; 699 base::string16 ext_string; 700 for (size_t j = 0; j < inner_exts.size(); ++j) { 701 if (!ext_string.empty()) 702 ext_string.push_back(L';'); 703 ext_string.append(L"*."); 704 ext_string.append(inner_exts[j]); 705 } 706 exts.push_back(ext_string); 707 } 708 return FormatFilterForExtensions( 709 exts, 710 file_types->extension_description_overrides, 711 file_types->include_all_files); 712 } 713 714 } // namespace 715 716 namespace ui { 717 718 // This function takes the output of a SaveAs dialog: a filename, a filter and 719 // the extension originally suggested to the user (shown in the dialog box) and 720 // returns back the filename with the appropriate extension tacked on. If the 721 // user requests an unknown extension and is not using the 'All files' filter, 722 // the suggested extension will be appended, otherwise we will leave the 723 // filename unmodified. |filename| should contain the filename selected in the 724 // SaveAs dialog box and may include the path, |filter_selected| should be 725 // '*.something', for example '*.*' or it can be blank (which is treated as 726 // *.*). |suggested_ext| should contain the extension without the dot (.) in 727 // front, for example 'jpg'. 728 std::wstring AppendExtensionIfNeeded( 729 const std::wstring& filename, 730 const std::wstring& filter_selected, 731 const std::wstring& suggested_ext) { 732 DCHECK(!filename.empty()); 733 std::wstring return_value = filename; 734 735 // If we wanted a specific extension, but the user's filename deleted it or 736 // changed it to something that the system doesn't understand, re-append. 737 // Careful: Checking net::GetMimeTypeFromExtension() will only find 738 // extensions with a known MIME type, which many "known" extensions on Windows 739 // don't have. So we check directly for the "known extension" registry key. 740 std::wstring file_extension( 741 GetExtensionWithoutLeadingDot(base::FilePath(filename).Extension())); 742 std::wstring key(L"." + file_extension); 743 if (!(filter_selected.empty() || filter_selected == L"*.*") && 744 !base::win::RegKey(HKEY_CLASSES_ROOT, key.c_str(), KEY_READ).Valid() && 745 file_extension != suggested_ext) { 746 if (return_value[return_value.length() - 1] != L'.') 747 return_value.append(L"."); 748 return_value.append(suggested_ext); 749 } 750 751 // Strip any trailing dots, which Windows doesn't allow. 752 size_t index = return_value.find_last_not_of(L'.'); 753 if (index < return_value.size() - 1) 754 return_value.resize(index + 1); 755 756 return return_value; 757 } 758 759 SelectFileDialog* CreateWinSelectFileDialog( 760 SelectFileDialog::Listener* listener, 761 SelectFilePolicy* policy, 762 const base::Callback<bool(OPENFILENAME* ofn)>& get_open_file_name_impl, 763 const base::Callback<bool(OPENFILENAME* ofn)>& get_save_file_name_impl) { 764 return new SelectFileDialogImpl( 765 listener, policy, get_open_file_name_impl, get_save_file_name_impl); 766 } 767 768 SelectFileDialog* CreateDefaultWinSelectFileDialog( 769 SelectFileDialog::Listener* listener, 770 SelectFilePolicy* policy) { 771 return CreateWinSelectFileDialog(listener, 772 policy, 773 base::Bind(&CallBuiltinGetOpenFileName), 774 base::Bind(&CallBuiltinGetSaveFileName)); 775 } 776 777 } // namespace ui 778