Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / select_file_dialog_impl_gtk2.cc
blob7a9d112961e869096df1e61ade01ec25ae9821ba
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 <gtk/gtk.h>
6 #include <map>
7 #include <set>
8 #include <vector>
10 // Xlib defines RootWindow
11 #undef RootWindow
13 #include "base/logging.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/sys_string_conversions.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/threading/thread.h"
20 #include "base/threading/thread_restrictions.h"
21 #include "chrome/browser/ui/libgtk2ui/gtk2_signal.h"
22 #include "chrome/browser/ui/libgtk2ui/gtk2_util.h"
23 #include "chrome/browser/ui/libgtk2ui/select_file_dialog_impl.h"
24 #include "ui/aura/window_observer.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/shell_dialogs/select_file_dialog.h"
27 #include "ui/strings/grit/ui_strings.h"
28 #include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
30 namespace {
32 // Makes sure that .jpg also shows .JPG.
33 gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info,
34 std::string* file_extension) {
35 return EndsWith(file_info->filename, *file_extension, false);
38 // Deletes |data| when gtk_file_filter_add_custom() is done with it.
39 void OnFileFilterDataDestroyed(std::string* file_extension) {
40 delete file_extension;
43 } // namespace
45 namespace libgtk2ui {
47 // Implementation of SelectFileDialog that shows a Gtk common dialog for
48 // choosing a file or folder. This acts as a modal dialog.
49 class SelectFileDialogImplGTK : public SelectFileDialogImpl,
50 public aura::WindowObserver {
51 public:
52 explicit SelectFileDialogImplGTK(Listener* listener,
53 ui::SelectFilePolicy* policy);
55 protected:
56 ~SelectFileDialogImplGTK() override;
58 // BaseShellDialog implementation:
59 bool IsRunning(gfx::NativeWindow parent_window) const override;
61 // SelectFileDialog implementation.
62 // |params| is user data we pass back via the Listener interface.
63 void SelectFileImpl(Type type,
64 const base::string16& title,
65 const base::FilePath& default_path,
66 const FileTypeInfo* file_types,
67 int file_type_index,
68 const base::FilePath::StringType& default_extension,
69 gfx::NativeWindow owning_window,
70 void* params) override;
72 private:
73 bool HasMultipleFileTypeChoicesImpl() override;
75 // Overridden from aura::WindowObserver:
76 void OnWindowDestroying(aura::Window* window) override;
78 // Add the filters from |file_types_| to |chooser|.
79 void AddFilters(GtkFileChooser* chooser);
81 // Notifies the listener that a single file was chosen.
82 void FileSelected(GtkWidget* dialog, const base::FilePath& path);
84 // Notifies the listener that multiple files were chosen.
85 void MultiFilesSelected(GtkWidget* dialog,
86 const std::vector<base::FilePath>& files);
88 // Notifies the listener that no file was chosen (the action was canceled).
89 // Dialog is passed so we can find that |params| pointer that was passed to
90 // us when we were told to show the dialog.
91 void FileNotSelected(GtkWidget* dialog);
93 GtkWidget* CreateSelectFolderDialog(
94 Type type,
95 const std::string& title,
96 const base::FilePath& default_path,
97 gfx::NativeWindow parent);
99 GtkWidget* CreateFileOpenDialog(const std::string& title,
100 const base::FilePath& default_path, gfx::NativeWindow parent);
102 GtkWidget* CreateMultiFileOpenDialog(const std::string& title,
103 const base::FilePath& default_path, gfx::NativeWindow parent);
105 GtkWidget* CreateSaveAsDialog(const std::string& title,
106 const base::FilePath& default_path, gfx::NativeWindow parent);
108 // Removes and returns the |params| associated with |dialog| from
109 // |params_map_|.
110 void* PopParamsForDialog(GtkWidget* dialog);
112 // Take care of internal data structures when a file dialog is destroyed.
113 void FileDialogDestroyed(GtkWidget* dialog);
115 // Check whether response_id corresponds to the user cancelling/closing the
116 // dialog. Used as a helper for the below callbacks.
117 bool IsCancelResponse(gint response_id);
119 // Common function for OnSelectSingleFileDialogResponse and
120 // OnSelectSingleFolderDialogResponse.
121 void SelectSingleFileHelper(GtkWidget* dialog,
122 gint response_id,
123 bool allow_folder);
125 // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
126 GtkWidget* CreateFileOpenHelper(const std::string& title,
127 const base::FilePath& default_path,
128 gfx::NativeWindow parent);
130 // Callback for when the user responds to a Save As or Open File dialog.
131 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
132 OnSelectSingleFileDialogResponse, int);
134 // Callback for when the user responds to a Select Folder dialog.
135 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
136 OnSelectSingleFolderDialogResponse, int);
138 // Callback for when the user responds to a Open Multiple Files dialog.
139 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
140 OnSelectMultiFileDialogResponse, int);
142 // Callback for when the file chooser gets destroyed.
143 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnFileChooserDestroy);
145 // Callback for when we update the preview for the selection.
146 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnUpdatePreview);
148 // A map from dialog windows to the |params| user data associated with them.
149 std::map<GtkWidget*, void*> params_map_;
151 // The GtkImage widget for showing previews of selected images.
152 GtkWidget* preview_;
154 // All our dialogs.
155 std::set<GtkWidget*> dialogs_;
157 // The set of all parent windows for which we are currently running dialogs.
158 std::set<aura::Window*> parents_;
160 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplGTK);
163 // The size of the preview we display for selected image files. We set height
164 // larger than width because generally there is more free space vertically
165 // than horiztonally (setting the preview image will alway expand the width of
166 // the dialog, but usually not the height). The image's aspect ratio will always
167 // be preserved.
168 static const int kPreviewWidth = 256;
169 static const int kPreviewHeight = 512;
171 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
172 Listener* listener, ui::SelectFilePolicy* policy) {
173 return new SelectFileDialogImplGTK(listener, policy);
176 SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener* listener,
177 ui::SelectFilePolicy* policy)
178 : SelectFileDialogImpl(listener, policy),
179 preview_(NULL) {
182 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() {
183 for (std::set<aura::Window*>::iterator iter = parents_.begin();
184 iter != parents_.end(); ++iter) {
185 (*iter)->RemoveObserver(this);
187 while (dialogs_.begin() != dialogs_.end()) {
188 gtk_widget_destroy(*(dialogs_.begin()));
192 bool SelectFileDialogImplGTK::IsRunning(gfx::NativeWindow parent_window) const {
193 return parents_.find(parent_window) != parents_.end();
196 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
197 return file_types_.extensions.size() > 1;
200 void SelectFileDialogImplGTK::OnWindowDestroying(aura::Window* window) {
201 // Remove the |parent| property associated with the |dialog|.
202 for (std::set<GtkWidget*>::iterator it = dialogs_.begin();
203 it != dialogs_.end(); ++it) {
204 aura::Window* parent = GetAuraTransientParent(*it);
205 if (parent == window)
206 ClearAuraTransientParent(*it);
209 std::set<aura::Window*>::iterator iter = parents_.find(window);
210 if (iter != parents_.end()) {
211 (*iter)->RemoveObserver(this);
212 parents_.erase(iter);
216 // We ignore |default_extension|.
217 void SelectFileDialogImplGTK::SelectFileImpl(
218 Type type,
219 const base::string16& title,
220 const base::FilePath& default_path,
221 const FileTypeInfo* file_types,
222 int file_type_index,
223 const base::FilePath::StringType& default_extension,
224 gfx::NativeWindow owning_window,
225 void* params) {
226 type_ = type;
227 // |owning_window| can be null when user right-clicks on a downloadable item
228 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
229 // before downloading.' preference is turned on. (http://crbug.com/29213)
230 if (owning_window) {
231 owning_window->AddObserver(this);
232 parents_.insert(owning_window);
235 std::string title_string = base::UTF16ToUTF8(title);
237 file_type_index_ = file_type_index;
238 if (file_types)
239 file_types_ = *file_types;
241 GtkWidget* dialog = NULL;
242 switch (type) {
243 case SELECT_FOLDER:
244 case SELECT_UPLOAD_FOLDER:
245 dialog = CreateSelectFolderDialog(type, title_string, default_path,
246 owning_window);
247 break;
248 case SELECT_OPEN_FILE:
249 dialog = CreateFileOpenDialog(title_string, default_path, owning_window);
250 break;
251 case SELECT_OPEN_MULTI_FILE:
252 dialog = CreateMultiFileOpenDialog(title_string, default_path,
253 owning_window);
254 break;
255 case SELECT_SAVEAS_FILE:
256 dialog = CreateSaveAsDialog(title_string, default_path, owning_window);
257 break;
258 default:
259 NOTREACHED();
260 return;
262 g_signal_connect(dialog, "delete-event",
263 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
264 dialogs_.insert(dialog);
266 preview_ = gtk_image_new();
267 g_signal_connect(dialog, "destroy",
268 G_CALLBACK(OnFileChooserDestroyThunk), this);
269 g_signal_connect(dialog, "update-preview",
270 G_CALLBACK(OnUpdatePreviewThunk), this);
271 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview_);
273 params_map_[dialog] = params;
275 // TODO(erg): Figure out how to fake GTK window-to-parent modality without
276 // having the parent be a real GtkWindow.
277 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
279 gtk_widget_show_all(dialog);
281 // We need to call gtk_window_present after making the widgets visible to make
282 // sure window gets correctly raised and gets focus.
283 int time = views::X11DesktopHandler::get()->wm_user_time_ms();
284 gtk_window_present_with_time(GTK_WINDOW(dialog), time);
287 void SelectFileDialogImplGTK::AddFilters(GtkFileChooser* chooser) {
288 for (size_t i = 0; i < file_types_.extensions.size(); ++i) {
289 GtkFileFilter* filter = NULL;
290 std::set<std::string> fallback_labels;
292 for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) {
293 const std::string& current_extension = file_types_.extensions[i][j];
294 if (!current_extension.empty()) {
295 if (!filter)
296 filter = gtk_file_filter_new();
297 scoped_ptr<std::string> file_extension(
298 new std::string("." + current_extension));
299 fallback_labels.insert(std::string("*").append(*file_extension));
300 gtk_file_filter_add_custom(
301 filter,
302 GTK_FILE_FILTER_FILENAME,
303 reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive),
304 file_extension.release(),
305 reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed));
308 // We didn't find any non-empty extensions to filter on.
309 if (!filter)
310 continue;
312 // The description vector may be blank, in which case we are supposed to
313 // use some sort of default description based on the filter.
314 if (i < file_types_.extension_description_overrides.size()) {
315 gtk_file_filter_set_name(filter, base::UTF16ToUTF8(
316 file_types_.extension_description_overrides[i]).c_str());
317 } else {
318 // There is no system default filter description so we use
319 // the extensions themselves if the description is blank.
320 std::vector<std::string> fallback_labels_vector(fallback_labels.begin(),
321 fallback_labels.end());
322 std::string fallback_label = JoinString(fallback_labels_vector, ',');
323 gtk_file_filter_set_name(filter, fallback_label.c_str());
326 gtk_file_chooser_add_filter(chooser, filter);
327 if (i == file_type_index_ - 1)
328 gtk_file_chooser_set_filter(chooser, filter);
331 // Add the *.* filter, but only if we have added other filters (otherwise it
332 // is implied).
333 if (file_types_.include_all_files && !file_types_.extensions.empty()) {
334 GtkFileFilter* filter = gtk_file_filter_new();
335 gtk_file_filter_add_pattern(filter, "*");
336 gtk_file_filter_set_name(filter,
337 l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES).c_str());
338 gtk_file_chooser_add_filter(chooser, filter);
342 void SelectFileDialogImplGTK::FileSelected(GtkWidget* dialog,
343 const base::FilePath& path) {
344 if (type_ == SELECT_SAVEAS_FILE) {
345 *last_saved_path_ = path.DirName();
346 } else if (type_ == SELECT_OPEN_FILE || type_ == SELECT_FOLDER ||
347 type_ == SELECT_UPLOAD_FOLDER) {
348 *last_opened_path_ = path.DirName();
349 } else {
350 NOTREACHED();
353 if (listener_) {
354 GtkFileFilter* selected_filter =
355 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
356 GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog));
357 int idx = g_slist_index(filters, selected_filter);
358 g_slist_free(filters);
359 listener_->FileSelected(path, idx + 1, PopParamsForDialog(dialog));
361 gtk_widget_destroy(dialog);
364 void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget* dialog,
365 const std::vector<base::FilePath>& files) {
366 *last_opened_path_ = files[0].DirName();
368 if (listener_)
369 listener_->MultiFilesSelected(files, PopParamsForDialog(dialog));
370 gtk_widget_destroy(dialog);
373 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget* dialog) {
374 void* params = PopParamsForDialog(dialog);
375 if (listener_)
376 listener_->FileSelectionCanceled(params);
377 gtk_widget_destroy(dialog);
380 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenHelper(
381 const std::string& title,
382 const base::FilePath& default_path,
383 gfx::NativeWindow parent) {
384 GtkWidget* dialog =
385 gtk_file_chooser_dialog_new(title.c_str(), NULL,
386 GTK_FILE_CHOOSER_ACTION_OPEN,
387 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
388 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
389 NULL);
390 SetGtkTransientForAura(dialog, parent);
391 AddFilters(GTK_FILE_CHOOSER(dialog));
393 if (!default_path.empty()) {
394 if (CallDirectoryExistsOnUIThread(default_path)) {
395 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
396 default_path.value().c_str());
397 } else {
398 // If the file doesn't exist, this will just switch to the correct
399 // directory. That's good enough.
400 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
401 default_path.value().c_str());
403 } else if (!last_opened_path_->empty()) {
404 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
405 last_opened_path_->value().c_str());
407 return dialog;
410 GtkWidget* SelectFileDialogImplGTK::CreateSelectFolderDialog(
411 Type type,
412 const std::string& title,
413 const base::FilePath& default_path,
414 gfx::NativeWindow parent) {
415 std::string title_string = title;
416 if (title_string.empty()) {
417 title_string = (type == SELECT_UPLOAD_FOLDER) ?
418 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE) :
419 l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
421 std::string accept_button_label = (type == SELECT_UPLOAD_FOLDER) ?
422 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON) :
423 GTK_STOCK_OPEN;
425 GtkWidget* dialog =
426 gtk_file_chooser_dialog_new(title_string.c_str(), NULL,
427 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
428 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
429 accept_button_label.c_str(),
430 GTK_RESPONSE_ACCEPT,
431 NULL);
432 SetGtkTransientForAura(dialog, parent);
434 if (!default_path.empty()) {
435 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
436 default_path.value().c_str());
437 } else if (!last_opened_path_->empty()) {
438 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
439 last_opened_path_->value().c_str());
441 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
442 g_signal_connect(dialog, "response",
443 G_CALLBACK(OnSelectSingleFolderDialogResponseThunk), this);
444 return dialog;
447 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenDialog(
448 const std::string& title,
449 const base::FilePath& default_path,
450 gfx::NativeWindow parent) {
451 std::string title_string = !title.empty() ? title :
452 l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE);
454 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
455 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
456 g_signal_connect(dialog, "response",
457 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
458 return dialog;
461 GtkWidget* SelectFileDialogImplGTK::CreateMultiFileOpenDialog(
462 const std::string& title,
463 const base::FilePath& default_path,
464 gfx::NativeWindow parent) {
465 std::string title_string = !title.empty() ? title :
466 l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE);
468 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
469 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
470 g_signal_connect(dialog, "response",
471 G_CALLBACK(OnSelectMultiFileDialogResponseThunk), this);
472 return dialog;
475 GtkWidget* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string& title,
476 const base::FilePath& default_path, gfx::NativeWindow parent) {
477 std::string title_string = !title.empty() ? title :
478 l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
480 GtkWidget* dialog =
481 gtk_file_chooser_dialog_new(title_string.c_str(), NULL,
482 GTK_FILE_CHOOSER_ACTION_SAVE,
483 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
484 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
485 NULL);
486 SetGtkTransientForAura(dialog, parent);
488 AddFilters(GTK_FILE_CHOOSER(dialog));
489 if (!default_path.empty()) {
490 // Since the file may not already exist, we use
491 // set_current_folder() followed by set_current_name(), as per the
492 // recommendation of the GTK docs.
493 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
494 default_path.DirName().value().c_str());
495 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),
496 default_path.BaseName().value().c_str());
497 } else if (!last_saved_path_->empty()) {
498 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
499 last_saved_path_->value().c_str());
501 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
502 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
503 TRUE);
504 g_signal_connect(dialog, "response",
505 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
506 return dialog;
509 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget* dialog) {
510 std::map<GtkWidget*, void*>::iterator iter = params_map_.find(dialog);
511 DCHECK(iter != params_map_.end());
512 void* params = iter->second;
513 params_map_.erase(iter);
514 return params;
517 void SelectFileDialogImplGTK::FileDialogDestroyed(GtkWidget* dialog) {
518 dialogs_.erase(dialog);
520 // Parent may be NULL in a few cases: 1) on shutdown when
521 // AllBrowsersClosed() trigger this handler after all the browser
522 // windows got destroyed, or 2) when the parent tab has been opened by
523 // 'Open Link in New Tab' context menu on a downloadable item and
524 // the tab has no content (see the comment in SelectFile as well).
525 aura::Window* parent = GetAuraTransientParent(dialog);
526 if (!parent)
527 return;
528 std::set<aura::Window*>::iterator iter = parents_.find(parent);
529 if (iter != parents_.end()) {
530 (*iter)->RemoveObserver(this);
531 parents_.erase(iter);
532 } else {
533 NOTREACHED();
537 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id) {
538 bool is_cancel = response_id == GTK_RESPONSE_CANCEL ||
539 response_id == GTK_RESPONSE_DELETE_EVENT;
540 if (is_cancel)
541 return true;
543 DCHECK(response_id == GTK_RESPONSE_ACCEPT);
544 return false;
547 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget* dialog,
548 gint response_id,
549 bool allow_folder) {
550 if (IsCancelResponse(response_id)) {
551 FileNotSelected(dialog);
552 return;
555 gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
556 if (!filename) {
557 FileNotSelected(dialog);
558 return;
561 base::FilePath path(filename);
562 g_free(filename);
564 if (allow_folder) {
565 FileSelected(dialog, path);
566 return;
569 if (CallDirectoryExistsOnUIThread(path))
570 FileNotSelected(dialog);
571 else
572 FileSelected(dialog, path);
575 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
576 GtkWidget* dialog, int response_id) {
577 SelectSingleFileHelper(dialog, response_id, false);
580 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
581 GtkWidget* dialog, int response_id) {
582 SelectSingleFileHelper(dialog, response_id, true);
585 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget* dialog,
586 int response_id) {
587 if (IsCancelResponse(response_id)) {
588 FileNotSelected(dialog);
589 return;
592 GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
593 if (!filenames) {
594 FileNotSelected(dialog);
595 return;
598 std::vector<base::FilePath> filenames_fp;
599 for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) {
600 base::FilePath path(static_cast<char*>(iter->data));
601 g_free(iter->data);
602 if (CallDirectoryExistsOnUIThread(path))
603 continue;
604 filenames_fp.push_back(path);
606 g_slist_free(filenames);
608 if (filenames_fp.empty()) {
609 FileNotSelected(dialog);
610 return;
612 MultiFilesSelected(dialog, filenames_fp);
615 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget* dialog) {
616 FileDialogDestroyed(dialog);
619 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget* chooser) {
620 gchar* filename = gtk_file_chooser_get_preview_filename(
621 GTK_FILE_CHOOSER(chooser));
622 if (!filename)
623 return;
624 // This will preserve the image's aspect ratio.
625 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
626 kPreviewHeight, NULL);
627 g_free(filename);
628 if (pixbuf) {
629 gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
630 g_object_unref(pixbuf);
632 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
633 pixbuf ? TRUE : FALSE);
636 } // namespace libgtk2ui