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