| OLD | NEW |
| (Empty) |
| 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 "chrome/browser/ui/select_file_dialog.h" | |
| 6 | |
| 7 #include <windows.h> | |
| 8 #include <commdlg.h> | |
| 9 #include <shlobj.h> | |
| 10 | |
| 11 #include <algorithm> | |
| 12 #include <set> | |
| 13 | |
| 14 #include "base/bind.h" | |
| 15 #include "base/file_path.h" | |
| 16 #include "base/file_util.h" | |
| 17 #include "base/i18n/case_conversion.h" | |
| 18 #include "base/message_loop.h" | |
| 19 #include "base/string_split.h" | |
| 20 #include "base/threading/thread.h" | |
| 21 #include "base/utf_string_conversions.h" | |
| 22 #include "base/win/metro.h" | |
| 23 #include "base/win/registry.h" | |
| 24 #include "base/win/scoped_comptr.h" | |
| 25 #include "base/win/windows_version.h" | |
| 26 #include "chrome/browser/ui/views/base_shell_dialog_win.h" | |
| 27 #include "content/public/browser/browser_thread.h" | |
| 28 #include "grit/generated_resources.h" | |
| 29 #include "grit/ui_strings.h" | |
| 30 #include "ui/base/l10n/l10n_util.h" | |
| 31 | |
| 32 using content::BrowserThread; | |
| 33 | |
| 34 namespace { | |
| 35 | |
| 36 // Given |extension|, if it's not empty, then remove the leading dot. | |
| 37 std::wstring GetExtensionWithoutLeadingDot(const std::wstring& extension) { | |
| 38 DCHECK(extension.empty() || extension[0] == L'.'); | |
| 39 return extension.empty() ? extension : extension.substr(1); | |
| 40 } | |
| 41 | |
| 42 // Diverts to a metro-specific implementation as appropriate. | |
| 43 bool CallGetOpenFileName(OPENFILENAME* ofn) { | |
| 44 HMODULE metro_module = base::win::GetMetroModule(); | |
| 45 if (metro_module != NULL) { | |
| 46 typedef BOOL (*MetroGetOpenFileName)(OPENFILENAME*); | |
| 47 MetroGetOpenFileName metro_get_open_file_name = | |
| 48 reinterpret_cast<MetroGetOpenFileName>( | |
| 49 ::GetProcAddress(metro_module, "MetroGetOpenFileName")); | |
| 50 if (metro_get_open_file_name == NULL) { | |
| 51 NOTREACHED(); | |
| 52 return false; | |
| 53 } | |
| 54 | |
| 55 return metro_get_open_file_name(ofn) == TRUE; | |
| 56 } else { | |
| 57 return GetOpenFileName(ofn) == TRUE; | |
| 58 } | |
| 59 } | |
| 60 | |
| 61 // Diverts to a metro-specific implementation as appropriate. | |
| 62 bool CallGetSaveFileName(OPENFILENAME* ofn) { | |
| 63 HMODULE metro_module = base::win::GetMetroModule(); | |
| 64 if (metro_module != NULL) { | |
| 65 typedef BOOL (*MetroGetSaveFileName)(OPENFILENAME*); | |
| 66 MetroGetSaveFileName metro_get_save_file_name = | |
| 67 reinterpret_cast<MetroGetSaveFileName>( | |
| 68 ::GetProcAddress(metro_module, "MetroGetSaveFileName")); | |
| 69 if (metro_get_save_file_name == NULL) { | |
| 70 NOTREACHED(); | |
| 71 return false; | |
| 72 } | |
| 73 | |
| 74 return metro_get_save_file_name(ofn) == TRUE; | |
| 75 } else { | |
| 76 return GetSaveFileName(ofn) == TRUE; | |
| 77 } | |
| 78 } | |
| 79 | |
| 80 } // namespace | |
| 81 | |
| 82 // This function takes the output of a SaveAs dialog: a filename, a filter and | |
| 83 // the extension originally suggested to the user (shown in the dialog box) and | |
| 84 // returns back the filename with the appropriate extension tacked on. If the | |
| 85 // user requests an unknown extension and is not using the 'All files' filter, | |
| 86 // the suggested extension will be appended, otherwise we will leave the | |
| 87 // filename unmodified. |filename| should contain the filename selected in the | |
| 88 // SaveAs dialog box and may include the path, |filter_selected| should be | |
| 89 // '*.something', for example '*.*' or it can be blank (which is treated as | |
| 90 // *.*). |suggested_ext| should contain the extension without the dot (.) in | |
| 91 // front, for example 'jpg'. | |
| 92 std::wstring AppendExtensionIfNeeded(const std::wstring& filename, | |
| 93 const std::wstring& filter_selected, | |
| 94 const std::wstring& suggested_ext) { | |
| 95 DCHECK(!filename.empty()); | |
| 96 std::wstring return_value = filename; | |
| 97 | |
| 98 // If we wanted a specific extension, but the user's filename deleted it or | |
| 99 // changed it to something that the system doesn't understand, re-append. | |
| 100 // Careful: Checking net::GetMimeTypeFromExtension() will only find | |
| 101 // extensions with a known MIME type, which many "known" extensions on Windows | |
| 102 // don't have. So we check directly for the "known extension" registry key. | |
| 103 std::wstring file_extension( | |
| 104 GetExtensionWithoutLeadingDot(FilePath(filename).Extension())); | |
| 105 std::wstring key(L"." + file_extension); | |
| 106 if (!(filter_selected.empty() || filter_selected == L"*.*") && | |
| 107 !base::win::RegKey(HKEY_CLASSES_ROOT, key.c_str(), KEY_READ).Valid() && | |
| 108 file_extension != suggested_ext) { | |
| 109 if (return_value[return_value.length() - 1] != L'.') | |
| 110 return_value.append(L"."); | |
| 111 return_value.append(suggested_ext); | |
| 112 } | |
| 113 | |
| 114 // Strip any trailing dots, which Windows doesn't allow. | |
| 115 size_t index = return_value.find_last_not_of(L'.'); | |
| 116 if (index < return_value.size() - 1) | |
| 117 return_value.resize(index + 1); | |
| 118 | |
| 119 return return_value; | |
| 120 } | |
| 121 | |
| 122 namespace { | |
| 123 | |
| 124 // Get the file type description from the registry. This will be "Text Document" | |
| 125 // for .txt files, "JPEG Image" for .jpg files, etc. If the registry doesn't | |
| 126 // have an entry for the file type, we return false, true if the description was | |
| 127 // found. 'file_ext' must be in form ".txt". | |
| 128 static bool GetRegistryDescriptionFromExtension(const std::wstring& file_ext, | |
| 129 std::wstring* reg_description) { | |
| 130 DCHECK(reg_description); | |
| 131 base::win::RegKey reg_ext(HKEY_CLASSES_ROOT, file_ext.c_str(), KEY_READ); | |
| 132 std::wstring reg_app; | |
| 133 if (reg_ext.ReadValue(NULL, ®_app) == ERROR_SUCCESS && !reg_app.empty()) { | |
| 134 base::win::RegKey reg_link(HKEY_CLASSES_ROOT, reg_app.c_str(), KEY_READ); | |
| 135 if (reg_link.ReadValue(NULL, reg_description) == ERROR_SUCCESS) | |
| 136 return true; | |
| 137 } | |
| 138 return false; | |
| 139 } | |
| 140 | |
| 141 // Set up a filter for a Save/Open dialog, which will consist of |file_ext| file | |
| 142 // extensions (internally separated by semicolons), |ext_desc| as the text | |
| 143 // descriptions of the |file_ext| types (optional), and (optionally) the default | |
| 144 // 'All Files' view. The purpose of the filter is to show only files of a | |
| 145 // particular type in a Windows Save/Open dialog box. The resulting filter is | |
| 146 // returned. The filters created here are: | |
| 147 // 1. only files that have 'file_ext' as their extension | |
| 148 // 2. all files (only added if 'include_all_files' is true) | |
| 149 // Example: | |
| 150 // file_ext: { "*.txt", "*.htm;*.html" } | |
| 151 // ext_desc: { "Text Document" } | |
| 152 // returned: "Text Document\0*.txt\0HTML Document\0*.htm;*.html\0" | |
| 153 // "All Files\0*.*\0\0" (in one big string) | |
| 154 // If a description is not provided for a file extension, it will be retrieved | |
| 155 // from the registry. If the file extension does not exist in the registry, it | |
| 156 // will be omitted from the filter, as it is likely a bogus extension. | |
| 157 std::wstring FormatFilterForExtensions( | |
| 158 const std::vector<std::wstring>& file_ext, | |
| 159 const std::vector<std::wstring>& ext_desc, | |
| 160 bool include_all_files) { | |
| 161 const std::wstring all_ext = L"*.*"; | |
| 162 const std::wstring all_desc = | |
| 163 l10n_util::GetStringUTF16(IDS_APP_SAVEAS_ALL_FILES); | |
| 164 | |
| 165 DCHECK(file_ext.size() >= ext_desc.size()); | |
| 166 | |
| 167 std::wstring result; | |
| 168 | |
| 169 for (size_t i = 0; i < file_ext.size(); ++i) { | |
| 170 std::wstring ext = file_ext[i]; | |
| 171 std::wstring desc; | |
| 172 if (i < ext_desc.size()) | |
| 173 desc = ext_desc[i]; | |
| 174 | |
| 175 if (ext.empty()) { | |
| 176 // Force something reasonable to appear in the dialog box if there is no | |
| 177 // extension provided. | |
| 178 include_all_files = true; | |
| 179 continue; | |
| 180 } | |
| 181 | |
| 182 if (desc.empty()) { | |
| 183 DCHECK(ext.find(L'.') != std::wstring::npos); | |
| 184 std::wstring first_extension = ext.substr(ext.find(L'.')); | |
| 185 size_t first_separator_index = first_extension.find(L';'); | |
| 186 if (first_separator_index != std::wstring::npos) | |
| 187 first_extension = first_extension.substr(0, first_separator_index); | |
| 188 | |
| 189 // Find the extension name without the preceeding '.' character. | |
| 190 std::wstring ext_name = first_extension; | |
| 191 size_t ext_index = ext_name.find_first_not_of(L'.'); | |
| 192 if (ext_index != std::wstring::npos) | |
| 193 ext_name = ext_name.substr(ext_index); | |
| 194 | |
| 195 if (!GetRegistryDescriptionFromExtension(first_extension, &desc)) { | |
| 196 // The extension doesn't exist in the registry. Create a description | |
| 197 // based on the unknown extension type (i.e. if the extension is .qqq, | |
| 198 // the we create a description "QQQ File (.qqq)"). | |
| 199 include_all_files = true; | |
| 200 desc = l10n_util::GetStringFUTF16( | |
| 201 IDS_APP_SAVEAS_EXTENSION_FORMAT, | |
| 202 base::i18n::ToUpper(WideToUTF16(ext_name)), | |
| 203 ext_name); | |
| 204 } | |
| 205 if (desc.empty()) | |
| 206 desc = L"*." + ext_name; | |
| 207 } | |
| 208 | |
| 209 result.append(desc.c_str(), desc.size() + 1); // Append NULL too. | |
| 210 result.append(ext.c_str(), ext.size() + 1); | |
| 211 } | |
| 212 | |
| 213 if (include_all_files) { | |
| 214 result.append(all_desc.c_str(), all_desc.size() + 1); | |
| 215 result.append(all_ext.c_str(), all_ext.size() + 1); | |
| 216 } | |
| 217 | |
| 218 result.append(1, '\0'); // Double NULL required. | |
| 219 return result; | |
| 220 } | |
| 221 | |
| 222 // Enforce visible dialog box. | |
| 223 UINT_PTR CALLBACK SaveAsDialogHook(HWND dialog, UINT message, | |
| 224 WPARAM wparam, LPARAM lparam) { | |
| 225 static const UINT kPrivateMessage = 0x2F3F; | |
| 226 switch (message) { | |
| 227 case WM_INITDIALOG: { | |
| 228 // Do nothing here. Just post a message to defer actual processing. | |
| 229 PostMessage(dialog, kPrivateMessage, 0, 0); | |
| 230 return TRUE; | |
| 231 } | |
| 232 case kPrivateMessage: { | |
| 233 // The dialog box is the parent of the current handle. | |
| 234 HWND real_dialog = GetParent(dialog); | |
| 235 | |
| 236 // Retrieve the final size. | |
| 237 RECT dialog_rect; | |
| 238 GetWindowRect(real_dialog, &dialog_rect); | |
| 239 | |
| 240 // Verify that the upper left corner is visible. | |
| 241 POINT point = { dialog_rect.left, dialog_rect.top }; | |
| 242 HMONITOR monitor1 = MonitorFromPoint(point, MONITOR_DEFAULTTONULL); | |
| 243 point.x = dialog_rect.right; | |
| 244 point.y = dialog_rect.bottom; | |
| 245 | |
| 246 // Verify that the lower right corner is visible. | |
| 247 HMONITOR monitor2 = MonitorFromPoint(point, MONITOR_DEFAULTTONULL); | |
| 248 if (monitor1 && monitor2) | |
| 249 return 0; | |
| 250 | |
| 251 // Some part of the dialog box is not visible, fix it by moving is to the | |
| 252 // client rect position of the browser window. | |
| 253 HWND parent_window = GetParent(real_dialog); | |
| 254 if (!parent_window) | |
| 255 return 0; | |
| 256 WINDOWINFO parent_info; | |
| 257 parent_info.cbSize = sizeof(WINDOWINFO); | |
| 258 GetWindowInfo(parent_window, &parent_info); | |
| 259 SetWindowPos(real_dialog, NULL, | |
| 260 parent_info.rcClient.left, | |
| 261 parent_info.rcClient.top, | |
| 262 0, 0, // Size. | |
| 263 SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | | |
| 264 SWP_NOZORDER); | |
| 265 | |
| 266 return 0; | |
| 267 } | |
| 268 } | |
| 269 return 0; | |
| 270 } | |
| 271 | |
| 272 // Prompt the user for location to save a file. | |
| 273 // Callers should provide the filter string, and also a filter index. | |
| 274 // The parameter |index| indicates the initial index of filter description | |
| 275 // and filter pattern for the dialog box. If |index| is zero or greater than | |
| 276 // the number of total filter types, the system uses the first filter in the | |
| 277 // |filter| buffer. |index| is used to specify the initial selected extension, | |
| 278 // and when done contains the extension the user chose. The parameter | |
| 279 // |final_name| returns the file name which contains the drive designator, | |
| 280 // path, file name, and extension of the user selected file name. |def_ext| is | |
| 281 // the default extension to give to the file if the user did not enter an | |
| 282 // extension. If |ignore_suggested_ext| is true, any file extension contained in | |
| 283 // |suggested_name| will not be used to generate the file name. This is useful | |
| 284 // in the case of saving web pages, where we know the extension type already and | |
| 285 // where |suggested_name| may contain a '.' character as a valid part of the | |
| 286 // name, thus confusing our extension detection code. | |
| 287 bool SaveFileAsWithFilter(HWND owner, | |
| 288 const std::wstring& suggested_name, | |
| 289 const std::wstring& filter, | |
| 290 const std::wstring& def_ext, | |
| 291 bool ignore_suggested_ext, | |
| 292 unsigned* index, | |
| 293 std::wstring* final_name) { | |
| 294 DCHECK(final_name); | |
| 295 // Having an empty filter makes for a bad user experience. We should always | |
| 296 // specify a filter when saving. | |
| 297 DCHECK(!filter.empty()); | |
| 298 const FilePath suggested_path(suggested_name); | |
| 299 std::wstring file_part = suggested_path.BaseName().value(); | |
| 300 // If the suggested_name is a root directory, file_part will be '\', and the | |
| 301 // call to GetSaveFileName below will fail. | |
| 302 if (file_part.size() == 1 && file_part[0] == L'\\') | |
| 303 file_part.clear(); | |
| 304 | |
| 305 // The size of the in/out buffer in number of characters we pass to win32 | |
| 306 // GetSaveFileName. From MSDN "The buffer must be large enough to store the | |
| 307 // path and file name string or strings, including the terminating NULL | |
| 308 // character. ... The buffer should be at least 256 characters long.". | |
| 309 // _IsValidPathComDlg does a copy expecting at most MAX_PATH, otherwise will | |
| 310 // result in an error of FNERR_INVALIDFILENAME. So we should only pass the | |
| 311 // API a buffer of at most MAX_PATH. | |
| 312 wchar_t file_name[MAX_PATH]; | |
| 313 base::wcslcpy(file_name, file_part.c_str(), arraysize(file_name)); | |
| 314 | |
| 315 OPENFILENAME save_as; | |
| 316 // We must do this otherwise the ofn's FlagsEx may be initialized to random | |
| 317 // junk in release builds which can cause the Places Bar not to show up! | |
| 318 ZeroMemory(&save_as, sizeof(save_as)); | |
| 319 save_as.lStructSize = sizeof(OPENFILENAME); | |
| 320 save_as.hwndOwner = owner; | |
| 321 save_as.hInstance = NULL; | |
| 322 | |
| 323 save_as.lpstrFilter = filter.empty() ? NULL : filter.c_str(); | |
| 324 | |
| 325 save_as.lpstrCustomFilter = NULL; | |
| 326 save_as.nMaxCustFilter = 0; | |
| 327 save_as.nFilterIndex = *index; | |
| 328 save_as.lpstrFile = file_name; | |
| 329 save_as.nMaxFile = arraysize(file_name); | |
| 330 save_as.lpstrFileTitle = NULL; | |
| 331 save_as.nMaxFileTitle = 0; | |
| 332 | |
| 333 // Set up the initial directory for the dialog. | |
| 334 std::wstring directory; | |
| 335 if (!suggested_name.empty()) | |
| 336 directory = suggested_path.DirName().value(); | |
| 337 | |
| 338 save_as.lpstrInitialDir = directory.c_str(); | |
| 339 save_as.lpstrTitle = NULL; | |
| 340 save_as.Flags = OFN_OVERWRITEPROMPT | OFN_EXPLORER | OFN_ENABLESIZING | | |
| 341 OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST; | |
| 342 save_as.lpstrDefExt = &def_ext[0]; | |
| 343 save_as.lCustData = NULL; | |
| 344 | |
| 345 if (base::win::GetVersion() < base::win::VERSION_VISTA) { | |
| 346 // The save as on Windows XP remembers its last position, | |
| 347 // and if the screen resolution changed, it will be off screen. | |
| 348 save_as.Flags |= OFN_ENABLEHOOK; | |
| 349 save_as.lpfnHook = &SaveAsDialogHook; | |
| 350 } | |
| 351 | |
| 352 // Must be NULL or 0. | |
| 353 save_as.pvReserved = NULL; | |
| 354 save_as.dwReserved = 0; | |
| 355 | |
| 356 if (!CallGetSaveFileName(&save_as)) { | |
| 357 // Zero means the dialog was closed, otherwise we had an error. | |
| 358 DWORD error_code = CommDlgExtendedError(); | |
| 359 if (error_code != 0) { | |
| 360 NOTREACHED() << "GetSaveFileName failed with code: " << error_code; | |
| 361 } | |
| 362 return false; | |
| 363 } | |
| 364 | |
| 365 // Return the user's choice. | |
| 366 final_name->assign(save_as.lpstrFile); | |
| 367 *index = save_as.nFilterIndex; | |
| 368 | |
| 369 // Figure out what filter got selected from the vector with embedded nulls. | |
| 370 // NOTE: The filter contains a string with embedded nulls, such as: | |
| 371 // JPG Image\0*.jpg\0All files\0*.*\0\0 | |
| 372 // The filter index is 1-based index for which pair got selected. So, using | |
| 373 // the example above, if the first index was selected we need to skip 1 | |
| 374 // instance of null to get to "*.jpg". | |
| 375 std::vector<std::wstring> filters; | |
| 376 if (!filter.empty() && save_as.nFilterIndex > 0) | |
| 377 base::SplitString(filter, '\0', &filters); | |
| 378 std::wstring filter_selected; | |
| 379 if (!filters.empty()) | |
| 380 filter_selected = filters[(2 * (save_as.nFilterIndex - 1)) + 1]; | |
| 381 | |
| 382 // Get the extension that was suggested to the user (when the Save As dialog | |
| 383 // was opened). For saving web pages, we skip this step since there may be | |
| 384 // 'extension characters' in the title of the web page. | |
| 385 std::wstring suggested_ext; | |
| 386 if (!ignore_suggested_ext) | |
| 387 suggested_ext = GetExtensionWithoutLeadingDot(suggested_path.Extension()); | |
| 388 | |
| 389 // If we can't get the extension from the suggested_name, we use the default | |
| 390 // extension passed in. This is to cover cases like when saving a web page, | |
| 391 // where we get passed in a name without an extension and a default extension | |
| 392 // along with it. | |
| 393 if (suggested_ext.empty()) | |
| 394 suggested_ext = def_ext; | |
| 395 | |
| 396 *final_name = | |
| 397 AppendExtensionIfNeeded(*final_name, filter_selected, suggested_ext); | |
| 398 return true; | |
| 399 } | |
| 400 | |
| 401 // Prompt the user for location to save a file. 'suggested_name' is a full path | |
| 402 // that gives the dialog box a hint as to how to initialize itself. | |
| 403 // For example, a 'suggested_name' of: | |
| 404 // "C:\Documents and Settings\jojo\My Documents\picture.png" | |
| 405 // will start the dialog in the "C:\Documents and Settings\jojo\My Documents\" | |
| 406 // directory, and filter for .png file types. | |
| 407 // 'owner' is the window to which the dialog box is modal, NULL for a modeless | |
| 408 // dialog box. | |
| 409 // On success, returns true and 'final_name' contains the full path of the file | |
| 410 // that the user chose. On error, returns false, and 'final_name' is not | |
| 411 // modified. | |
| 412 bool SaveFileAs(HWND owner, | |
| 413 const std::wstring& suggested_name, | |
| 414 std::wstring* final_name) { | |
| 415 std::wstring file_ext = FilePath(suggested_name).Extension().insert(0, L"*"); | |
| 416 std::wstring filter = FormatFilterForExtensions( | |
| 417 std::vector<std::wstring>(1, file_ext), | |
| 418 std::vector<std::wstring>(), | |
| 419 true); | |
| 420 unsigned index = 1; | |
| 421 return SaveFileAsWithFilter(owner, | |
| 422 suggested_name, | |
| 423 filter, | |
| 424 L"", | |
| 425 false, | |
| 426 &index, | |
| 427 final_name); | |
| 428 } | |
| 429 | |
| 430 } // namespace | |
| 431 | |
| 432 // Implementation of SelectFileDialog that shows a Windows common dialog for | |
| 433 // choosing a file or folder. | |
| 434 class SelectFileDialogImpl : public SelectFileDialog, | |
| 435 public BaseShellDialogImpl { | |
| 436 public: | |
| 437 explicit SelectFileDialogImpl(Listener* listener, | |
| 438 ui::SelectFilePolicy* policy); | |
| 439 | |
| 440 // BaseShellDialog implementation: | |
| 441 virtual bool IsRunning(HWND owning_hwnd) const OVERRIDE; | |
| 442 virtual void ListenerDestroyed() OVERRIDE; | |
| 443 | |
| 444 protected: | |
| 445 // SelectFileDialog implementation: | |
| 446 virtual void SelectFileImpl(Type type, | |
| 447 const string16& title, | |
| 448 const FilePath& default_path, | |
| 449 const FileTypeInfo* file_types, | |
| 450 int file_type_index, | |
| 451 const FilePath::StringType& default_extension, | |
| 452 gfx::NativeWindow owning_window, | |
| 453 void* params) OVERRIDE; | |
| 454 | |
| 455 private: | |
| 456 virtual ~SelectFileDialogImpl(); | |
| 457 | |
| 458 // A struct for holding all the state necessary for displaying a Save dialog. | |
| 459 struct ExecuteSelectParams { | |
| 460 ExecuteSelectParams(Type type, | |
| 461 const std::wstring& title, | |
| 462 const FilePath& default_path, | |
| 463 const FileTypeInfo* file_types, | |
| 464 int file_type_index, | |
| 465 const std::wstring& default_extension, | |
| 466 RunState run_state, | |
| 467 HWND owner, | |
| 468 void* params) | |
| 469 : type(type), | |
| 470 title(title), | |
| 471 default_path(default_path), | |
| 472 file_type_index(file_type_index), | |
| 473 default_extension(default_extension), | |
| 474 run_state(run_state), | |
| 475 owner(owner), | |
| 476 params(params) { | |
| 477 if (file_types) { | |
| 478 this->file_types = *file_types; | |
| 479 } else { | |
| 480 this->file_types.include_all_files = true; | |
| 481 } | |
| 482 } | |
| 483 SelectFileDialog::Type type; | |
| 484 std::wstring title; | |
| 485 FilePath default_path; | |
| 486 FileTypeInfo file_types; | |
| 487 int file_type_index; | |
| 488 std::wstring default_extension; | |
| 489 RunState run_state; | |
| 490 HWND owner; | |
| 491 void* params; | |
| 492 }; | |
| 493 | |
| 494 // Shows the file selection dialog modal to |owner| and calls the result | |
| 495 // back on the ui thread. Run on the dialog thread. | |
| 496 void ExecuteSelectFile(const ExecuteSelectParams& params); | |
| 497 | |
| 498 // Notifies the listener that a folder was chosen. Run on the ui thread. | |
| 499 void FileSelected(const FilePath& path, int index, | |
| 500 void* params, RunState run_state); | |
| 501 | |
| 502 // Notifies listener that multiple files were chosen. Run on the ui thread. | |
| 503 void MultiFilesSelected(const std::vector<FilePath>& paths, void* params, | |
| 504 RunState run_state); | |
| 505 | |
| 506 // Notifies the listener that no file was chosen (the action was canceled). | |
| 507 // Run on the ui thread. | |
| 508 void FileNotSelected(void* params, RunState run_state); | |
| 509 | |
| 510 // Runs a Folder selection dialog box, passes back the selected folder in | |
| 511 // |path| and returns true if the user clicks OK. If the user cancels the | |
| 512 // dialog box the value in |path| is not modified and returns false. |title| | |
| 513 // is the user-supplied title text to show for the dialog box. Run on the | |
| 514 // dialog thread. | |
| 515 bool RunSelectFolderDialog(const std::wstring& title, | |
| 516 HWND owner, | |
| 517 FilePath* path); | |
| 518 | |
| 519 // Runs an Open file dialog box, with similar semantics for input paramaters | |
| 520 // as RunSelectFolderDialog. | |
| 521 bool RunOpenFileDialog(const std::wstring& title, | |
| 522 const std::wstring& filters, | |
| 523 HWND owner, | |
| 524 FilePath* path); | |
| 525 | |
| 526 // Runs an Open file dialog box that supports multi-select, with similar | |
| 527 // semantics for input paramaters as RunOpenFileDialog. | |
| 528 bool RunOpenMultiFileDialog(const std::wstring& title, | |
| 529 const std::wstring& filter, | |
| 530 HWND owner, | |
| 531 std::vector<FilePath>* paths); | |
| 532 | |
| 533 // The callback function for when the select folder dialog is opened. | |
| 534 static int CALLBACK BrowseCallbackProc(HWND window, UINT message, | |
| 535 LPARAM parameter, | |
| 536 LPARAM data); | |
| 537 | |
| 538 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE; | |
| 539 | |
| 540 bool has_multiple_file_type_choices_; | |
| 541 | |
| 542 | |
| 543 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImpl); | |
| 544 }; | |
| 545 | |
| 546 SelectFileDialogImpl::SelectFileDialogImpl(Listener* listener, | |
| 547 ui::SelectFilePolicy* policy) | |
| 548 : SelectFileDialog(listener, policy), | |
| 549 BaseShellDialogImpl(), | |
| 550 has_multiple_file_type_choices_(false) { | |
| 551 } | |
| 552 | |
| 553 SelectFileDialogImpl::~SelectFileDialogImpl() { | |
| 554 } | |
| 555 | |
| 556 void SelectFileDialogImpl::SelectFileImpl( | |
| 557 Type type, | |
| 558 const string16& title, | |
| 559 const FilePath& default_path, | |
| 560 const FileTypeInfo* file_types, | |
| 561 int file_type_index, | |
| 562 const FilePath::StringType& default_extension, | |
| 563 gfx::NativeWindow owning_window, | |
| 564 void* params) { | |
| 565 has_multiple_file_type_choices_ = | |
| 566 file_types ? file_types->extensions.size() > 1 : true; | |
| 567 ExecuteSelectParams execute_params(type, UTF16ToWide(title), default_path, | |
| 568 file_types, file_type_index, | |
| 569 default_extension, BeginRun(owning_window), | |
| 570 owning_window, params); | |
| 571 execute_params.run_state.dialog_thread->message_loop()->PostTask( | |
| 572 FROM_HERE, | |
| 573 base::Bind(&SelectFileDialogImpl::ExecuteSelectFile, this, | |
| 574 execute_params)); | |
| 575 } | |
| 576 | |
| 577 bool SelectFileDialogImpl::HasMultipleFileTypeChoicesImpl() { | |
| 578 return has_multiple_file_type_choices_; | |
| 579 } | |
| 580 | |
| 581 bool SelectFileDialogImpl::IsRunning(HWND owning_hwnd) const { | |
| 582 return listener_ && IsRunningDialogForOwner(owning_hwnd); | |
| 583 } | |
| 584 | |
| 585 void SelectFileDialogImpl::ListenerDestroyed() { | |
| 586 // Our associated listener has gone away, so we shouldn't call back to it if | |
| 587 // our worker thread returns after the listener is dead. | |
| 588 listener_ = NULL; | |
| 589 } | |
| 590 | |
| 591 void SelectFileDialogImpl::ExecuteSelectFile( | |
| 592 const ExecuteSelectParams& params) { | |
| 593 std::vector<std::wstring> exts; | |
| 594 for (size_t i = 0; i < params.file_types.extensions.size(); ++i) { | |
| 595 const std::vector<std::wstring>& inner_exts = | |
| 596 params.file_types.extensions[i]; | |
| 597 std::wstring ext_string; | |
| 598 for (size_t j = 0; j < inner_exts.size(); ++j) { | |
| 599 if (!ext_string.empty()) | |
| 600 ext_string.push_back(L';'); | |
| 601 ext_string.append(L"*."); | |
| 602 ext_string.append(inner_exts[j]); | |
| 603 } | |
| 604 exts.push_back(ext_string); | |
| 605 } | |
| 606 std::wstring filter = FormatFilterForExtensions( | |
| 607 exts, | |
| 608 params.file_types.extension_description_overrides, | |
| 609 params.file_types.include_all_files); | |
| 610 | |
| 611 FilePath path = params.default_path; | |
| 612 bool success = false; | |
| 613 unsigned filter_index = params.file_type_index; | |
| 614 if (params.type == SELECT_FOLDER) { | |
| 615 success = RunSelectFolderDialog(params.title, | |
| 616 params.run_state.owner, | |
| 617 &path); | |
| 618 } else if (params.type == SELECT_SAVEAS_FILE) { | |
| 619 std::wstring path_as_wstring = path.value(); | |
| 620 success = SaveFileAsWithFilter(params.run_state.owner, | |
| 621 params.default_path.value(), filter, | |
| 622 params.default_extension, false, &filter_index, &path_as_wstring); | |
| 623 if (success) | |
| 624 path = FilePath(path_as_wstring); | |
| 625 DisableOwner(params.run_state.owner); | |
| 626 } else if (params.type == SELECT_OPEN_FILE) { | |
| 627 success = RunOpenFileDialog(params.title, filter, | |
| 628 params.run_state.owner, &path); | |
| 629 } else if (params.type == SELECT_OPEN_MULTI_FILE) { | |
| 630 std::vector<FilePath> paths; | |
| 631 if (RunOpenMultiFileDialog(params.title, filter, | |
| 632 params.run_state.owner, &paths)) { | |
| 633 BrowserThread::PostTask( | |
| 634 BrowserThread::UI, | |
| 635 FROM_HERE, | |
| 636 base::Bind(&SelectFileDialogImpl::MultiFilesSelected, this, paths, | |
| 637 params.params, params.run_state)); | |
| 638 return; | |
| 639 } | |
| 640 } | |
| 641 | |
| 642 if (success) { | |
| 643 BrowserThread::PostTask( | |
| 644 BrowserThread::UI, | |
| 645 FROM_HERE, | |
| 646 base::Bind(&SelectFileDialogImpl::FileSelected, this, path, | |
| 647 filter_index, params.params, params.run_state)); | |
| 648 } else { | |
| 649 BrowserThread::PostTask( | |
| 650 BrowserThread::UI, | |
| 651 FROM_HERE, | |
| 652 base::Bind(&SelectFileDialogImpl::FileNotSelected, this, params.params, | |
| 653 params.run_state)); | |
| 654 } | |
| 655 } | |
| 656 | |
| 657 void SelectFileDialogImpl::FileSelected(const FilePath& selected_folder, | |
| 658 int index, | |
| 659 void* params, | |
| 660 RunState run_state) { | |
| 661 if (listener_) | |
| 662 listener_->FileSelected(selected_folder, index, params); | |
| 663 EndRun(run_state); | |
| 664 } | |
| 665 | |
| 666 void SelectFileDialogImpl::MultiFilesSelected( | |
| 667 const std::vector<FilePath>& selected_files, | |
| 668 void* params, | |
| 669 RunState run_state) { | |
| 670 if (listener_) | |
| 671 listener_->MultiFilesSelected(selected_files, params); | |
| 672 EndRun(run_state); | |
| 673 } | |
| 674 | |
| 675 void SelectFileDialogImpl::FileNotSelected(void* params, RunState run_state) { | |
| 676 if (listener_) | |
| 677 listener_->FileSelectionCanceled(params); | |
| 678 EndRun(run_state); | |
| 679 } | |
| 680 | |
| 681 int CALLBACK SelectFileDialogImpl::BrowseCallbackProc(HWND window, | |
| 682 UINT message, | |
| 683 LPARAM parameter, | |
| 684 LPARAM data) { | |
| 685 if (message == BFFM_INITIALIZED) { | |
| 686 // WParam is TRUE since passing a path. | |
| 687 // data lParam member of the BROWSEINFO structure. | |
| 688 SendMessage(window, BFFM_SETSELECTION, TRUE, (LPARAM)data); | |
| 689 } | |
| 690 return 0; | |
| 691 } | |
| 692 | |
| 693 bool SelectFileDialogImpl::RunSelectFolderDialog(const std::wstring& title, | |
| 694 HWND owner, | |
| 695 FilePath* path) { | |
| 696 DCHECK(path); | |
| 697 | |
| 698 wchar_t dir_buffer[MAX_PATH + 1]; | |
| 699 | |
| 700 bool result = false; | |
| 701 BROWSEINFO browse_info = {0}; | |
| 702 browse_info.hwndOwner = owner; | |
| 703 browse_info.lpszTitle = title.c_str(); | |
| 704 browse_info.pszDisplayName = dir_buffer; | |
| 705 browse_info.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; | |
| 706 | |
| 707 if (path->value().length()) { | |
| 708 // Highlight the current value. | |
| 709 browse_info.lParam = (LPARAM)path->value().c_str(); | |
| 710 browse_info.lpfn = &BrowseCallbackProc; | |
| 711 } | |
| 712 | |
| 713 LPITEMIDLIST list = SHBrowseForFolder(&browse_info); | |
| 714 DisableOwner(owner); | |
| 715 if (list) { | |
| 716 STRRET out_dir_buffer; | |
| 717 ZeroMemory(&out_dir_buffer, sizeof(out_dir_buffer)); | |
| 718 out_dir_buffer.uType = STRRET_WSTR; | |
| 719 base::win::ScopedComPtr<IShellFolder> shell_folder; | |
| 720 if (SHGetDesktopFolder(shell_folder.Receive()) == NOERROR) { | |
| 721 HRESULT hr = shell_folder->GetDisplayNameOf(list, SHGDN_FORPARSING, | |
| 722 &out_dir_buffer); | |
| 723 if (SUCCEEDED(hr) && out_dir_buffer.uType == STRRET_WSTR) { | |
| 724 *path = FilePath(out_dir_buffer.pOleStr); | |
| 725 CoTaskMemFree(out_dir_buffer.pOleStr); | |
| 726 result = true; | |
| 727 } else { | |
| 728 // Use old way if we don't get what we want. | |
| 729 wchar_t old_out_dir_buffer[MAX_PATH + 1]; | |
| 730 if (SHGetPathFromIDList(list, old_out_dir_buffer)) { | |
| 731 *path = FilePath(old_out_dir_buffer); | |
| 732 result = true; | |
| 733 } | |
| 734 } | |
| 735 | |
| 736 // According to MSDN, win2000 will not resolve shortcuts, so we do it | |
| 737 // ourself. | |
| 738 file_util::ResolveShortcut(path); | |
| 739 } | |
| 740 CoTaskMemFree(list); | |
| 741 } | |
| 742 return result; | |
| 743 } | |
| 744 | |
| 745 bool SelectFileDialogImpl::RunOpenFileDialog( | |
| 746 const std::wstring& title, | |
| 747 const std::wstring& filter, | |
| 748 HWND owner, | |
| 749 FilePath* path) { | |
| 750 OPENFILENAME ofn; | |
| 751 // We must do this otherwise the ofn's FlagsEx may be initialized to random | |
| 752 // junk in release builds which can cause the Places Bar not to show up! | |
| 753 ZeroMemory(&ofn, sizeof(ofn)); | |
| 754 ofn.lStructSize = sizeof(ofn); | |
| 755 ofn.hwndOwner = owner; | |
| 756 | |
| 757 wchar_t filename[MAX_PATH]; | |
| 758 // According to http://support.microsoft.com/?scid=kb;en-us;222003&x=8&y=12, | |
| 759 // The lpstrFile Buffer MUST be NULL Terminated. | |
| 760 filename[0] = 0; | |
| 761 // Define the dir in here to keep the string buffer pointer pointed to | |
| 762 // ofn.lpstrInitialDir available during the period of running the | |
| 763 // GetOpenFileName. | |
| 764 FilePath dir; | |
| 765 // Use lpstrInitialDir to specify the initial directory | |
| 766 if (!path->empty()) { | |
| 767 bool is_dir; | |
| 768 base::PlatformFileInfo file_info; | |
| 769 if (file_util::GetFileInfo(*path, &file_info)) | |
| 770 is_dir = file_info.is_directory; | |
| 771 else | |
| 772 is_dir = file_util::EndsWithSeparator(*path); | |
| 773 if (is_dir) { | |
| 774 ofn.lpstrInitialDir = path->value().c_str(); | |
| 775 } else { | |
| 776 dir = path->DirName(); | |
| 777 ofn.lpstrInitialDir = dir.value().c_str(); | |
| 778 // Only pure filename can be put in lpstrFile field. | |
| 779 base::wcslcpy(filename, path->BaseName().value().c_str(), | |
| 780 arraysize(filename)); | |
| 781 } | |
| 782 } | |
| 783 | |
| 784 ofn.lpstrFile = filename; | |
| 785 ofn.nMaxFile = MAX_PATH; | |
| 786 | |
| 787 // We use OFN_NOCHANGEDIR so that the user can rename or delete the directory | |
| 788 // without having to close Chrome first. | |
| 789 ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; | |
| 790 | |
| 791 if (!filter.empty()) | |
| 792 ofn.lpstrFilter = filter.c_str(); | |
| 793 bool success = CallGetOpenFileName(&ofn); | |
| 794 DisableOwner(owner); | |
| 795 if (success) | |
| 796 *path = FilePath(filename); | |
| 797 return success; | |
| 798 } | |
| 799 | |
| 800 bool SelectFileDialogImpl::RunOpenMultiFileDialog( | |
| 801 const std::wstring& title, | |
| 802 const std::wstring& filter, | |
| 803 HWND owner, | |
| 804 std::vector<FilePath>* paths) { | |
| 805 OPENFILENAME ofn; | |
| 806 // We must do this otherwise the ofn's FlagsEx may be initialized to random | |
| 807 // junk in release builds which can cause the Places Bar not to show up! | |
| 808 ZeroMemory(&ofn, sizeof(ofn)); | |
| 809 ofn.lStructSize = sizeof(ofn); | |
| 810 ofn.hwndOwner = owner; | |
| 811 | |
| 812 scoped_array<wchar_t> filename(new wchar_t[UNICODE_STRING_MAX_CHARS]); | |
| 813 filename[0] = 0; | |
| 814 | |
| 815 ofn.lpstrFile = filename.get(); | |
| 816 ofn.nMaxFile = UNICODE_STRING_MAX_CHARS; | |
| 817 // We use OFN_NOCHANGEDIR so that the user can rename or delete the directory | |
| 818 // without having to close Chrome first. | |
| 819 ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER | |
| 820 | OFN_HIDEREADONLY | OFN_ALLOWMULTISELECT; | |
| 821 | |
| 822 if (!filter.empty()) { | |
| 823 ofn.lpstrFilter = filter.c_str(); | |
| 824 } | |
| 825 | |
| 826 bool success = CallGetOpenFileName(&ofn); | |
| 827 DisableOwner(owner); | |
| 828 if (success) { | |
| 829 std::vector<FilePath> files; | |
| 830 const wchar_t* selection = ofn.lpstrFile; | |
| 831 while (*selection) { // Empty string indicates end of list. | |
| 832 files.push_back(FilePath(selection)); | |
| 833 // Skip over filename and null-terminator. | |
| 834 selection += files.back().value().length() + 1; | |
| 835 } | |
| 836 if (files.empty()) { | |
| 837 success = false; | |
| 838 } else if (files.size() == 1) { | |
| 839 // When there is one file, it contains the path and filename. | |
| 840 paths->swap(files); | |
| 841 } else { | |
| 842 // Otherwise, the first string is the path, and the remainder are | |
| 843 // filenames. | |
| 844 std::vector<FilePath>::iterator path = files.begin(); | |
| 845 for (std::vector<FilePath>::iterator file = path + 1; | |
| 846 file != files.end(); ++file) { | |
| 847 paths->push_back(path->Append(*file)); | |
| 848 } | |
| 849 } | |
| 850 } | |
| 851 return success; | |
| 852 } | |
| 853 | |
| 854 // static | |
| 855 SelectFileDialog* SelectFileDialog::Create(Listener* listener, | |
| 856 ui::SelectFilePolicy* policy) { | |
| 857 return new SelectFileDialogImpl(listener, policy); | |
| 858 } | |
| OLD | NEW |