Home | History | Annotate | Download | only in gtk
      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
    162 SelectFileDialog* SelectFileDialog::Create(Listener* listener) {
    163   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    164   return new SelectFileDialogImpl(listener);
    165 }
    166 
    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 
    178 SelectFileDialogImpl::~SelectFileDialogImpl() {
    179   while (dialogs_.begin() != dialogs_.end()) {
    180     gtk_widget_destroy(*(dialogs_.begin()));
    181   }
    182 }
    183 
    184 bool SelectFileDialogImpl::IsRunning(gfx::NativeWindow parent_window) const {
    185   return parents_.find(parent_window) != parents_.end();
    186 }
    187 
    188 void SelectFileDialogImpl::ListenerDestroyed() {
    189   listener_ = NULL;
    190 }
    191 
    192 // We ignore |default_extension|.
    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 
    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 
    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 
    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 
    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 
    343 bool SelectFileDialogImpl::CallDirectoryExistsOnUIThread(const FilePath& path) {
    344   base::ThreadRestrictions::ScopedAllowIO allow_io;
    345   return file_util::DirectoryExists(path);
    346 }
    347 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    527 void SelectFileDialogImpl::OnSelectSingleFileDialogResponse(GtkWidget* dialog,
    528                                                             int response_id) {
    529   SelectSingleFileHelper(dialog, response_id, false);
    530 }
    531 
    532 void SelectFileDialogImpl::OnSelectSingleFolderDialogResponse(GtkWidget* dialog,
    533                                                               int response_id) {
    534   SelectSingleFileHelper(dialog, response_id, true);
    535 }
    536 
    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 
    567 void SelectFileDialogImpl::OnFileChooserDestroy(GtkWidget* dialog) {
    568   FileDialogDestroyed(dialog);
    569 }
    570 
    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