Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / select_file_dialog_impl_gtk2.cc
blob76633ee849841717b3221dade512fe5cc20a1a4e
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.
5 #include <gdk/gdk.h>
6 #include <gdk/gdkx.h>
7 #include <gtk/gtk.h>
8 #include <map>
9 #include <set>
10 #include <vector>
12 // Xlib defines RootWindow
13 #undef RootWindow
15 #include "base/logging.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/message_loop/message_loop.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/sys_string_conversions.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/threading/thread.h"
22 #include "base/threading/thread_restrictions.h"
23 #include "chrome/browser/ui/libgtk2ui/gtk2_signal.h"
24 #include "chrome/browser/ui/libgtk2ui/select_file_dialog_impl.h"
25 #include "grit/ui_strings.h"
26 #include "ui/aura/root_window.h"
27 #include "ui/aura/window_observer.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/shell_dialogs/select_file_dialog.h"
31 namespace {
33 const char kAuraTransientParent[] = "aura-transient-parent";
35 // Set |dialog| as transient for |parent|, which will keep it on top and center
36 // it above |parent|.
37 void SetGtkTransientForAura(GtkWidget* dialog, aura::Window* parent) {
38 gtk_widget_realize(dialog);
39 GdkWindow* gdk_window = gtk_widget_get_window(dialog);
41 // TODO(erg): Check to make sure we're using X11 if wayland or some other
42 // display server ever happens. Otherwise, this will crash.
43 XSetTransientForHint(GDK_WINDOW_XDISPLAY(gdk_window),
44 GDK_WINDOW_XID(gdk_window),
45 parent->GetDispatcher()->host()->GetAcceleratedWidget());
47 // We also set the |parent| as a property of |dialog|, so that we can unlink
48 // the two later.
49 g_object_set_data(G_OBJECT(dialog), kAuraTransientParent, parent);
52 // Makes sure that .jpg also shows .JPG.
53 gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info,
54 std::string* file_extension) {
55 return EndsWith(file_info->filename, *file_extension, false);
58 // Deletes |data| when gtk_file_filter_add_custom() is done with it.
59 void OnFileFilterDataDestroyed(std::string* file_extension) {
60 delete file_extension;
63 } // namespace
65 namespace libgtk2ui {
67 // Implementation of SelectFileDialog that shows a Gtk common dialog for
68 // choosing a file or folder. This acts as a modal dialog.
69 class SelectFileDialogImplGTK : public SelectFileDialogImpl,
70 public aura::WindowObserver {
71 public:
72 explicit SelectFileDialogImplGTK(Listener* listener,
73 ui::SelectFilePolicy* policy);
75 protected:
76 virtual ~SelectFileDialogImplGTK();
78 // SelectFileDialog implementation.
79 // |params| is user data we pass back via the Listener interface.
80 virtual void SelectFileImpl(
81 Type type,
82 const base::string16& title,
83 const base::FilePath& default_path,
84 const FileTypeInfo* file_types,
85 int file_type_index,
86 const base::FilePath::StringType& default_extension,
87 gfx::NativeWindow owning_window,
88 void* params) OVERRIDE;
90 private:
91 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE;
93 // Overridden from aura::WindowObserver:
94 virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
96 // Add the filters from |file_types_| to |chooser|.
97 void AddFilters(GtkFileChooser* chooser);
99 // Notifies the listener that a single file was chosen.
100 void FileSelected(GtkWidget* dialog, const base::FilePath& path);
102 // Notifies the listener that multiple files were chosen.
103 void MultiFilesSelected(GtkWidget* dialog,
104 const std::vector<base::FilePath>& files);
106 // Notifies the listener that no file was chosen (the action was canceled).
107 // Dialog is passed so we can find that |params| pointer that was passed to
108 // us when we were told to show the dialog.
109 void FileNotSelected(GtkWidget* dialog);
111 GtkWidget* CreateSelectFolderDialog(
112 Type type,
113 const std::string& title,
114 const base::FilePath& default_path,
115 gfx::NativeWindow parent);
117 GtkWidget* CreateFileOpenDialog(const std::string& title,
118 const base::FilePath& default_path, gfx::NativeWindow parent);
120 GtkWidget* CreateMultiFileOpenDialog(const std::string& title,
121 const base::FilePath& default_path, gfx::NativeWindow parent);
123 GtkWidget* CreateSaveAsDialog(const std::string& title,
124 const base::FilePath& default_path, gfx::NativeWindow parent);
126 // Removes and returns the |params| associated with |dialog| from
127 // |params_map_|.
128 void* PopParamsForDialog(GtkWidget* dialog);
130 // Take care of internal data structures when a file dialog is destroyed.
131 void FileDialogDestroyed(GtkWidget* dialog);
133 // Check whether response_id corresponds to the user cancelling/closing the
134 // dialog. Used as a helper for the below callbacks.
135 bool IsCancelResponse(gint response_id);
137 // Common function for OnSelectSingleFileDialogResponse and
138 // OnSelectSingleFolderDialogResponse.
139 void SelectSingleFileHelper(GtkWidget* dialog,
140 gint response_id,
141 bool allow_folder);
143 // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
144 GtkWidget* CreateFileOpenHelper(const std::string& title,
145 const base::FilePath& default_path,
146 gfx::NativeWindow parent);
148 // Callback for when the user responds to a Save As or Open File dialog.
149 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
150 OnSelectSingleFileDialogResponse, int);
152 // Callback for when the user responds to a Select Folder dialog.
153 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
154 OnSelectSingleFolderDialogResponse, int);
156 // Callback for when the user responds to a Open Multiple Files dialog.
157 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
158 OnSelectMultiFileDialogResponse, int);
160 // Callback for when the file chooser gets destroyed.
161 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnFileChooserDestroy);
163 // Callback for when we update the preview for the selection.
164 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnUpdatePreview);
166 // A map from dialog windows to the |params| user data associated with them.
167 std::map<GtkWidget*, void*> params_map_;
169 // The GtkImage widget for showing previews of selected images.
170 GtkWidget* preview_;
172 // All our dialogs.
173 std::set<GtkWidget*> dialogs_;
175 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplGTK);
178 // The size of the preview we display for selected image files. We set height
179 // larger than width because generally there is more free space vertically
180 // than horiztonally (setting the preview image will alway expand the width of
181 // the dialog, but usually not the height). The image's aspect ratio will always
182 // be preserved.
183 static const int kPreviewWidth = 256;
184 static const int kPreviewHeight = 512;
186 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
187 Listener* listener, ui::SelectFilePolicy* policy) {
188 return new SelectFileDialogImplGTK(listener, policy);
191 SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener* listener,
192 ui::SelectFilePolicy* policy)
193 : SelectFileDialogImpl(listener, policy),
194 preview_(NULL) {
197 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() {
198 while (dialogs_.begin() != dialogs_.end()) {
199 gtk_widget_destroy(*(dialogs_.begin()));
203 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
204 return file_types_.extensions.size() > 1;
207 void SelectFileDialogImplGTK::OnWindowDestroying(aura::Window* window) {
208 std::set<aura::Window*>::iterator iter = parents_.find(window);
209 if (iter != parents_.end()) {
210 (*iter)->RemoveObserver(this);
211 parents_.erase(iter);
215 // We ignore |default_extension|.
216 void SelectFileDialogImplGTK::SelectFileImpl(
217 Type type,
218 const base::string16& title,
219 const base::FilePath& default_path,
220 const FileTypeInfo* file_types,
221 int file_type_index,
222 const base::FilePath::StringType& default_extension,
223 gfx::NativeWindow owning_window,
224 void* params) {
225 type_ = type;
226 // |owning_window| can be null when user right-clicks on a downloadable item
227 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
228 // before downloading.' preference is turned on. (http://crbug.com/29213)
229 if (owning_window) {
230 owning_window->AddObserver(this);
231 parents_.insert(owning_window);
234 std::string title_string = base::UTF16ToUTF8(title);
236 file_type_index_ = file_type_index;
237 if (file_types)
238 file_types_ = *file_types;
240 GtkWidget* dialog = NULL;
241 switch (type) {
242 case SELECT_FOLDER:
243 case SELECT_UPLOAD_FOLDER:
244 dialog = CreateSelectFolderDialog(type, title_string, default_path,
245 owning_window);
246 break;
247 case SELECT_OPEN_FILE:
248 dialog = CreateFileOpenDialog(title_string, default_path, owning_window);
249 break;
250 case SELECT_OPEN_MULTI_FILE:
251 dialog = CreateMultiFileOpenDialog(title_string, default_path,
252 owning_window);
253 break;
254 case SELECT_SAVEAS_FILE:
255 dialog = CreateSaveAsDialog(title_string, default_path, owning_window);
256 break;
257 default:
258 NOTREACHED();
259 return;
261 g_signal_connect(dialog, "delete-event",
262 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
263 dialogs_.insert(dialog);
265 preview_ = gtk_image_new();
266 g_signal_connect(dialog, "destroy",
267 G_CALLBACK(OnFileChooserDestroyThunk), this);
268 g_signal_connect(dialog, "update-preview",
269 G_CALLBACK(OnUpdatePreviewThunk), this);
270 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview_);
272 params_map_[dialog] = params;
274 // TODO(erg): Figure out how to fake GTK window-to-parent modality without
275 // having the parent be a real GtkWindow.
276 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
278 gtk_widget_show_all(dialog);
281 void SelectFileDialogImplGTK::AddFilters(GtkFileChooser* chooser) {
282 for (size_t i = 0; i < file_types_.extensions.size(); ++i) {
283 GtkFileFilter* filter = NULL;
284 std::set<std::string> fallback_labels;
286 for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) {
287 const std::string& current_extension = file_types_.extensions[i][j];
288 if (!current_extension.empty()) {
289 if (!filter)
290 filter = gtk_file_filter_new();
291 scoped_ptr<std::string> file_extension(
292 new std::string("." + current_extension));
293 fallback_labels.insert(std::string("*").append(*file_extension));
294 gtk_file_filter_add_custom(
295 filter,
296 GTK_FILE_FILTER_FILENAME,
297 reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive),
298 file_extension.release(),
299 reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed));
302 // We didn't find any non-empty extensions to filter on.
303 if (!filter)
304 continue;
306 // The description vector may be blank, in which case we are supposed to
307 // use some sort of default description based on the filter.
308 if (i < file_types_.extension_description_overrides.size()) {
309 gtk_file_filter_set_name(filter, base::UTF16ToUTF8(
310 file_types_.extension_description_overrides[i]).c_str());
311 } else {
312 // There is no system default filter description so we use
313 // the extensions themselves if the description is blank.
314 std::vector<std::string> fallback_labels_vector(fallback_labels.begin(),
315 fallback_labels.end());
316 std::string fallback_label = JoinString(fallback_labels_vector, ',');
317 gtk_file_filter_set_name(filter, fallback_label.c_str());
320 gtk_file_chooser_add_filter(chooser, filter);
321 if (i == file_type_index_ - 1)
322 gtk_file_chooser_set_filter(chooser, filter);
325 // Add the *.* filter, but only if we have added other filters (otherwise it
326 // is implied).
327 if (file_types_.include_all_files && !file_types_.extensions.empty()) {
328 GtkFileFilter* filter = gtk_file_filter_new();
329 gtk_file_filter_add_pattern(filter, "*");
330 gtk_file_filter_set_name(filter,
331 l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES).c_str());
332 gtk_file_chooser_add_filter(chooser, filter);
336 void SelectFileDialogImplGTK::FileSelected(GtkWidget* dialog,
337 const base::FilePath& path) {
338 if (type_ == SELECT_SAVEAS_FILE) {
339 *last_saved_path_ = path.DirName();
340 } else if (type_ == SELECT_OPEN_FILE || type_ == SELECT_FOLDER ||
341 type_ == SELECT_UPLOAD_FOLDER) {
342 *last_opened_path_ = path.DirName();
343 } else {
344 NOTREACHED();
347 if (listener_) {
348 GtkFileFilter* selected_filter =
349 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
350 GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog));
351 int idx = g_slist_index(filters, selected_filter);
352 g_slist_free(filters);
353 listener_->FileSelected(path, idx + 1, PopParamsForDialog(dialog));
355 gtk_widget_destroy(dialog);
358 void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget* dialog,
359 const std::vector<base::FilePath>& files) {
360 *last_opened_path_ = files[0].DirName();
362 if (listener_)
363 listener_->MultiFilesSelected(files, PopParamsForDialog(dialog));
364 gtk_widget_destroy(dialog);
367 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget* dialog) {
368 void* params = PopParamsForDialog(dialog);
369 if (listener_)
370 listener_->FileSelectionCanceled(params);
371 gtk_widget_destroy(dialog);
374 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenHelper(
375 const std::string& title,
376 const base::FilePath& default_path,
377 gfx::NativeWindow parent) {
378 GtkWidget* dialog =
379 gtk_file_chooser_dialog_new(title.c_str(), NULL,
380 GTK_FILE_CHOOSER_ACTION_OPEN,
381 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
382 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
383 NULL);
384 SetGtkTransientForAura(dialog, parent);
385 AddFilters(GTK_FILE_CHOOSER(dialog));
387 if (!default_path.empty()) {
388 if (CallDirectoryExistsOnUIThread(default_path)) {
389 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
390 default_path.value().c_str());
391 } else {
392 // If the file doesn't exist, this will just switch to the correct
393 // directory. That's good enough.
394 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
395 default_path.value().c_str());
397 } else if (!last_opened_path_->empty()) {
398 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
399 last_opened_path_->value().c_str());
401 return dialog;
404 GtkWidget* SelectFileDialogImplGTK::CreateSelectFolderDialog(
405 Type type,
406 const std::string& title,
407 const base::FilePath& default_path,
408 gfx::NativeWindow parent) {
409 std::string title_string = title;
410 if (title_string.empty()) {
411 title_string = (type == SELECT_UPLOAD_FOLDER) ?
412 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE) :
413 l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
415 std::string accept_button_label = (type == SELECT_UPLOAD_FOLDER) ?
416 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON) :
417 GTK_STOCK_OPEN;
419 GtkWidget* dialog =
420 gtk_file_chooser_dialog_new(title_string.c_str(), NULL,
421 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
422 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
423 accept_button_label.c_str(),
424 GTK_RESPONSE_ACCEPT,
425 NULL);
426 SetGtkTransientForAura(dialog, parent);
428 if (!default_path.empty()) {
429 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
430 default_path.value().c_str());
431 } else if (!last_opened_path_->empty()) {
432 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
433 last_opened_path_->value().c_str());
435 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
436 g_signal_connect(dialog, "response",
437 G_CALLBACK(OnSelectSingleFolderDialogResponseThunk), this);
438 return dialog;
441 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenDialog(
442 const std::string& title,
443 const base::FilePath& default_path,
444 gfx::NativeWindow parent) {
445 std::string title_string = !title.empty() ? title :
446 l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE);
448 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
449 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
450 g_signal_connect(dialog, "response",
451 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
452 return dialog;
455 GtkWidget* SelectFileDialogImplGTK::CreateMultiFileOpenDialog(
456 const std::string& title,
457 const base::FilePath& default_path,
458 gfx::NativeWindow parent) {
459 std::string title_string = !title.empty() ? title :
460 l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE);
462 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
463 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
464 g_signal_connect(dialog, "response",
465 G_CALLBACK(OnSelectMultiFileDialogResponseThunk), this);
466 return dialog;
469 GtkWidget* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string& title,
470 const base::FilePath& default_path, gfx::NativeWindow parent) {
471 std::string title_string = !title.empty() ? title :
472 l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
474 GtkWidget* dialog =
475 gtk_file_chooser_dialog_new(title_string.c_str(), NULL,
476 GTK_FILE_CHOOSER_ACTION_SAVE,
477 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
478 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
479 NULL);
480 SetGtkTransientForAura(dialog, parent);
482 AddFilters(GTK_FILE_CHOOSER(dialog));
483 if (!default_path.empty()) {
484 // Since the file may not already exist, we use
485 // set_current_folder() followed by set_current_name(), as per the
486 // recommendation of the GTK docs.
487 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
488 default_path.DirName().value().c_str());
489 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),
490 default_path.BaseName().value().c_str());
491 } else if (!last_saved_path_->empty()) {
492 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
493 last_saved_path_->value().c_str());
495 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
496 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
497 TRUE);
498 g_signal_connect(dialog, "response",
499 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
500 return dialog;
503 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget* dialog) {
504 std::map<GtkWidget*, void*>::iterator iter = params_map_.find(dialog);
505 DCHECK(iter != params_map_.end());
506 void* params = iter->second;
507 params_map_.erase(iter);
508 return params;
511 void SelectFileDialogImplGTK::FileDialogDestroyed(GtkWidget* dialog) {
512 dialogs_.erase(dialog);
514 // Parent may be NULL in a few cases: 1) on shutdown when
515 // AllBrowsersClosed() trigger this handler after all the browser
516 // windows got destroyed, or 2) when the parent tab has been opened by
517 // 'Open Link in New Tab' context menu on a downloadable item and
518 // the tab has no content (see the comment in SelectFile as well).
519 aura::Window* parent = reinterpret_cast<aura::Window*>(
520 g_object_get_data(G_OBJECT(dialog), kAuraTransientParent));
521 if (!parent)
522 return;
523 std::set<aura::Window*>::iterator iter = parents_.find(parent);
524 if (iter != parents_.end()) {
525 (*iter)->RemoveObserver(this);
526 parents_.erase(iter);
527 } else {
528 NOTREACHED();
532 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id) {
533 bool is_cancel = response_id == GTK_RESPONSE_CANCEL ||
534 response_id == GTK_RESPONSE_DELETE_EVENT;
535 if (is_cancel)
536 return true;
538 DCHECK(response_id == GTK_RESPONSE_ACCEPT);
539 return false;
542 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget* dialog,
543 gint response_id,
544 bool allow_folder) {
545 if (IsCancelResponse(response_id)) {
546 FileNotSelected(dialog);
547 return;
550 gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
551 if (!filename) {
552 FileNotSelected(dialog);
553 return;
556 base::FilePath path(filename);
557 g_free(filename);
559 if (allow_folder) {
560 FileSelected(dialog, path);
561 return;
564 if (CallDirectoryExistsOnUIThread(path))
565 FileNotSelected(dialog);
566 else
567 FileSelected(dialog, path);
570 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
571 GtkWidget* dialog, int response_id) {
572 SelectSingleFileHelper(dialog, response_id, false);
575 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
576 GtkWidget* dialog, int response_id) {
577 SelectSingleFileHelper(dialog, response_id, true);
580 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget* dialog,
581 int response_id) {
582 if (IsCancelResponse(response_id)) {
583 FileNotSelected(dialog);
584 return;
587 GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
588 if (!filenames) {
589 FileNotSelected(dialog);
590 return;
593 std::vector<base::FilePath> filenames_fp;
594 for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) {
595 base::FilePath path(static_cast<char*>(iter->data));
596 g_free(iter->data);
597 if (CallDirectoryExistsOnUIThread(path))
598 continue;
599 filenames_fp.push_back(path);
601 g_slist_free(filenames);
603 if (filenames_fp.empty()) {
604 FileNotSelected(dialog);
605 return;
607 MultiFilesSelected(dialog, filenames_fp);
610 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget* dialog) {
611 FileDialogDestroyed(dialog);
614 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget* chooser) {
615 gchar* filename = gtk_file_chooser_get_preview_filename(
616 GTK_FILE_CHOOSER(chooser));
617 if (!filename)
618 return;
619 // This will preserve the image's aspect ratio.
620 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
621 kPreviewHeight, NULL);
622 g_free(filename);
623 if (pixbuf) {
624 gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
625 g_object_unref(pixbuf);
627 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
628 pixbuf ? TRUE : FALSE);
631 } // namespace libgtk2ui