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/libgtkui/select_file_dialog_impl_gtk2.h" | |
6 | |
7 #include <gdk/gdkx.h> | |
8 #include <gtk/gtk.h> | |
9 #include <stddef.h> | |
10 #include <sys/stat.h> | |
11 #include <sys/types.h> | |
12 #include <unistd.h> | |
13 | |
14 #include <map> | |
15 #include <memory> | |
16 #include <set> | |
17 #include <vector> | |
18 | |
19 // Xlib defines RootWindow | |
20 #undef RootWindow | |
21 | |
22 #include "base/logging.h" | |
23 #include "base/message_loop/message_loop.h" | |
24 #include "base/strings/string_util.h" | |
25 #include "base/strings/sys_string_conversions.h" | |
26 #include "base/strings/utf_string_conversions.h" | |
27 #include "base/threading/thread.h" | |
28 #include "base/threading/thread_restrictions.h" | |
29 #include "chrome/browser/ui/libgtkui/gtk2_signal.h" | |
30 #include "chrome/browser/ui/libgtkui/gtk2_util.h" | |
31 #include "chrome/browser/ui/libgtkui/select_file_dialog_impl.h" | |
32 #include "ui/aura/window_observer.h" | |
33 #include "ui/base/l10n/l10n_util.h" | |
34 #include "ui/events/platform/x11/x11_event_source.h" | |
35 #include "ui/shell_dialogs/select_file_dialog.h" | |
36 #include "ui/strings/grit/ui_strings.h" | |
37 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h" | |
38 | |
39 namespace { | |
40 | |
41 // Makes sure that .jpg also shows .JPG. | |
42 gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info, | |
43 std::string* file_extension) { | |
44 return base::EndsWith(file_info->filename, *file_extension, | |
45 base::CompareCase::INSENSITIVE_ASCII); | |
46 } | |
47 | |
48 // Deletes |data| when gtk_file_filter_add_custom() is done with it. | |
49 void OnFileFilterDataDestroyed(std::string* file_extension) { | |
50 delete file_extension; | |
51 } | |
52 | |
53 // Runs DesktopWindowTreeHostX11::EnableEventListening() when the file-picker | |
54 // is closed. | |
55 void OnFilePickerDestroy(base::Closure* callback) { | |
56 callback->Run(); | |
57 } | |
58 | |
59 } // namespace | |
60 | |
61 namespace libgtkui { | |
62 | |
63 // The size of the preview we display for selected image files. We set height | |
64 // larger than width because generally there is more free space vertically | |
65 // than horiztonally (setting the preview image will alway expand the width of | |
66 // the dialog, but usually not the height). The image's aspect ratio will always | |
67 // be preserved. | |
68 static const int kPreviewWidth = 256; | |
69 static const int kPreviewHeight = 512; | |
70 | |
71 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplGTK( | |
72 Listener* listener, ui::SelectFilePolicy* policy) { | |
73 return new SelectFileDialogImplGTK(listener, policy); | |
74 } | |
75 | |
76 SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener* listener, | |
77 ui::SelectFilePolicy* policy) | |
78 : SelectFileDialogImpl(listener, policy), | |
79 preview_(NULL) { | |
80 } | |
81 | |
82 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() { | |
83 for (std::set<aura::Window*>::iterator iter = parents_.begin(); | |
84 iter != parents_.end(); ++iter) { | |
85 (*iter)->RemoveObserver(this); | |
86 } | |
87 while (dialogs_.begin() != dialogs_.end()) { | |
88 gtk_widget_destroy(*(dialogs_.begin())); | |
89 } | |
90 } | |
91 | |
92 bool SelectFileDialogImplGTK::IsRunning(gfx::NativeWindow parent_window) const { | |
93 return parents_.find(parent_window) != parents_.end(); | |
94 } | |
95 | |
96 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() { | |
97 return file_types_.extensions.size() > 1; | |
98 } | |
99 | |
100 void SelectFileDialogImplGTK::OnWindowDestroying(aura::Window* window) { | |
101 // Remove the |parent| property associated with the |dialog|. | |
102 for (std::set<GtkWidget*>::iterator it = dialogs_.begin(); | |
103 it != dialogs_.end(); ++it) { | |
104 aura::Window* parent = GetAuraTransientParent(*it); | |
105 if (parent == window) | |
106 ClearAuraTransientParent(*it); | |
107 } | |
108 | |
109 std::set<aura::Window*>::iterator iter = parents_.find(window); | |
110 if (iter != parents_.end()) { | |
111 (*iter)->RemoveObserver(this); | |
112 parents_.erase(iter); | |
113 } | |
114 } | |
115 | |
116 // We ignore |default_extension|. | |
117 void SelectFileDialogImplGTK::SelectFileImpl( | |
118 Type type, | |
119 const base::string16& title, | |
120 const base::FilePath& default_path, | |
121 const FileTypeInfo* file_types, | |
122 int file_type_index, | |
123 const base::FilePath::StringType& default_extension, | |
124 gfx::NativeWindow owning_window, | |
125 void* params) { | |
126 type_ = type; | |
127 if (owning_window) { | |
128 owning_window->AddObserver(this); | |
129 parents_.insert(owning_window); | |
130 } | |
131 | |
132 std::string title_string = base::UTF16ToUTF8(title); | |
133 | |
134 file_type_index_ = file_type_index; | |
135 if (file_types) | |
136 file_types_ = *file_types; | |
137 | |
138 GtkWidget* dialog = NULL; | |
139 switch (type) { | |
140 case SELECT_FOLDER: | |
141 case SELECT_UPLOAD_FOLDER: | |
142 dialog = CreateSelectFolderDialog(type, title_string, default_path, | |
143 owning_window); | |
144 break; | |
145 case SELECT_OPEN_FILE: | |
146 dialog = CreateFileOpenDialog(title_string, default_path, owning_window); | |
147 break; | |
148 case SELECT_OPEN_MULTI_FILE: | |
149 dialog = CreateMultiFileOpenDialog(title_string, default_path, | |
150 owning_window); | |
151 break; | |
152 case SELECT_SAVEAS_FILE: | |
153 dialog = CreateSaveAsDialog(title_string, default_path, owning_window); | |
154 break; | |
155 default: | |
156 NOTREACHED(); | |
157 return; | |
158 } | |
159 g_signal_connect(dialog, "delete-event", | |
160 G_CALLBACK(gtk_widget_hide_on_delete), NULL); | |
161 dialogs_.insert(dialog); | |
162 | |
163 preview_ = gtk_image_new(); | |
164 g_signal_connect(dialog, "destroy", | |
165 G_CALLBACK(OnFileChooserDestroyThunk), this); | |
166 g_signal_connect(dialog, "update-preview", | |
167 G_CALLBACK(OnUpdatePreviewThunk), this); | |
168 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview_); | |
169 | |
170 params_map_[dialog] = params; | |
171 | |
172 // Disable input events handling in the host window to make this dialog modal. | |
173 if (owning_window) { | |
174 aura::WindowTreeHost* host = owning_window->GetHost(); | |
175 if (host) { | |
176 std::unique_ptr<base::Closure> callback = | |
177 views::DesktopWindowTreeHostX11::GetHostForXID( | |
178 host->GetAcceleratedWidget())->DisableEventListening( | |
179 GDK_WINDOW_XID(gtk_widget_get_window(dialog))); | |
180 // OnFilePickerDestroy() is called when |dialog| destroyed, which allows | |
181 // to invoke the callback function to re-enable event handling on the | |
182 // owning window. | |
183 g_object_set_data_full(G_OBJECT(dialog), "callback", callback.release(), | |
184 reinterpret_cast<GDestroyNotify>(OnFilePickerDestroy)); | |
185 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); | |
186 } | |
187 } | |
188 | |
189 gtk_widget_show_all(dialog); | |
190 | |
191 // We need to call gtk_window_present after making the widgets visible to make | |
192 // sure window gets correctly raised and gets focus. | |
193 gtk_window_present_with_time( | |
194 GTK_WINDOW(dialog), ui::X11EventSource::GetInstance()->GetTimestamp()); | |
195 } | |
196 | |
197 void SelectFileDialogImplGTK::AddFilters(GtkFileChooser* chooser) { | |
198 for (size_t i = 0; i < file_types_.extensions.size(); ++i) { | |
199 GtkFileFilter* filter = NULL; | |
200 std::set<std::string> fallback_labels; | |
201 | |
202 for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) { | |
203 const std::string& current_extension = file_types_.extensions[i][j]; | |
204 if (!current_extension.empty()) { | |
205 if (!filter) | |
206 filter = gtk_file_filter_new(); | |
207 std::unique_ptr<std::string> file_extension( | |
208 new std::string("." + current_extension)); | |
209 fallback_labels.insert(std::string("*").append(*file_extension)); | |
210 gtk_file_filter_add_custom( | |
211 filter, | |
212 GTK_FILE_FILTER_FILENAME, | |
213 reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive), | |
214 file_extension.release(), | |
215 reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed)); | |
216 } | |
217 } | |
218 // We didn't find any non-empty extensions to filter on. | |
219 if (!filter) | |
220 continue; | |
221 | |
222 // The description vector may be blank, in which case we are supposed to | |
223 // use some sort of default description based on the filter. | |
224 if (i < file_types_.extension_description_overrides.size()) { | |
225 gtk_file_filter_set_name(filter, base::UTF16ToUTF8( | |
226 file_types_.extension_description_overrides[i]).c_str()); | |
227 } else { | |
228 // There is no system default filter description so we use | |
229 // the extensions themselves if the description is blank. | |
230 std::vector<std::string> fallback_labels_vector(fallback_labels.begin(), | |
231 fallback_labels.end()); | |
232 std::string fallback_label = | |
233 base::JoinString(fallback_labels_vector, ","); | |
234 gtk_file_filter_set_name(filter, fallback_label.c_str()); | |
235 } | |
236 | |
237 gtk_file_chooser_add_filter(chooser, filter); | |
238 if (i == file_type_index_ - 1) | |
239 gtk_file_chooser_set_filter(chooser, filter); | |
240 } | |
241 | |
242 // Add the *.* filter, but only if we have added other filters (otherwise it | |
243 // is implied). | |
244 if (file_types_.include_all_files && !file_types_.extensions.empty()) { | |
245 GtkFileFilter* filter = gtk_file_filter_new(); | |
246 gtk_file_filter_add_pattern(filter, "*"); | |
247 gtk_file_filter_set_name(filter, | |
248 l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES).c_str()); | |
249 gtk_file_chooser_add_filter(chooser, filter); | |
250 } | |
251 } | |
252 | |
253 void SelectFileDialogImplGTK::FileSelected(GtkWidget* dialog, | |
254 const base::FilePath& path) { | |
255 if (type_ == SELECT_SAVEAS_FILE) { | |
256 *last_saved_path_ = path.DirName(); | |
257 } else if (type_ == SELECT_OPEN_FILE || type_ == SELECT_FOLDER || | |
258 type_ == SELECT_UPLOAD_FOLDER) { | |
259 *last_opened_path_ = path.DirName(); | |
260 } else { | |
261 NOTREACHED(); | |
262 } | |
263 | |
264 if (listener_) { | |
265 GtkFileFilter* selected_filter = | |
266 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog)); | |
267 GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog)); | |
268 int idx = g_slist_index(filters, selected_filter); | |
269 g_slist_free(filters); | |
270 listener_->FileSelected(path, idx + 1, PopParamsForDialog(dialog)); | |
271 } | |
272 gtk_widget_destroy(dialog); | |
273 } | |
274 | |
275 void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget* dialog, | |
276 const std::vector<base::FilePath>& files) { | |
277 *last_opened_path_ = files[0].DirName(); | |
278 | |
279 if (listener_) | |
280 listener_->MultiFilesSelected(files, PopParamsForDialog(dialog)); | |
281 gtk_widget_destroy(dialog); | |
282 } | |
283 | |
284 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget* dialog) { | |
285 void* params = PopParamsForDialog(dialog); | |
286 if (listener_) | |
287 listener_->FileSelectionCanceled(params); | |
288 gtk_widget_destroy(dialog); | |
289 } | |
290 | |
291 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenHelper( | |
292 const std::string& title, | |
293 const base::FilePath& default_path, | |
294 gfx::NativeWindow parent) { | |
295 GtkWidget* dialog = | |
296 gtk_file_chooser_dialog_new(title.c_str(), NULL, | |
297 GTK_FILE_CHOOSER_ACTION_OPEN, | |
298 "_Cancel", GTK_RESPONSE_CANCEL, | |
299 "_Open", GTK_RESPONSE_ACCEPT, | |
300 nullptr); | |
301 SetGtkTransientForAura(dialog, parent); | |
302 AddFilters(GTK_FILE_CHOOSER(dialog)); | |
303 | |
304 if (!default_path.empty()) { | |
305 if (CallDirectoryExistsOnUIThread(default_path)) { | |
306 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), | |
307 default_path.value().c_str()); | |
308 } else { | |
309 // If the file doesn't exist, this will just switch to the correct | |
310 // directory. That's good enough. | |
311 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), | |
312 default_path.value().c_str()); | |
313 } | |
314 } else if (!last_opened_path_->empty()) { | |
315 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), | |
316 last_opened_path_->value().c_str()); | |
317 } | |
318 return dialog; | |
319 } | |
320 | |
321 GtkWidget* SelectFileDialogImplGTK::CreateSelectFolderDialog( | |
322 Type type, | |
323 const std::string& title, | |
324 const base::FilePath& default_path, | |
325 gfx::NativeWindow parent) { | |
326 std::string title_string = title; | |
327 if (title_string.empty()) { | |
328 title_string = (type == SELECT_UPLOAD_FOLDER) ? | |
329 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE) : | |
330 l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE); | |
331 } | |
332 std::string accept_button_label = (type == SELECT_UPLOAD_FOLDER) ? | |
333 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON) : | |
334 "_Open"; | |
335 | |
336 GtkWidget* dialog = | |
337 gtk_file_chooser_dialog_new(title_string.c_str(), NULL, | |
338 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, | |
339 "_Cancel", GTK_RESPONSE_CANCEL, | |
340 accept_button_label.c_str(), | |
341 GTK_RESPONSE_ACCEPT, | |
342 nullptr); | |
343 SetGtkTransientForAura(dialog, parent); | |
344 | |
345 if (!default_path.empty()) { | |
346 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), | |
347 default_path.value().c_str()); | |
348 } else if (!last_opened_path_->empty()) { | |
349 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), | |
350 last_opened_path_->value().c_str()); | |
351 } | |
352 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); | |
353 g_signal_connect(dialog, "response", | |
354 G_CALLBACK(OnSelectSingleFolderDialogResponseThunk), this); | |
355 return dialog; | |
356 } | |
357 | |
358 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenDialog( | |
359 const std::string& title, | |
360 const base::FilePath& default_path, | |
361 gfx::NativeWindow parent) { | |
362 std::string title_string = !title.empty() ? title : | |
363 l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE); | |
364 | |
365 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent); | |
366 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); | |
367 g_signal_connect(dialog, "response", | |
368 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this); | |
369 return dialog; | |
370 } | |
371 | |
372 GtkWidget* SelectFileDialogImplGTK::CreateMultiFileOpenDialog( | |
373 const std::string& title, | |
374 const base::FilePath& default_path, | |
375 gfx::NativeWindow parent) { | |
376 std::string title_string = !title.empty() ? title : | |
377 l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE); | |
378 | |
379 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent); | |
380 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); | |
381 g_signal_connect(dialog, "response", | |
382 G_CALLBACK(OnSelectMultiFileDialogResponseThunk), this); | |
383 return dialog; | |
384 } | |
385 | |
386 GtkWidget* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string& title, | |
387 const base::FilePath& default_path, gfx::NativeWindow parent) { | |
388 std::string title_string = !title.empty() ? title : | |
389 l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE); | |
390 | |
391 GtkWidget* dialog = | |
392 gtk_file_chooser_dialog_new(title_string.c_str(), NULL, | |
393 GTK_FILE_CHOOSER_ACTION_SAVE, | |
394 "_Cancel", GTK_RESPONSE_CANCEL, | |
395 "_Save", GTK_RESPONSE_ACCEPT, | |
396 nullptr); | |
397 SetGtkTransientForAura(dialog, parent); | |
398 | |
399 AddFilters(GTK_FILE_CHOOSER(dialog)); | |
400 if (!default_path.empty()) { | |
401 if (CallDirectoryExistsOnUIThread(default_path)) { | |
402 // If this is an existing directory, navigate to that directory, with no | |
403 // filename. | |
404 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), | |
405 default_path.value().c_str()); | |
406 } else { | |
407 // The default path does not exist, or is an existing file. We use | |
408 // set_current_folder() followed by set_current_name(), as per the | |
409 // recommendation of the GTK docs. | |
410 gtk_file_chooser_set_current_folder( | |
411 GTK_FILE_CHOOSER(dialog), default_path.DirName().value().c_str()); | |
412 gtk_file_chooser_set_current_name( | |
413 GTK_FILE_CHOOSER(dialog), default_path.BaseName().value().c_str()); | |
414 } | |
415 } else if (!last_saved_path_->empty()) { | |
416 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), | |
417 last_saved_path_->value().c_str()); | |
418 } | |
419 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); | |
420 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), | |
421 TRUE); | |
422 g_signal_connect(dialog, "response", | |
423 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this); | |
424 return dialog; | |
425 } | |
426 | |
427 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget* dialog) { | |
428 std::map<GtkWidget*, void*>::iterator iter = params_map_.find(dialog); | |
429 DCHECK(iter != params_map_.end()); | |
430 void* params = iter->second; | |
431 params_map_.erase(iter); | |
432 return params; | |
433 } | |
434 | |
435 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id) { | |
436 bool is_cancel = response_id == GTK_RESPONSE_CANCEL || | |
437 response_id == GTK_RESPONSE_DELETE_EVENT; | |
438 if (is_cancel) | |
439 return true; | |
440 | |
441 DCHECK(response_id == GTK_RESPONSE_ACCEPT); | |
442 return false; | |
443 } | |
444 | |
445 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget* dialog, | |
446 gint response_id, | |
447 bool allow_folder) { | |
448 if (IsCancelResponse(response_id)) { | |
449 FileNotSelected(dialog); | |
450 return; | |
451 } | |
452 | |
453 gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); | |
454 if (!filename) { | |
455 FileNotSelected(dialog); | |
456 return; | |
457 } | |
458 | |
459 base::FilePath path(filename); | |
460 g_free(filename); | |
461 | |
462 if (allow_folder) { | |
463 FileSelected(dialog, path); | |
464 return; | |
465 } | |
466 | |
467 if (CallDirectoryExistsOnUIThread(path)) | |
468 FileNotSelected(dialog); | |
469 else | |
470 FileSelected(dialog, path); | |
471 } | |
472 | |
473 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse( | |
474 GtkWidget* dialog, int response_id) { | |
475 SelectSingleFileHelper(dialog, response_id, false); | |
476 } | |
477 | |
478 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse( | |
479 GtkWidget* dialog, int response_id) { | |
480 SelectSingleFileHelper(dialog, response_id, true); | |
481 } | |
482 | |
483 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget* dialog, | |
484 int response_id) { | |
485 if (IsCancelResponse(response_id)) { | |
486 FileNotSelected(dialog); | |
487 return; | |
488 } | |
489 | |
490 GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); | |
491 if (!filenames) { | |
492 FileNotSelected(dialog); | |
493 return; | |
494 } | |
495 | |
496 std::vector<base::FilePath> filenames_fp; | |
497 for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) { | |
498 base::FilePath path(static_cast<char*>(iter->data)); | |
499 g_free(iter->data); | |
500 if (CallDirectoryExistsOnUIThread(path)) | |
501 continue; | |
502 filenames_fp.push_back(path); | |
503 } | |
504 g_slist_free(filenames); | |
505 | |
506 if (filenames_fp.empty()) { | |
507 FileNotSelected(dialog); | |
508 return; | |
509 } | |
510 MultiFilesSelected(dialog, filenames_fp); | |
511 } | |
512 | |
513 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget* dialog) { | |
514 dialogs_.erase(dialog); | |
515 | |
516 // |parent| can be NULL when closing the host window | |
517 // while opening the file-picker. | |
518 aura::Window* parent = GetAuraTransientParent(dialog); | |
519 if (!parent) | |
520 return; | |
521 std::set<aura::Window*>::iterator iter = parents_.find(parent); | |
522 if (iter != parents_.end()) { | |
523 (*iter)->RemoveObserver(this); | |
524 parents_.erase(iter); | |
525 } else { | |
526 NOTREACHED(); | |
527 } | |
528 } | |
529 | |
530 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget* chooser) { | |
531 gchar* filename = gtk_file_chooser_get_preview_filename( | |
532 GTK_FILE_CHOOSER(chooser)); | |
533 if (!filename) { | |
534 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser), | |
535 FALSE); | |
536 return; | |
537 } | |
538 | |
539 // Don't attempt to open anything which isn't a regular file. If a named pipe, | |
540 // this may hang. See https://crbug.com/534754. | |
541 struct stat stat_buf; | |
542 if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) { | |
543 g_free(filename); | |
544 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser), | |
545 FALSE); | |
546 return; | |
547 } | |
548 | |
549 // This will preserve the image's aspect ratio. | |
550 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth, | |
551 kPreviewHeight, NULL); | |
552 g_free(filename); | |
553 if (pixbuf) { | |
554 gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf); | |
555 g_object_unref(pixbuf); | |
556 } | |
557 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser), | |
558 pixbuf ? TRUE : FALSE); | |
559 } | |
560 | |
561 } // namespace libgtkui | |
OLD | NEW |