| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "app/win_util.h" | 5 #include "app/win_util.h" |
| 6 | 6 |
| 7 #include <commdlg.h> | 7 #include <commdlg.h> |
| 8 #include <dwmapi.h> | 8 #include <dwmapi.h> |
| 9 #include <shellapi.h> | 9 #include <shellapi.h> |
| 10 #include <shlobj.h> | 10 #include <shlobj.h> |
| 11 | 11 |
| 12 #include <algorithm> | 12 #include <algorithm> |
| 13 | 13 |
| 14 #include "app/gfx/codec/png_codec.h" | 14 #include "app/gfx/codec/png_codec.h" |
| 15 #include "app/gfx/gdi_util.h" | 15 #include "app/gfx/gdi_util.h" |
| 16 #include "app/l10n_util.h" | 16 #include "app/l10n_util.h" |
| 17 #include "app/l10n_util_win.h" | 17 #include "app/l10n_util_win.h" |
| 18 #include "base/base_switches.h" | 18 #include "base/base_switches.h" |
| 19 #include "base/command_line.h" | 19 #include "base/command_line.h" |
| 20 #include "base/file_util.h" | 20 #include "base/file_util.h" |
| 21 #include "base/logging.h" | 21 #include "base/logging.h" |
| 22 #include "base/native_library.h" | 22 #include "base/native_library.h" |
| 23 #include "base/registry.h" | 23 #include "base/registry.h" |
| 24 #include "base/scoped_comptr_win.h" | 24 #include "base/scoped_comptr_win.h" |
| 25 #include "base/scoped_handle.h" | 25 #include "base/scoped_handle.h" |
| 26 #include "base/scoped_handle_win.h" | 26 #include "base/scoped_handle_win.h" |
| 27 #include "base/string_util.h" | 27 #include "base/string_util.h" |
| 28 #include "base/win_util.h" | 28 #include "base/win_util.h" |
| 29 #include "grit/app_strings.h" | |
| 30 #include "net/base/mime_util.h" | |
| 31 | 29 |
| 32 // Ensure that we pick up this link library. | 30 // Ensure that we pick up this link library. |
| 33 #pragma comment(lib, "dwmapi.lib") | 31 #pragma comment(lib, "dwmapi.lib") |
| 34 | 32 |
| 35 namespace win_util { | 33 namespace win_util { |
| 36 | 34 |
| 37 const int kAutoHideTaskbarThicknessPx = 2; | 35 const int kAutoHideTaskbarThicknessPx = 2; |
| 38 | 36 |
| 39 namespace { | 37 namespace { |
| 40 | 38 |
| 41 const wchar_t kShell32[] = L"shell32.dll"; | 39 const wchar_t kShell32[] = L"shell32.dll"; |
| 42 const char kSHGetPropertyStoreForWindow[] = "SHGetPropertyStoreForWindow"; | 40 const char kSHGetPropertyStoreForWindow[] = "SHGetPropertyStoreForWindow"; |
| 43 | 41 |
| 44 // Define the type of SHGetPropertyStoreForWindow is SHGPSFW. | 42 // Define the type of SHGetPropertyStoreForWindow is SHGPSFW. |
| 45 typedef DECLSPEC_IMPORT HRESULT (STDAPICALLTYPE *SHGPSFW)(HWND hwnd, | 43 typedef DECLSPEC_IMPORT HRESULT (STDAPICALLTYPE *SHGPSFW)(HWND hwnd, |
| 46 REFIID riid, | 44 REFIID riid, |
| 47 void** ppv); | 45 void** ppv); |
| 48 | |
| 49 // Enforce visible dialog box. | |
| 50 UINT_PTR CALLBACK SaveAsDialogHook(HWND dialog, UINT message, | |
| 51 WPARAM wparam, LPARAM lparam) { | |
| 52 static const UINT kPrivateMessage = 0x2F3F; | |
| 53 switch (message) { | |
| 54 case WM_INITDIALOG: { | |
| 55 // Do nothing here. Just post a message to defer actual processing. | |
| 56 PostMessage(dialog, kPrivateMessage, 0, 0); | |
| 57 return TRUE; | |
| 58 } | |
| 59 case kPrivateMessage: { | |
| 60 // The dialog box is the parent of the current handle. | |
| 61 HWND real_dialog = GetParent(dialog); | |
| 62 | |
| 63 // Retrieve the final size. | |
| 64 RECT dialog_rect; | |
| 65 GetWindowRect(real_dialog, &dialog_rect); | |
| 66 | |
| 67 // Verify that the upper left corner is visible. | |
| 68 POINT point = { dialog_rect.left, dialog_rect.top }; | |
| 69 HMONITOR monitor1 = MonitorFromPoint(point, MONITOR_DEFAULTTONULL); | |
| 70 point.x = dialog_rect.right; | |
| 71 point.y = dialog_rect.bottom; | |
| 72 | |
| 73 // Verify that the lower right corner is visible. | |
| 74 HMONITOR monitor2 = MonitorFromPoint(point, MONITOR_DEFAULTTONULL); | |
| 75 if (monitor1 && monitor2) | |
| 76 return 0; | |
| 77 | |
| 78 // Some part of the dialog box is not visible, fix it by moving is to the | |
| 79 // client rect position of the browser window. | |
| 80 HWND parent_window = GetParent(real_dialog); | |
| 81 if (!parent_window) | |
| 82 return 0; | |
| 83 WINDOWINFO parent_info; | |
| 84 parent_info.cbSize = sizeof(WINDOWINFO); | |
| 85 GetWindowInfo(parent_window, &parent_info); | |
| 86 SetWindowPos(real_dialog, NULL, | |
| 87 parent_info.rcClient.left, | |
| 88 parent_info.rcClient.top, | |
| 89 0, 0, // Size. | |
| 90 SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | | |
| 91 SWP_NOZORDER); | |
| 92 | |
| 93 return 0; | |
| 94 } | |
| 95 } | |
| 96 return 0; | |
| 97 } | |
| 98 | |
| 99 } // namespace | 46 } // namespace |
| 100 | 47 |
| 101 std::wstring FormatSystemTime(const SYSTEMTIME& time, | 48 std::wstring FormatSystemTime(const SYSTEMTIME& time, |
| 102 const std::wstring& format) { | 49 const std::wstring& format) { |
| 103 // If the format string is empty, just use the default format. | 50 // If the format string is empty, just use the default format. |
| 104 LPCTSTR format_ptr = NULL; | 51 LPCTSTR format_ptr = NULL; |
| 105 if (!format.empty()) | 52 if (!format.empty()) |
| 106 format_ptr = format.c_str(); | 53 format_ptr = format.c_str(); |
| 107 | 54 |
| 108 int buffer_size = GetTimeFormat(LOCALE_USER_DEFAULT, NULL, &time, format_ptr, | 55 int buffer_size = GetTimeFormat(LOCALE_USER_DEFAULT, NULL, &time, format_ptr, |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 195 // open the file with. | 142 // open the file with. |
| 196 bool OpenItemWithExternalApp(const std::wstring& full_path) { | 143 bool OpenItemWithExternalApp(const std::wstring& full_path) { |
| 197 SHELLEXECUTEINFO sei = { sizeof(sei) }; | 144 SHELLEXECUTEINFO sei = { sizeof(sei) }; |
| 198 sei.fMask = SEE_MASK_FLAG_DDEWAIT; | 145 sei.fMask = SEE_MASK_FLAG_DDEWAIT; |
| 199 sei.nShow = SW_SHOWNORMAL; | 146 sei.nShow = SW_SHOWNORMAL; |
| 200 sei.lpVerb = L"openas"; | 147 sei.lpVerb = L"openas"; |
| 201 sei.lpFile = full_path.c_str(); | 148 sei.lpFile = full_path.c_str(); |
| 202 return (TRUE == ::ShellExecuteExW(&sei)); | 149 return (TRUE == ::ShellExecuteExW(&sei)); |
| 203 } | 150 } |
| 204 | 151 |
| 205 // Get the file type description from the registry. This will be "Text Document" | |
| 206 // for .txt files, "JPEG Image" for .jpg files, etc. If the registry doesn't | |
| 207 // have an entry for the file type, we return false, true if the description was | |
| 208 // found. 'file_ext' must be in form ".txt". | |
| 209 static bool GetRegistryDescriptionFromExtension(const std::wstring& file_ext, | |
| 210 std::wstring* reg_description) { | |
| 211 DCHECK(reg_description); | |
| 212 RegKey reg_ext(HKEY_CLASSES_ROOT, file_ext.c_str(), KEY_READ); | |
| 213 std::wstring reg_app; | |
| 214 if (reg_ext.ReadValue(NULL, ®_app) && !reg_app.empty()) { | |
| 215 RegKey reg_link(HKEY_CLASSES_ROOT, reg_app.c_str(), KEY_READ); | |
| 216 if (reg_link.ReadValue(NULL, reg_description)) | |
| 217 return true; | |
| 218 } | |
| 219 return false; | |
| 220 } | |
| 221 | |
| 222 std::wstring FormatFilterForExtensions( | |
| 223 const std::vector<std::wstring>& file_ext, | |
| 224 const std::vector<std::wstring>& ext_desc, | |
| 225 bool include_all_files) { | |
| 226 const std::wstring all_ext = L"*.*"; | |
| 227 const std::wstring all_desc = l10n_util::GetString(IDS_APP_SAVEAS_ALL_FILES); | |
| 228 | |
| 229 DCHECK(file_ext.size() >= ext_desc.size()); | |
| 230 | |
| 231 std::wstring result; | |
| 232 | |
| 233 for (size_t i = 0; i < file_ext.size(); ++i) { | |
| 234 std::wstring ext = file_ext[i]; | |
| 235 std::wstring desc; | |
| 236 if (i < ext_desc.size()) | |
| 237 desc = ext_desc[i]; | |
| 238 | |
| 239 if (ext.empty()) { | |
| 240 // Force something reasonable to appear in the dialog box if there is no | |
| 241 // extension provided. | |
| 242 include_all_files = true; | |
| 243 continue; | |
| 244 } | |
| 245 | |
| 246 if (desc.empty()) { | |
| 247 DCHECK(ext.find(L'.') != std::wstring::npos); | |
| 248 std::wstring first_extension = ext.substr(ext.find(L'.')); | |
| 249 size_t first_separator_index = first_extension.find(L';'); | |
| 250 if (first_separator_index != std::wstring::npos) | |
| 251 first_extension = first_extension.substr(0, first_separator_index); | |
| 252 | |
| 253 // Find the extension name without the preceeding '.' character. | |
| 254 std::wstring ext_name = first_extension; | |
| 255 size_t ext_index = ext_name.find_first_not_of(L'.'); | |
| 256 if (ext_index != std::wstring::npos) | |
| 257 ext_name = ext_name.substr(ext_index); | |
| 258 | |
| 259 if (!GetRegistryDescriptionFromExtension(first_extension, &desc)) { | |
| 260 // The extension doesn't exist in the registry. Create a description | |
| 261 // based on the unknown extension type (i.e. if the extension is .qqq, | |
| 262 // the we create a description "QQQ File (.qqq)"). | |
| 263 include_all_files = true; | |
| 264 desc = l10n_util::GetStringF(IDS_APP_SAVEAS_EXTENSION_FORMAT, | |
| 265 l10n_util::ToUpper(ext_name), | |
| 266 ext_name); | |
| 267 } | |
| 268 if (desc.empty()) | |
| 269 desc = L"*." + ext_name; | |
| 270 } | |
| 271 | |
| 272 result.append(desc.c_str(), desc.size() + 1); // Append NULL too. | |
| 273 result.append(ext.c_str(), ext.size() + 1); | |
| 274 } | |
| 275 | |
| 276 if (include_all_files) { | |
| 277 result.append(all_desc.c_str(), all_desc.size() + 1); | |
| 278 result.append(all_ext.c_str(), all_ext.size() + 1); | |
| 279 } | |
| 280 | |
| 281 result.append(1, '\0'); // Double NULL required. | |
| 282 return result; | |
| 283 } | |
| 284 | |
| 285 bool SaveFileAs(HWND owner, | |
| 286 const std::wstring& suggested_name, | |
| 287 std::wstring* final_name) { | |
| 288 std::wstring file_ext = file_util::GetFileExtensionFromPath(suggested_name); | |
| 289 file_ext.insert(0, L"*."); | |
| 290 std::wstring filter = FormatFilterForExtensions( | |
| 291 std::vector<std::wstring>(1, file_ext), | |
| 292 std::vector<std::wstring>(), | |
| 293 true); | |
| 294 unsigned index = 1; | |
| 295 return SaveFileAsWithFilter(owner, | |
| 296 suggested_name, | |
| 297 filter, | |
| 298 L"", | |
| 299 false, | |
| 300 &index, | |
| 301 final_name); | |
| 302 } | |
| 303 | |
| 304 bool SaveFileAsWithFilter(HWND owner, | |
| 305 const std::wstring& suggested_name, | |
| 306 const std::wstring& filter, | |
| 307 const std::wstring& def_ext, | |
| 308 bool ignore_suggested_ext, | |
| 309 unsigned* index, | |
| 310 std::wstring* final_name) { | |
| 311 DCHECK(final_name); | |
| 312 // Having an empty filter makes for a bad user experience. We should always | |
| 313 // specify a filter when saving. | |
| 314 DCHECK(!filter.empty()); | |
| 315 std::wstring file_part = file_util::GetFilenameFromPath(suggested_name); | |
| 316 | |
| 317 // The size of the in/out buffer in number of characters we pass to win32 | |
| 318 // GetSaveFileName. From MSDN "The buffer must be large enough to store the | |
| 319 // path and file name string or strings, including the terminating NULL | |
| 320 // character. ... The buffer should be at least 256 characters long.". | |
| 321 // _IsValidPathComDlg does a copy expecting at most MAX_PATH, otherwise will | |
| 322 // result in an error of FNERR_INVALIDFILENAME. So we should only pass the | |
| 323 // API a buffer of at most MAX_PATH. | |
| 324 wchar_t file_name[MAX_PATH]; | |
| 325 base::wcslcpy(file_name, file_part.c_str(), arraysize(file_name)); | |
| 326 | |
| 327 OPENFILENAME save_as; | |
| 328 // We must do this otherwise the ofn's FlagsEx may be initialized to random | |
| 329 // junk in release builds which can cause the Places Bar not to show up! | |
| 330 ZeroMemory(&save_as, sizeof(save_as)); | |
| 331 save_as.lStructSize = sizeof(OPENFILENAME); | |
| 332 save_as.hwndOwner = owner; | |
| 333 save_as.hInstance = NULL; | |
| 334 | |
| 335 save_as.lpstrFilter = filter.empty() ? NULL : filter.c_str(); | |
| 336 | |
| 337 save_as.lpstrCustomFilter = NULL; | |
| 338 save_as.nMaxCustFilter = 0; | |
| 339 save_as.nFilterIndex = *index; | |
| 340 save_as.lpstrFile = file_name; | |
| 341 save_as.nMaxFile = arraysize(file_name); | |
| 342 save_as.lpstrFileTitle = NULL; | |
| 343 save_as.nMaxFileTitle = 0; | |
| 344 | |
| 345 // Set up the initial directory for the dialog. | |
| 346 std::wstring directory = file_util::GetDirectoryFromPath(suggested_name); | |
| 347 save_as.lpstrInitialDir = directory.c_str(); | |
| 348 save_as.lpstrTitle = NULL; | |
| 349 save_as.Flags = OFN_OVERWRITEPROMPT | OFN_EXPLORER | OFN_ENABLESIZING | | |
| 350 OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST; | |
| 351 save_as.lpstrDefExt = &def_ext[0]; | |
| 352 save_as.lCustData = NULL; | |
| 353 | |
| 354 if (win_util::GetWinVersion() < win_util::WINVERSION_VISTA) { | |
| 355 // The save as on Windows XP remembers its last position, | |
| 356 // and if the screen resolution changed, it will be off screen. | |
| 357 save_as.Flags |= OFN_ENABLEHOOK; | |
| 358 save_as.lpfnHook = &SaveAsDialogHook; | |
| 359 } | |
| 360 | |
| 361 // Must be NULL or 0. | |
| 362 save_as.pvReserved = NULL; | |
| 363 save_as.dwReserved = 0; | |
| 364 | |
| 365 if (!GetSaveFileName(&save_as)) { | |
| 366 // Zero means the dialog was closed, otherwise we had an error. | |
| 367 DWORD error_code = CommDlgExtendedError(); | |
| 368 if (error_code != 0) { | |
| 369 NOTREACHED() << "GetSaveFileName failed with code: " << error_code; | |
| 370 } | |
| 371 return false; | |
| 372 } | |
| 373 | |
| 374 // Return the user's choice. | |
| 375 final_name->assign(save_as.lpstrFile); | |
| 376 *index = save_as.nFilterIndex; | |
| 377 | |
| 378 // Figure out what filter got selected from the vector with embedded nulls. | |
| 379 // NOTE: The filter contains a string with embedded nulls, such as: | |
| 380 // JPG Image\0*.jpg\0All files\0*.*\0\0 | |
| 381 // The filter index is 1-based index for which pair got selected. So, using | |
| 382 // the example above, if the first index was selected we need to skip 1 | |
| 383 // instance of null to get to "*.jpg". | |
| 384 std::vector<std::wstring> filters; | |
| 385 if (!filter.empty() && save_as.nFilterIndex > 0) | |
| 386 SplitString(filter, '\0', &filters); | |
| 387 std::wstring filter_selected; | |
| 388 if (!filters.empty()) | |
| 389 filter_selected = filters[(2 * (save_as.nFilterIndex - 1)) + 1]; | |
| 390 | |
| 391 // Get the extension that was suggested to the user (when the Save As dialog | |
| 392 // was opened). For saving web pages, we skip this step since there may be | |
| 393 // 'extension characters' in the title of the web page. | |
| 394 std::wstring suggested_ext; | |
| 395 if (!ignore_suggested_ext) | |
| 396 suggested_ext = file_util::GetFileExtensionFromPath(suggested_name); | |
| 397 | |
| 398 // If we can't get the extension from the suggested_name, we use the default | |
| 399 // extension passed in. This is to cover cases like when saving a web page, | |
| 400 // where we get passed in a name without an extension and a default extension | |
| 401 // along with it. | |
| 402 if (suggested_ext.empty()) | |
| 403 suggested_ext = def_ext; | |
| 404 | |
| 405 *final_name = | |
| 406 AppendExtensionIfNeeded(*final_name, filter_selected, suggested_ext); | |
| 407 return true; | |
| 408 } | |
| 409 | |
| 410 std::wstring AppendExtensionIfNeeded(const std::wstring& filename, | |
| 411 const std::wstring& filter_selected, | |
| 412 const std::wstring& suggested_ext) { | |
| 413 std::wstring return_value = filename; | |
| 414 | |
| 415 // Get the extension the user ended up selecting. | |
| 416 std::wstring selected_ext = file_util::GetFileExtensionFromPath(filename); | |
| 417 | |
| 418 if (filter_selected.empty() || filter_selected == L"*.*") { | |
| 419 // If the user selects 'All files' we respect any extension given to us from | |
| 420 // the File Save dialog. We also strip any trailing dots, which matches | |
| 421 // Windows Explorer and is needed because Windows doesn't allow filenames | |
| 422 // to have trailing dots. The GetSaveFileName dialog will not return a | |
| 423 // string with only one or more dots. | |
| 424 size_t index = return_value.find_last_not_of(L'.'); | |
| 425 if (index < return_value.size() - 1) | |
| 426 return_value.resize(index + 1); | |
| 427 } else { | |
| 428 // User selected a specific filter (not *.*) so we need to check if the | |
| 429 // extension provided has the same mime type. If it doesn't we append the | |
| 430 // extension. | |
| 431 std::string suggested_mime_type, selected_mime_type; | |
| 432 if (suggested_ext != selected_ext && | |
| 433 (!net::GetMimeTypeFromExtension(suggested_ext, &suggested_mime_type) || | |
| 434 !net::GetMimeTypeFromExtension(selected_ext, &selected_mime_type) || | |
| 435 suggested_mime_type != selected_mime_type)) { | |
| 436 return_value.append(L"."); | |
| 437 return_value.append(suggested_ext); | |
| 438 } | |
| 439 } | |
| 440 | |
| 441 return return_value; | |
| 442 } | |
| 443 | |
| 444 // Adjust the window to fit, returning true if the window was resized or moved. | 152 // Adjust the window to fit, returning true if the window was resized or moved. |
| 445 static bool AdjustWindowToFit(HWND hwnd, const RECT& bounds) { | 153 static bool AdjustWindowToFit(HWND hwnd, const RECT& bounds) { |
| 446 // Get the monitor. | 154 // Get the monitor. |
| 447 HMONITOR hmon = MonitorFromRect(&bounds, MONITOR_DEFAULTTONEAREST); | 155 HMONITOR hmon = MonitorFromRect(&bounds, MONITOR_DEFAULTTONEAREST); |
| 448 if (!hmon) { | 156 if (!hmon) { |
| 449 NOTREACHED() << "Unable to find default monitor"; | 157 NOTREACHED() << "Unable to find default monitor"; |
| 450 // No monitor available. | 158 // No monitor available. |
| 451 return false; | 159 return false; |
| 452 } | 160 } |
| 453 | 161 |
| (...skipping 413 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 867 hwnd, __uuidof(*pps), reinterpret_cast<void**>(pps.Receive())); | 575 hwnd, __uuidof(*pps), reinterpret_cast<void**>(pps.Receive())); |
| 868 if (S_OK == result) { | 576 if (S_OK == result) { |
| 869 SetAppIdForPropertyStore(pps, app_id.c_str()); | 577 SetAppIdForPropertyStore(pps, app_id.c_str()); |
| 870 } | 578 } |
| 871 | 579 |
| 872 // Cleanup. | 580 // Cleanup. |
| 873 base::UnloadNativeLibrary(shell32_library); | 581 base::UnloadNativeLibrary(shell32_library); |
| 874 } | 582 } |
| 875 | 583 |
| 876 } // namespace win_util | 584 } // namespace win_util |
| OLD | NEW |