Home | History | Annotate | Download | only in gtk
      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.
     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.
     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 
    153 SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener* listener,
    154                                                  ui::SelectFilePolicy* policy)
    155     : SelectFileDialogImpl(listener, policy),
    156       preview_(NULL) {
    157 }
    158 
    159 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() {
    160   while (dialogs_.begin() != dialogs_.end()) {
    161     gtk_widget_destroy(*(dialogs_.begin()));
    162   }
    163 }
    164 
    165 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
    166   return file_types_.extensions.size() > 1;
    167 }
    168 
    169 // We ignore |default_extension|.
    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 
    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 
    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     *last_opened_path_ = path.DirName();
    298   else
    299     NOTREACHED();
    300 
    301   if (listener_) {
    302     GtkFileFilter* selected_filter =
    303         gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
    304     GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog));
    305     int idx = g_slist_index(filters, selected_filter);
    306     g_slist_free(filters);
    307     listener_->FileSelected(path, idx + 1, PopParamsForDialog(dialog));
    308   }
    309   gtk_widget_destroy(dialog);
    310 }
    311 
    312 void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget* dialog,
    313     const std::vector<base::FilePath>& files) {
    314     *last_opened_path_ = files[0].DirName();
    315 
    316   if (listener_)
    317     listener_->MultiFilesSelected(files, PopParamsForDialog(dialog));
    318   gtk_widget_destroy(dialog);
    319 }
    320 
    321 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget* dialog) {
    322   void* params = PopParamsForDialog(dialog);
    323   if (listener_)
    324     listener_->FileSelectionCanceled(params);
    325   gtk_widget_destroy(dialog);
    326 }
    327 
    328 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenHelper(
    329     const std::string& title,
    330     const base::FilePath& default_path,
    331     gfx::NativeWindow parent) {
    332   GtkWidget* dialog =
    333       gtk_file_chooser_dialog_new(title.c_str(), parent,
    334                                   GTK_FILE_CHOOSER_ACTION_OPEN,
    335                                   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
    336                                   GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
    337                                   NULL);
    338   AddFilters(GTK_FILE_CHOOSER(dialog));
    339 
    340   if (!default_path.empty()) {
    341     if (CallDirectoryExistsOnUIThread(default_path)) {
    342       gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
    343                                           default_path.value().c_str());
    344     } else {
    345       // If the file doesn't exist, this will just switch to the correct
    346       // directory. That's good enough.
    347       gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
    348                                     default_path.value().c_str());
    349     }
    350   } else if (!last_opened_path_->empty()) {
    351     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
    352                                         last_opened_path_->value().c_str());
    353   }
    354   return dialog;
    355 }
    356 
    357 GtkWidget* SelectFileDialogImplGTK::CreateSelectFolderDialog(
    358     Type type,
    359     const std::string& title,
    360     const base::FilePath& default_path,
    361     gfx::NativeWindow parent) {
    362   std::string title_string = title;
    363   if (title_string.empty()) {
    364     title_string = (type == SELECT_UPLOAD_FOLDER) ?
    365         l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE) :
    366         l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
    367   }
    368   std::string accept_button_label = (type == SELECT_UPLOAD_FOLDER) ?
    369       l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON) :
    370       GTK_STOCK_OPEN;
    371 
    372   GtkWidget* dialog =
    373       gtk_file_chooser_dialog_new(title_string.c_str(), parent,
    374                                   GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
    375                                   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
    376                                   accept_button_label.c_str(),
    377                                   GTK_RESPONSE_ACCEPT,
    378                                   NULL);
    379 
    380   if (!default_path.empty()) {
    381     gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
    382                                   default_path.value().c_str());
    383   } else if (!last_opened_path_->empty()) {
    384     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
    385                                         last_opened_path_->value().c_str());
    386   }
    387   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
    388   g_signal_connect(dialog, "response",
    389                    G_CALLBACK(OnSelectSingleFolderDialogResponseThunk), this);
    390   return dialog;
    391 }
    392 
    393 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenDialog(
    394     const std::string& title,
    395     const base::FilePath& default_path,
    396     gfx::NativeWindow parent) {
    397   std::string title_string = !title.empty() ? title :
    398         l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE);
    399 
    400   GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
    401   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
    402   g_signal_connect(dialog, "response",
    403                    G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
    404   return dialog;
    405 }
    406 
    407 GtkWidget* SelectFileDialogImplGTK::CreateMultiFileOpenDialog(
    408     const std::string& title,
    409     const base::FilePath& default_path,
    410     gfx::NativeWindow parent) {
    411   std::string title_string = !title.empty() ? title :
    412         l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE);
    413 
    414   GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
    415   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
    416   g_signal_connect(dialog, "response",
    417                    G_CALLBACK(OnSelectMultiFileDialogResponseThunk), this);
    418   return dialog;
    419 }
    420 
    421 GtkWidget* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string& title,
    422     const base::FilePath& default_path, gfx::NativeWindow parent) {
    423   std::string title_string = !title.empty() ? title :
    424         l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
    425 
    426   GtkWidget* dialog =
    427       gtk_file_chooser_dialog_new(title_string.c_str(), parent,
    428                                   GTK_FILE_CHOOSER_ACTION_SAVE,
    429                                   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
    430                                   GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
    431                                   NULL);
    432 
    433   AddFilters(GTK_FILE_CHOOSER(dialog));
    434   if (!default_path.empty()) {
    435     // Since the file may not already exist, we use
    436     // set_current_folder() followed by set_current_name(), as per the
    437     // recommendation of the GTK docs.
    438     if (CallDirectoryExistsOnUIThread(default_path)) {
    439       gtk_file_chooser_set_current_folder(
    440           GTK_FILE_CHOOSER(dialog), default_path.value().c_str());
    441       gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "");
    442     } else {
    443       gtk_file_chooser_set_current_folder(
    444           GTK_FILE_CHOOSER(dialog), default_path.DirName().value().c_str());
    445       gtk_file_chooser_set_current_name(
    446           GTK_FILE_CHOOSER(dialog), default_path.BaseName().value().c_str());
    447     }
    448   } else if (!last_saved_path_->empty()) {
    449     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
    450                                         last_saved_path_->value().c_str());
    451   }
    452   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
    453   gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
    454                                                  TRUE);
    455   g_signal_connect(dialog, "response",
    456                    G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
    457   return dialog;
    458 }
    459 
    460 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget* dialog) {
    461   std::map<GtkWidget*, void*>::iterator iter = params_map_.find(dialog);
    462   DCHECK(iter != params_map_.end());
    463   void* params = iter->second;
    464   params_map_.erase(iter);
    465   return params;
    466 }
    467 
    468 void SelectFileDialogImplGTK::FileDialogDestroyed(GtkWidget* dialog) {
    469   dialogs_.erase(dialog);
    470 
    471   // Parent may be NULL in a few cases: 1) on shutdown when
    472   // AllBrowsersClosed() trigger this handler after all the browser
    473   // windows got destroyed, or 2) when the parent tab has been opened by
    474   // 'Open Link in New Tab' context menu on a downloadable item and
    475   // the tab has no content (see the comment in SelectFile as well).
    476   GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(dialog));
    477   if (!parent)
    478     return;
    479   std::set<GtkWindow*>::iterator iter = parents_.find(parent);
    480   if (iter != parents_.end())
    481     parents_.erase(iter);
    482   else
    483     NOTREACHED();
    484 }
    485 
    486 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id) {
    487   bool is_cancel = response_id == GTK_RESPONSE_CANCEL ||
    488                    response_id == GTK_RESPONSE_DELETE_EVENT;
    489   if (is_cancel)
    490     return true;
    491 
    492   DCHECK(response_id == GTK_RESPONSE_ACCEPT);
    493   return false;
    494 }
    495 
    496 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget* dialog,
    497     gint response_id,
    498     bool allow_folder) {
    499   if (IsCancelResponse(response_id)) {
    500     FileNotSelected(dialog);
    501     return;
    502   }
    503 
    504   gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
    505   if (!filename) {
    506     FileNotSelected(dialog);
    507     return;
    508   }
    509 
    510   base::FilePath path(filename);
    511   g_free(filename);
    512 
    513   if (allow_folder) {
    514     FileSelected(dialog, path);
    515     return;
    516   }
    517 
    518   if (CallDirectoryExistsOnUIThread(path))
    519     FileNotSelected(dialog);
    520   else
    521     FileSelected(dialog, path);
    522 }
    523 
    524 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
    525     GtkWidget* dialog, int response_id) {
    526   SelectSingleFileHelper(dialog, response_id, false);
    527 }
    528 
    529 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
    530     GtkWidget* dialog, int response_id) {
    531   SelectSingleFileHelper(dialog, response_id, true);
    532 }
    533 
    534 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget* dialog,
    535                                                               int response_id) {
    536   if (IsCancelResponse(response_id)) {
    537     FileNotSelected(dialog);
    538     return;
    539   }
    540 
    541   GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
    542   if (!filenames) {
    543     FileNotSelected(dialog);
    544     return;
    545   }
    546 
    547   std::vector<base::FilePath> filenames_fp;
    548   for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) {
    549     base::FilePath path(static_cast<char*>(iter->data));
    550     g_free(iter->data);
    551     if (CallDirectoryExistsOnUIThread(path))
    552       continue;
    553     filenames_fp.push_back(path);
    554   }
    555   g_slist_free(filenames);
    556 
    557   if (filenames_fp.empty()) {
    558     FileNotSelected(dialog);
    559     return;
    560   }
    561   MultiFilesSelected(dialog, filenames_fp);
    562 }
    563 
    564 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget* dialog) {
    565   FileDialogDestroyed(dialog);
    566 }
    567 
    568 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget* chooser) {
    569   gchar* filename = gtk_file_chooser_get_preview_filename(
    570       GTK_FILE_CHOOSER(chooser));
    571   if (!filename)
    572     return;
    573   // This will preserve the image's aspect ratio.
    574   GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
    575                                                        kPreviewHeight, NULL);
    576   g_free(filename);
    577   if (pixbuf) {
    578     gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
    579     g_object_unref(pixbuf);
    580   }
    581   gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
    582                                              pixbuf ? TRUE : FALSE);
    583 }
    584 
    585 }  // namespace
    586 
    587 namespace ui {
    588 
    589 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
    590     Listener* listener, ui::SelectFilePolicy* policy) {
    591   return new SelectFileDialogImplGTK(listener, policy);
    592 }
    593 
    594 }  // namespace ui
    595