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