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