Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / libgtk2ui / select_file_dialog_impl_gtk2.cc
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 <gtk/gtk.h>
6 #include <map>
7 #include <set>
8 #include <vector>
9
10 // Xlib defines RootWindow
11 #undef RootWindow
12
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 "grit/ui_strings.h"
25 #include "ui/aura/window_observer.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/shell_dialogs/select_file_dialog.h"
28
29 namespace {
30
31 // Makes sure that .jpg also shows .JPG.
32 gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info,
33                                    std::string* file_extension) {
34   return EndsWith(file_info->filename, *file_extension, false);
35 }
36
37 // Deletes |data| when gtk_file_filter_add_custom() is done with it.
38 void OnFileFilterDataDestroyed(std::string* file_extension) {
39   delete file_extension;
40 }
41
42 }  // namespace
43
44 namespace libgtk2ui {
45
46 // Implementation of SelectFileDialog that shows a Gtk common dialog for
47 // choosing a file or folder. This acts as a modal dialog.
48 class SelectFileDialogImplGTK : public SelectFileDialogImpl,
49                                 public aura::WindowObserver {
50  public:
51   explicit SelectFileDialogImplGTK(Listener* listener,
52                                    ui::SelectFilePolicy* policy);
53
54  protected:
55   virtual ~SelectFileDialogImplGTK();
56
57   // BaseShellDialog implementation:
58   virtual bool IsRunning(gfx::NativeWindow parent_window) const OVERRIDE;
59
60   // SelectFileDialog implementation.
61   // |params| is user data we pass back via the Listener interface.
62   virtual void SelectFileImpl(
63       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;
71
72  private:
73   virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE;
74
75   // Overridden from aura::WindowObserver:
76   virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
77
78   // Add the filters from |file_types_| to |chooser|.
79   void AddFilters(GtkFileChooser* chooser);
80
81   // Notifies the listener that a single file was chosen.
82   void FileSelected(GtkWidget* dialog, const base::FilePath& path);
83
84   // Notifies the listener that multiple files were chosen.
85   void MultiFilesSelected(GtkWidget* dialog,
86                           const std::vector<base::FilePath>& files);
87
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);
92
93   GtkWidget* CreateSelectFolderDialog(
94       Type type,
95       const std::string& title,
96       const base::FilePath& default_path,
97       gfx::NativeWindow parent);
98
99   GtkWidget* CreateFileOpenDialog(const std::string& title,
100       const base::FilePath& default_path, gfx::NativeWindow parent);
101
102   GtkWidget* CreateMultiFileOpenDialog(const std::string& title,
103       const base::FilePath& default_path, gfx::NativeWindow parent);
104
105   GtkWidget* CreateSaveAsDialog(const std::string& title,
106       const base::FilePath& default_path, gfx::NativeWindow parent);
107
108   // Removes and returns the |params| associated with |dialog| from
109   // |params_map_|.
110   void* PopParamsForDialog(GtkWidget* dialog);
111
112   // Take care of internal data structures when a file dialog is destroyed.
113   void FileDialogDestroyed(GtkWidget* dialog);
114
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);
118
119   // Common function for OnSelectSingleFileDialogResponse and
120   // OnSelectSingleFolderDialogResponse.
121   void SelectSingleFileHelper(GtkWidget* dialog,
122                               gint response_id,
123                               bool allow_folder);
124
125   // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
126   GtkWidget* CreateFileOpenHelper(const std::string& title,
127                                   const base::FilePath& default_path,
128                                   gfx::NativeWindow parent);
129
130   // Callback for when the user responds to a Save As or Open File dialog.
131   CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
132                        OnSelectSingleFileDialogResponse, int);
133
134   // Callback for when the user responds to a Select Folder dialog.
135   CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
136                        OnSelectSingleFolderDialogResponse, int);
137
138   // Callback for when the user responds to a Open Multiple Files dialog.
139   CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
140                        OnSelectMultiFileDialogResponse, int);
141
142   // Callback for when the file chooser gets destroyed.
143   CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnFileChooserDestroy);
144
145   // Callback for when we update the preview for the selection.
146   CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnUpdatePreview);
147
148   // A map from dialog windows to the |params| user data associated with them.
149   std::map<GtkWidget*, void*> params_map_;
150
151   // The GtkImage widget for showing previews of selected images.
152   GtkWidget* preview_;
153
154   // All our dialogs.
155   std::set<GtkWidget*> dialogs_;
156
157   // The set of all parent windows for which we are currently running dialogs.
158   std::set<aura::Window*> parents_;
159
160   DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplGTK);
161 };
162
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;
170
171 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
172     Listener* listener, ui::SelectFilePolicy* policy) {
173   return new SelectFileDialogImplGTK(listener, policy);
174 }
175
176 SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener* listener,
177                                                  ui::SelectFilePolicy* policy)
178     : SelectFileDialogImpl(listener, policy),
179       preview_(NULL) {
180 }
181
182 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() {
183   while (dialogs_.begin() != dialogs_.end()) {
184     gtk_widget_destroy(*(dialogs_.begin()));
185   }
186 }
187
188 bool SelectFileDialogImplGTK::IsRunning(gfx::NativeWindow parent_window) const {
189   return parents_.find(parent_window) != parents_.end();
190 }
191
192 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
193   return file_types_.extensions.size() > 1;
194 }
195
196 void SelectFileDialogImplGTK::OnWindowDestroying(aura::Window* window) {
197   // Remove the |parent| property associated with the |dialog|.
198   for (std::set<GtkWidget*>::iterator it = dialogs_.begin();
199        it != dialogs_.end(); ++it) {
200     aura::Window* parent = GetAuraTransientParent(*it);
201     if (parent == window)
202       ClearAuraTransientParent(*it);
203   }
204
205   std::set<aura::Window*>::iterator iter = parents_.find(window);
206   if (iter != parents_.end()) {
207     (*iter)->RemoveObserver(this);
208     parents_.erase(iter);
209   }
210 }
211
212 // We ignore |default_extension|.
213 void SelectFileDialogImplGTK::SelectFileImpl(
214     Type type,
215     const base::string16& title,
216     const base::FilePath& default_path,
217     const FileTypeInfo* file_types,
218     int file_type_index,
219     const base::FilePath::StringType& default_extension,
220     gfx::NativeWindow owning_window,
221     void* params) {
222   type_ = type;
223   // |owning_window| can be null when user right-clicks on a downloadable item
224   // and chooses 'Open Link in New Tab' when 'Ask where to save each file
225   // before downloading.' preference is turned on. (http://crbug.com/29213)
226   if (owning_window) {
227     owning_window->AddObserver(this);
228     parents_.insert(owning_window);
229   }
230
231   std::string title_string = base::UTF16ToUTF8(title);
232
233   file_type_index_ = file_type_index;
234   if (file_types)
235     file_types_ = *file_types;
236
237   GtkWidget* dialog = NULL;
238   switch (type) {
239     case SELECT_FOLDER:
240     case SELECT_UPLOAD_FOLDER:
241       dialog = CreateSelectFolderDialog(type, title_string, default_path,
242                                         owning_window);
243       break;
244     case SELECT_OPEN_FILE:
245       dialog = CreateFileOpenDialog(title_string, default_path, owning_window);
246       break;
247     case SELECT_OPEN_MULTI_FILE:
248       dialog = CreateMultiFileOpenDialog(title_string, default_path,
249                                          owning_window);
250       break;
251     case SELECT_SAVEAS_FILE:
252       dialog = CreateSaveAsDialog(title_string, default_path, owning_window);
253       break;
254     default:
255       NOTREACHED();
256       return;
257   }
258   g_signal_connect(dialog, "delete-event",
259                    G_CALLBACK(gtk_widget_hide_on_delete), NULL);
260   dialogs_.insert(dialog);
261
262   preview_ = gtk_image_new();
263   g_signal_connect(dialog, "destroy",
264                    G_CALLBACK(OnFileChooserDestroyThunk), this);
265   g_signal_connect(dialog, "update-preview",
266                    G_CALLBACK(OnUpdatePreviewThunk), this);
267   gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview_);
268
269   params_map_[dialog] = params;
270
271   // TODO(erg): Figure out how to fake GTK window-to-parent modality without
272   // having the parent be a real GtkWindow.
273   gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
274
275   gtk_widget_show_all(dialog);
276 }
277
278 void SelectFileDialogImplGTK::AddFilters(GtkFileChooser* chooser) {
279   for (size_t i = 0; i < file_types_.extensions.size(); ++i) {
280     GtkFileFilter* filter = NULL;
281     std::set<std::string> fallback_labels;
282
283     for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) {
284       const std::string& current_extension = file_types_.extensions[i][j];
285       if (!current_extension.empty()) {
286         if (!filter)
287           filter = gtk_file_filter_new();
288         scoped_ptr<std::string> file_extension(
289             new std::string("." + current_extension));
290         fallback_labels.insert(std::string("*").append(*file_extension));
291         gtk_file_filter_add_custom(
292             filter,
293             GTK_FILE_FILTER_FILENAME,
294             reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive),
295             file_extension.release(),
296             reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed));
297       }
298     }
299     // We didn't find any non-empty extensions to filter on.
300     if (!filter)
301       continue;
302
303     // The description vector may be blank, in which case we are supposed to
304     // use some sort of default description based on the filter.
305     if (i < file_types_.extension_description_overrides.size()) {
306       gtk_file_filter_set_name(filter, base::UTF16ToUTF8(
307           file_types_.extension_description_overrides[i]).c_str());
308     } else {
309       // There is no system default filter description so we use
310       // the extensions themselves if the description is blank.
311       std::vector<std::string> fallback_labels_vector(fallback_labels.begin(),
312                                                       fallback_labels.end());
313       std::string fallback_label = JoinString(fallback_labels_vector, ',');
314       gtk_file_filter_set_name(filter, fallback_label.c_str());
315     }
316
317     gtk_file_chooser_add_filter(chooser, filter);
318     if (i == file_type_index_ - 1)
319       gtk_file_chooser_set_filter(chooser, filter);
320   }
321
322   // Add the *.* filter, but only if we have added other filters (otherwise it
323   // is implied).
324   if (file_types_.include_all_files && !file_types_.extensions.empty()) {
325     GtkFileFilter* filter = gtk_file_filter_new();
326     gtk_file_filter_add_pattern(filter, "*");
327     gtk_file_filter_set_name(filter,
328         l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES).c_str());
329     gtk_file_chooser_add_filter(chooser, filter);
330   }
331 }
332
333 void SelectFileDialogImplGTK::FileSelected(GtkWidget* dialog,
334                                            const base::FilePath& path) {
335   if (type_ == SELECT_SAVEAS_FILE) {
336     *last_saved_path_ = path.DirName();
337   } else if (type_ == SELECT_OPEN_FILE || type_ == SELECT_FOLDER ||
338              type_ == SELECT_UPLOAD_FOLDER) {
339     *last_opened_path_ = path.DirName();
340   } else {
341     NOTREACHED();
342   }
343
344   if (listener_) {
345     GtkFileFilter* selected_filter =
346         gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
347     GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog));
348     int idx = g_slist_index(filters, selected_filter);
349     g_slist_free(filters);
350     listener_->FileSelected(path, idx + 1, PopParamsForDialog(dialog));
351   }
352   gtk_widget_destroy(dialog);
353 }
354
355 void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget* dialog,
356     const std::vector<base::FilePath>& files) {
357     *last_opened_path_ = files[0].DirName();
358
359   if (listener_)
360     listener_->MultiFilesSelected(files, PopParamsForDialog(dialog));
361   gtk_widget_destroy(dialog);
362 }
363
364 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget* dialog) {
365   void* params = PopParamsForDialog(dialog);
366   if (listener_)
367     listener_->FileSelectionCanceled(params);
368   gtk_widget_destroy(dialog);
369 }
370
371 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenHelper(
372     const std::string& title,
373     const base::FilePath& default_path,
374     gfx::NativeWindow parent) {
375   GtkWidget* dialog =
376       gtk_file_chooser_dialog_new(title.c_str(), NULL,
377                                   GTK_FILE_CHOOSER_ACTION_OPEN,
378                                   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
379                                   GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
380                                   NULL);
381   SetGtkTransientForAura(dialog, parent);
382   AddFilters(GTK_FILE_CHOOSER(dialog));
383
384   if (!default_path.empty()) {
385     if (CallDirectoryExistsOnUIThread(default_path)) {
386       gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
387                                           default_path.value().c_str());
388     } else {
389       // If the file doesn't exist, this will just switch to the correct
390       // directory. That's good enough.
391       gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
392                                     default_path.value().c_str());
393     }
394   } else if (!last_opened_path_->empty()) {
395     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
396                                         last_opened_path_->value().c_str());
397   }
398   return dialog;
399 }
400
401 GtkWidget* SelectFileDialogImplGTK::CreateSelectFolderDialog(
402     Type type,
403     const std::string& title,
404     const base::FilePath& default_path,
405     gfx::NativeWindow parent) {
406   std::string title_string = title;
407   if (title_string.empty()) {
408     title_string = (type == SELECT_UPLOAD_FOLDER) ?
409         l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE) :
410         l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
411   }
412   std::string accept_button_label = (type == SELECT_UPLOAD_FOLDER) ?
413       l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON) :
414       GTK_STOCK_OPEN;
415
416   GtkWidget* dialog =
417       gtk_file_chooser_dialog_new(title_string.c_str(), NULL,
418                                   GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
419                                   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
420                                   accept_button_label.c_str(),
421                                   GTK_RESPONSE_ACCEPT,
422                                   NULL);
423   SetGtkTransientForAura(dialog, parent);
424
425   if (!default_path.empty()) {
426     gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
427                                   default_path.value().c_str());
428   } else if (!last_opened_path_->empty()) {
429     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
430                                         last_opened_path_->value().c_str());
431   }
432   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
433   g_signal_connect(dialog, "response",
434                    G_CALLBACK(OnSelectSingleFolderDialogResponseThunk), this);
435   return dialog;
436 }
437
438 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenDialog(
439     const std::string& title,
440     const base::FilePath& default_path,
441     gfx::NativeWindow parent) {
442   std::string title_string = !title.empty() ? title :
443         l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE);
444
445   GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
446   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
447   g_signal_connect(dialog, "response",
448                    G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
449   return dialog;
450 }
451
452 GtkWidget* SelectFileDialogImplGTK::CreateMultiFileOpenDialog(
453     const std::string& title,
454     const base::FilePath& default_path,
455     gfx::NativeWindow parent) {
456   std::string title_string = !title.empty() ? title :
457         l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE);
458
459   GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
460   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
461   g_signal_connect(dialog, "response",
462                    G_CALLBACK(OnSelectMultiFileDialogResponseThunk), this);
463   return dialog;
464 }
465
466 GtkWidget* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string& title,
467     const base::FilePath& default_path, gfx::NativeWindow parent) {
468   std::string title_string = !title.empty() ? title :
469         l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
470
471   GtkWidget* dialog =
472       gtk_file_chooser_dialog_new(title_string.c_str(), NULL,
473                                   GTK_FILE_CHOOSER_ACTION_SAVE,
474                                   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
475                                   GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
476                                   NULL);
477   SetGtkTransientForAura(dialog, parent);
478
479   AddFilters(GTK_FILE_CHOOSER(dialog));
480   if (!default_path.empty()) {
481     // Since the file may not already exist, we use
482     // set_current_folder() followed by set_current_name(), as per the
483     // recommendation of the GTK docs.
484     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
485         default_path.DirName().value().c_str());
486     gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),
487         default_path.BaseName().value().c_str());
488   } else if (!last_saved_path_->empty()) {
489     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
490                                         last_saved_path_->value().c_str());
491   }
492   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
493   gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
494                                                  TRUE);
495   g_signal_connect(dialog, "response",
496                    G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
497   return dialog;
498 }
499
500 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget* dialog) {
501   std::map<GtkWidget*, void*>::iterator iter = params_map_.find(dialog);
502   DCHECK(iter != params_map_.end());
503   void* params = iter->second;
504   params_map_.erase(iter);
505   return params;
506 }
507
508 void SelectFileDialogImplGTK::FileDialogDestroyed(GtkWidget* dialog) {
509   dialogs_.erase(dialog);
510
511   // Parent may be NULL in a few cases: 1) on shutdown when
512   // AllBrowsersClosed() trigger this handler after all the browser
513   // windows got destroyed, or 2) when the parent tab has been opened by
514   // 'Open Link in New Tab' context menu on a downloadable item and
515   // the tab has no content (see the comment in SelectFile as well).
516   aura::Window* parent = GetAuraTransientParent(dialog);
517   if (!parent)
518     return;
519   std::set<aura::Window*>::iterator iter = parents_.find(parent);
520   if (iter != parents_.end()) {
521     (*iter)->RemoveObserver(this);
522     parents_.erase(iter);
523   } else {
524     NOTREACHED();
525   }
526 }
527
528 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id) {
529   bool is_cancel = response_id == GTK_RESPONSE_CANCEL ||
530                    response_id == GTK_RESPONSE_DELETE_EVENT;
531   if (is_cancel)
532     return true;
533
534   DCHECK(response_id == GTK_RESPONSE_ACCEPT);
535   return false;
536 }
537
538 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget* dialog,
539     gint response_id,
540     bool allow_folder) {
541   if (IsCancelResponse(response_id)) {
542     FileNotSelected(dialog);
543     return;
544   }
545
546   gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
547   if (!filename) {
548     FileNotSelected(dialog);
549     return;
550   }
551
552   base::FilePath path(filename);
553   g_free(filename);
554
555   if (allow_folder) {
556     FileSelected(dialog, path);
557     return;
558   }
559
560   if (CallDirectoryExistsOnUIThread(path))
561     FileNotSelected(dialog);
562   else
563     FileSelected(dialog, path);
564 }
565
566 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
567     GtkWidget* dialog, int response_id) {
568   SelectSingleFileHelper(dialog, response_id, false);
569 }
570
571 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
572     GtkWidget* dialog, int response_id) {
573   SelectSingleFileHelper(dialog, response_id, true);
574 }
575
576 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget* dialog,
577                                                               int response_id) {
578   if (IsCancelResponse(response_id)) {
579     FileNotSelected(dialog);
580     return;
581   }
582
583   GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
584   if (!filenames) {
585     FileNotSelected(dialog);
586     return;
587   }
588
589   std::vector<base::FilePath> filenames_fp;
590   for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) {
591     base::FilePath path(static_cast<char*>(iter->data));
592     g_free(iter->data);
593     if (CallDirectoryExistsOnUIThread(path))
594       continue;
595     filenames_fp.push_back(path);
596   }
597   g_slist_free(filenames);
598
599   if (filenames_fp.empty()) {
600     FileNotSelected(dialog);
601     return;
602   }
603   MultiFilesSelected(dialog, filenames_fp);
604 }
605
606 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget* dialog) {
607   FileDialogDestroyed(dialog);
608 }
609
610 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget* chooser) {
611   gchar* filename = gtk_file_chooser_get_preview_filename(
612       GTK_FILE_CHOOSER(chooser));
613   if (!filename)
614     return;
615   // This will preserve the image's aspect ratio.
616   GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
617                                                        kPreviewHeight, NULL);
618   g_free(filename);
619   if (pixbuf) {
620     gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
621     g_object_unref(pixbuf);
622   }
623   gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
624                                              pixbuf ? TRUE : FALSE);
625 }
626
627 }  // namespace libgtk2ui