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 |