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
29namespace {
30
31// Makes sure that .jpg also shows .JPG.
32gboolean 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.
38void OnFileFilterDataDestroyed(std::string* file_extension) {
39  delete file_extension;
40}
41
42}  // namespace
43
44namespace 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.
48class 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.
168static const int kPreviewWidth = 256;
169static const int kPreviewHeight = 512;
170
171SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
172    Listener* listener, ui::SelectFilePolicy* policy) {
173  return new SelectFileDialogImplGTK(listener, policy);
174}
175
176SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener* listener,
177                                                 ui::SelectFilePolicy* policy)
178    : SelectFileDialogImpl(listener, policy),
179      preview_(NULL) {
180}
181
182SelectFileDialogImplGTK::~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
192bool SelectFileDialogImplGTK::IsRunning(gfx::NativeWindow parent_window) const {
193  return parents_.find(parent_window) != parents_.end();
194}
195
196bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
197  return file_types_.extensions.size() > 1;
198}
199
200void 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|.
217void 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
282void 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
337void 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
359void 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
368void SelectFileDialogImplGTK::FileNotSelected(GtkWidget* dialog) {
369  void* params = PopParamsForDialog(dialog);
370  if (listener_)
371    listener_->FileSelectionCanceled(params);
372  gtk_widget_destroy(dialog);
373}
374
375GtkWidget* 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
405GtkWidget* 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
442GtkWidget* 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
456GtkWidget* 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
470GtkWidget* 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
504void* 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
512void 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
532bool 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
542void 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
570void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
571    GtkWidget* dialog, int response_id) {
572  SelectSingleFileHelper(dialog, response_id, false);
573}
574
575void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
576    GtkWidget* dialog, int response_id) {
577  SelectSingleFileHelper(dialog, response_id, true);
578}
579
580void 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
610void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget* dialog) {
611  FileDialogDestroyed(dialog);
612}
613
614void 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