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