Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / select_file_dialog_impl_gtk2.cc
blobabeab2936f1064e1f1a6323fb2a91d823ad45dc0
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 base::EndsWith(file_info->filename, *file_extension,
36 base::CompareCase::INSENSITIVE_ASCII);
39 // Deletes |data| when gtk_file_filter_add_custom() is done with it.
40 void OnFileFilterDataDestroyed(std::string* file_extension) {
41 delete file_extension;
44 } // namespace
46 namespace libgtk2ui {
48 // Implementation of SelectFileDialog that shows a Gtk common dialog for
49 // choosing a file or folder. This acts as a modal dialog.
50 class SelectFileDialogImplGTK : public SelectFileDialogImpl,
51 public aura::WindowObserver {
52 public:
53 explicit SelectFileDialogImplGTK(Listener* listener,
54 ui::SelectFilePolicy* policy);
56 protected:
57 ~SelectFileDialogImplGTK() override;
59 // BaseShellDialog implementation:
60 bool IsRunning(gfx::NativeWindow parent_window) const override;
62 // SelectFileDialog implementation.
63 // |params| is user data we pass back via the Listener interface.
64 void SelectFileImpl(Type type,
65 const base::string16& title,
66 const base::FilePath& default_path,
67 const FileTypeInfo* file_types,
68 int file_type_index,
69 const base::FilePath::StringType& default_extension,
70 gfx::NativeWindow owning_window,
71 void* params) override;
73 private:
74 bool HasMultipleFileTypeChoicesImpl() override;
76 // Overridden from aura::WindowObserver:
77 void OnWindowDestroying(aura::Window* window) override;
79 // Add the filters from |file_types_| to |chooser|.
80 void AddFilters(GtkFileChooser* chooser);
82 // Notifies the listener that a single file was chosen.
83 void FileSelected(GtkWidget* dialog, const base::FilePath& path);
85 // Notifies the listener that multiple files were chosen.
86 void MultiFilesSelected(GtkWidget* dialog,
87 const std::vector<base::FilePath>& files);
89 // Notifies the listener that no file was chosen (the action was canceled).
90 // Dialog is passed so we can find that |params| pointer that was passed to
91 // us when we were told to show the dialog.
92 void FileNotSelected(GtkWidget* dialog);
94 GtkWidget* CreateSelectFolderDialog(
95 Type type,
96 const std::string& title,
97 const base::FilePath& default_path,
98 gfx::NativeWindow parent);
100 GtkWidget* CreateFileOpenDialog(const std::string& title,
101 const base::FilePath& default_path, gfx::NativeWindow parent);
103 GtkWidget* CreateMultiFileOpenDialog(const std::string& title,
104 const base::FilePath& default_path, gfx::NativeWindow parent);
106 GtkWidget* CreateSaveAsDialog(const std::string& title,
107 const base::FilePath& default_path, gfx::NativeWindow parent);
109 // Removes and returns the |params| associated with |dialog| from
110 // |params_map_|.
111 void* PopParamsForDialog(GtkWidget* dialog);
113 // Take care of internal data structures when a file dialog is destroyed.
114 void FileDialogDestroyed(GtkWidget* dialog);
116 // Check whether response_id corresponds to the user cancelling/closing the
117 // dialog. Used as a helper for the below callbacks.
118 bool IsCancelResponse(gint response_id);
120 // Common function for OnSelectSingleFileDialogResponse and
121 // OnSelectSingleFolderDialogResponse.
122 void SelectSingleFileHelper(GtkWidget* dialog,
123 gint response_id,
124 bool allow_folder);
126 // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
127 GtkWidget* CreateFileOpenHelper(const std::string& title,
128 const base::FilePath& default_path,
129 gfx::NativeWindow parent);
131 // Callback for when the user responds to a Save As or Open File dialog.
132 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
133 OnSelectSingleFileDialogResponse, int);
135 // Callback for when the user responds to a Select Folder dialog.
136 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
137 OnSelectSingleFolderDialogResponse, int);
139 // Callback for when the user responds to a Open Multiple Files dialog.
140 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
141 OnSelectMultiFileDialogResponse, int);
143 // Callback for when the file chooser gets destroyed.
144 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnFileChooserDestroy);
146 // Callback for when we update the preview for the selection.
147 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnUpdatePreview);
149 // A map from dialog windows to the |params| user data associated with them.
150 std::map<GtkWidget*, void*> params_map_;
152 // The GtkImage widget for showing previews of selected images.
153 GtkWidget* preview_;
155 // All our dialogs.
156 std::set<GtkWidget*> dialogs_;
158 // The set of all parent windows for which we are currently running dialogs.
159 std::set<aura::Window*> parents_;
161 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplGTK);
164 // The size of the preview we display for selected image files. We set height
165 // larger than width because generally there is more free space vertically
166 // than horiztonally (setting the preview image will alway expand the width of
167 // the dialog, but usually not the height). The image's aspect ratio will always
168 // be preserved.
169 static const int kPreviewWidth = 256;
170 static const int kPreviewHeight = 512;
172 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
173 Listener* listener, ui::SelectFilePolicy* policy) {
174 return new SelectFileDialogImplGTK(listener, policy);
177 SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener* listener,
178 ui::SelectFilePolicy* policy)
179 : SelectFileDialogImpl(listener, policy),
180 preview_(NULL) {
183 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() {
184 for (std::set<aura::Window*>::iterator iter = parents_.begin();
185 iter != parents_.end(); ++iter) {
186 (*iter)->RemoveObserver(this);
188 while (dialogs_.begin() != dialogs_.end()) {
189 gtk_widget_destroy(*(dialogs_.begin()));
193 bool SelectFileDialogImplGTK::IsRunning(gfx::NativeWindow parent_window) const {
194 return parents_.find(parent_window) != parents_.end();
197 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
198 return file_types_.extensions.size() > 1;
201 void SelectFileDialogImplGTK::OnWindowDestroying(aura::Window* window) {
202 // Remove the |parent| property associated with the |dialog|.
203 for (std::set<GtkWidget*>::iterator it = dialogs_.begin();
204 it != dialogs_.end(); ++it) {
205 aura::Window* parent = GetAuraTransientParent(*it);
206 if (parent == window)
207 ClearAuraTransientParent(*it);
210 std::set<aura::Window*>::iterator iter = parents_.find(window);
211 if (iter != parents_.end()) {
212 (*iter)->RemoveObserver(this);
213 parents_.erase(iter);
217 // We ignore |default_extension|.
218 void SelectFileDialogImplGTK::SelectFileImpl(
219 Type type,
220 const base::string16& title,
221 const base::FilePath& default_path,
222 const FileTypeInfo* file_types,
223 int file_type_index,
224 const base::FilePath::StringType& default_extension,
225 gfx::NativeWindow owning_window,
226 void* params) {
227 type_ = type;
228 // |owning_window| can be null when user right-clicks on a downloadable item
229 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
230 // before downloading.' preference is turned on. (http://crbug.com/29213)
231 if (owning_window) {
232 owning_window->AddObserver(this);
233 parents_.insert(owning_window);
236 std::string title_string = base::UTF16ToUTF8(title);
238 file_type_index_ = file_type_index;
239 if (file_types)
240 file_types_ = *file_types;
242 GtkWidget* dialog = NULL;
243 switch (type) {
244 case SELECT_FOLDER:
245 case SELECT_UPLOAD_FOLDER:
246 dialog = CreateSelectFolderDialog(type, title_string, default_path,
247 owning_window);
248 break;
249 case SELECT_OPEN_FILE:
250 dialog = CreateFileOpenDialog(title_string, default_path, owning_window);
251 break;
252 case SELECT_OPEN_MULTI_FILE:
253 dialog = CreateMultiFileOpenDialog(title_string, default_path,
254 owning_window);
255 break;
256 case SELECT_SAVEAS_FILE:
257 dialog = CreateSaveAsDialog(title_string, default_path, owning_window);
258 break;
259 default:
260 NOTREACHED();
261 return;
263 g_signal_connect(dialog, "delete-event",
264 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
265 dialogs_.insert(dialog);
267 preview_ = gtk_image_new();
268 g_signal_connect(dialog, "destroy",
269 G_CALLBACK(OnFileChooserDestroyThunk), this);
270 g_signal_connect(dialog, "update-preview",
271 G_CALLBACK(OnUpdatePreviewThunk), this);
272 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview_);
274 params_map_[dialog] = params;
276 // TODO(erg): Figure out how to fake GTK window-to-parent modality without
277 // having the parent be a real GtkWindow.
278 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
280 gtk_widget_show_all(dialog);
282 // We need to call gtk_window_present after making the widgets visible to make
283 // sure window gets correctly raised and gets focus.
284 int time = views::X11DesktopHandler::get()->wm_user_time_ms();
285 gtk_window_present_with_time(GTK_WINDOW(dialog), time);
288 void SelectFileDialogImplGTK::AddFilters(GtkFileChooser* chooser) {
289 for (size_t i = 0; i < file_types_.extensions.size(); ++i) {
290 GtkFileFilter* filter = NULL;
291 std::set<std::string> fallback_labels;
293 for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) {
294 const std::string& current_extension = file_types_.extensions[i][j];
295 if (!current_extension.empty()) {
296 if (!filter)
297 filter = gtk_file_filter_new();
298 scoped_ptr<std::string> file_extension(
299 new std::string("." + current_extension));
300 fallback_labels.insert(std::string("*").append(*file_extension));
301 gtk_file_filter_add_custom(
302 filter,
303 GTK_FILE_FILTER_FILENAME,
304 reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive),
305 file_extension.release(),
306 reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed));
309 // We didn't find any non-empty extensions to filter on.
310 if (!filter)
311 continue;
313 // The description vector may be blank, in which case we are supposed to
314 // use some sort of default description based on the filter.
315 if (i < file_types_.extension_description_overrides.size()) {
316 gtk_file_filter_set_name(filter, base::UTF16ToUTF8(
317 file_types_.extension_description_overrides[i]).c_str());
318 } else {
319 // There is no system default filter description so we use
320 // the extensions themselves if the description is blank.
321 std::vector<std::string> fallback_labels_vector(fallback_labels.begin(),
322 fallback_labels.end());
323 std::string fallback_label =
324 base::JoinString(fallback_labels_vector, ",");
325 gtk_file_filter_set_name(filter, fallback_label.c_str());
328 gtk_file_chooser_add_filter(chooser, filter);
329 if (i == file_type_index_ - 1)
330 gtk_file_chooser_set_filter(chooser, filter);
333 // Add the *.* filter, but only if we have added other filters (otherwise it
334 // is implied).
335 if (file_types_.include_all_files && !file_types_.extensions.empty()) {
336 GtkFileFilter* filter = gtk_file_filter_new();
337 gtk_file_filter_add_pattern(filter, "*");
338 gtk_file_filter_set_name(filter,
339 l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES).c_str());
340 gtk_file_chooser_add_filter(chooser, filter);
344 void SelectFileDialogImplGTK::FileSelected(GtkWidget* dialog,
345 const base::FilePath& path) {
346 if (type_ == SELECT_SAVEAS_FILE) {
347 *last_saved_path_ = path.DirName();
348 } else if (type_ == SELECT_OPEN_FILE || type_ == SELECT_FOLDER ||
349 type_ == SELECT_UPLOAD_FOLDER) {
350 *last_opened_path_ = path.DirName();
351 } else {
352 NOTREACHED();
355 if (listener_) {
356 GtkFileFilter* selected_filter =
357 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
358 GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog));
359 int idx = g_slist_index(filters, selected_filter);
360 g_slist_free(filters);
361 listener_->FileSelected(path, idx + 1, PopParamsForDialog(dialog));
363 gtk_widget_destroy(dialog);
366 void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget* dialog,
367 const std::vector<base::FilePath>& files) {
368 *last_opened_path_ = files[0].DirName();
370 if (listener_)
371 listener_->MultiFilesSelected(files, PopParamsForDialog(dialog));
372 gtk_widget_destroy(dialog);
375 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget* dialog) {
376 void* params = PopParamsForDialog(dialog);
377 if (listener_)
378 listener_->FileSelectionCanceled(params);
379 gtk_widget_destroy(dialog);
382 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenHelper(
383 const std::string& title,
384 const base::FilePath& default_path,
385 gfx::NativeWindow parent) {
386 GtkWidget* dialog =
387 gtk_file_chooser_dialog_new(title.c_str(), NULL,
388 GTK_FILE_CHOOSER_ACTION_OPEN,
389 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
390 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
391 NULL);
392 SetGtkTransientForAura(dialog, parent);
393 AddFilters(GTK_FILE_CHOOSER(dialog));
395 if (!default_path.empty()) {
396 if (CallDirectoryExistsOnUIThread(default_path)) {
397 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
398 default_path.value().c_str());
399 } else {
400 // If the file doesn't exist, this will just switch to the correct
401 // directory. That's good enough.
402 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
403 default_path.value().c_str());
405 } else if (!last_opened_path_->empty()) {
406 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
407 last_opened_path_->value().c_str());
409 return dialog;
412 GtkWidget* SelectFileDialogImplGTK::CreateSelectFolderDialog(
413 Type type,
414 const std::string& title,
415 const base::FilePath& default_path,
416 gfx::NativeWindow parent) {
417 std::string title_string = title;
418 if (title_string.empty()) {
419 title_string = (type == SELECT_UPLOAD_FOLDER) ?
420 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE) :
421 l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
423 std::string accept_button_label = (type == SELECT_UPLOAD_FOLDER) ?
424 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON) :
425 GTK_STOCK_OPEN;
427 GtkWidget* dialog =
428 gtk_file_chooser_dialog_new(title_string.c_str(), NULL,
429 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
430 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
431 accept_button_label.c_str(),
432 GTK_RESPONSE_ACCEPT,
433 NULL);
434 SetGtkTransientForAura(dialog, parent);
436 if (!default_path.empty()) {
437 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
438 default_path.value().c_str());
439 } else if (!last_opened_path_->empty()) {
440 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
441 last_opened_path_->value().c_str());
443 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
444 g_signal_connect(dialog, "response",
445 G_CALLBACK(OnSelectSingleFolderDialogResponseThunk), this);
446 return dialog;
449 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenDialog(
450 const std::string& title,
451 const base::FilePath& default_path,
452 gfx::NativeWindow parent) {
453 std::string title_string = !title.empty() ? title :
454 l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE);
456 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
457 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
458 g_signal_connect(dialog, "response",
459 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
460 return dialog;
463 GtkWidget* SelectFileDialogImplGTK::CreateMultiFileOpenDialog(
464 const std::string& title,
465 const base::FilePath& default_path,
466 gfx::NativeWindow parent) {
467 std::string title_string = !title.empty() ? title :
468 l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE);
470 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
471 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
472 g_signal_connect(dialog, "response",
473 G_CALLBACK(OnSelectMultiFileDialogResponseThunk), this);
474 return dialog;
477 GtkWidget* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string& title,
478 const base::FilePath& default_path, gfx::NativeWindow parent) {
479 std::string title_string = !title.empty() ? title :
480 l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
482 GtkWidget* dialog =
483 gtk_file_chooser_dialog_new(title_string.c_str(), NULL,
484 GTK_FILE_CHOOSER_ACTION_SAVE,
485 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
486 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
487 NULL);
488 SetGtkTransientForAura(dialog, parent);
490 AddFilters(GTK_FILE_CHOOSER(dialog));
491 if (!default_path.empty()) {
492 // Since the file may not already exist, we use
493 // set_current_folder() followed by set_current_name(), as per the
494 // recommendation of the GTK docs.
495 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
496 default_path.DirName().value().c_str());
497 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),
498 default_path.BaseName().value().c_str());
499 } else if (!last_saved_path_->empty()) {
500 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
501 last_saved_path_->value().c_str());
503 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
504 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
505 TRUE);
506 g_signal_connect(dialog, "response",
507 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
508 return dialog;
511 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget* dialog) {
512 std::map<GtkWidget*, void*>::iterator iter = params_map_.find(dialog);
513 DCHECK(iter != params_map_.end());
514 void* params = iter->second;
515 params_map_.erase(iter);
516 return params;
519 void SelectFileDialogImplGTK::FileDialogDestroyed(GtkWidget* dialog) {
520 dialogs_.erase(dialog);
522 // Parent may be NULL in a few cases: 1) on shutdown when
523 // AllBrowsersClosed() trigger this handler after all the browser
524 // windows got destroyed, or 2) when the parent tab has been opened by
525 // 'Open Link in New Tab' context menu on a downloadable item and
526 // the tab has no content (see the comment in SelectFile as well).
527 aura::Window* parent = GetAuraTransientParent(dialog);
528 if (!parent)
529 return;
530 std::set<aura::Window*>::iterator iter = parents_.find(parent);
531 if (iter != parents_.end()) {
532 (*iter)->RemoveObserver(this);
533 parents_.erase(iter);
534 } else {
535 NOTREACHED();
539 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id) {
540 bool is_cancel = response_id == GTK_RESPONSE_CANCEL ||
541 response_id == GTK_RESPONSE_DELETE_EVENT;
542 if (is_cancel)
543 return true;
545 DCHECK(response_id == GTK_RESPONSE_ACCEPT);
546 return false;
549 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget* dialog,
550 gint response_id,
551 bool allow_folder) {
552 if (IsCancelResponse(response_id)) {
553 FileNotSelected(dialog);
554 return;
557 gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
558 if (!filename) {
559 FileNotSelected(dialog);
560 return;
563 base::FilePath path(filename);
564 g_free(filename);
566 if (allow_folder) {
567 FileSelected(dialog, path);
568 return;
571 if (CallDirectoryExistsOnUIThread(path))
572 FileNotSelected(dialog);
573 else
574 FileSelected(dialog, path);
577 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
578 GtkWidget* dialog, int response_id) {
579 SelectSingleFileHelper(dialog, response_id, false);
582 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
583 GtkWidget* dialog, int response_id) {
584 SelectSingleFileHelper(dialog, response_id, true);
587 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget* dialog,
588 int response_id) {
589 if (IsCancelResponse(response_id)) {
590 FileNotSelected(dialog);
591 return;
594 GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
595 if (!filenames) {
596 FileNotSelected(dialog);
597 return;
600 std::vector<base::FilePath> filenames_fp;
601 for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) {
602 base::FilePath path(static_cast<char*>(iter->data));
603 g_free(iter->data);
604 if (CallDirectoryExistsOnUIThread(path))
605 continue;
606 filenames_fp.push_back(path);
608 g_slist_free(filenames);
610 if (filenames_fp.empty()) {
611 FileNotSelected(dialog);
612 return;
614 MultiFilesSelected(dialog, filenames_fp);
617 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget* dialog) {
618 FileDialogDestroyed(dialog);
621 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget* chooser) {
622 gchar* filename = gtk_file_chooser_get_preview_filename(
623 GTK_FILE_CHOOSER(chooser));
624 if (!filename)
625 return;
626 // This will preserve the image's aspect ratio.
627 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
628 kPreviewHeight, NULL);
629 g_free(filename);
630 if (pixbuf) {
631 gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
632 g_object_unref(pixbuf);
634 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
635 pixbuf ? TRUE : FALSE);
638 } // namespace libgtk2ui