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 "ui/gtk/select_file_dialog_impl_gtk.h"
6 
7 #include <gtk/gtk.h>
8 #include <stddef.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <unistd.h>
12 
13 #include <map>
14 #include <memory>
15 #include <set>
16 #include <vector>
17 
18 #include "base/logging.h"
19 #include "base/memory/ptr_util.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/sys_string_conversions.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/threading/thread.h"
24 #include "base/threading/thread_restrictions.h"
25 #include "ui/aura/window_observer.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/gtk/gtk_ui.h"
28 #include "ui/gtk/gtk_ui_delegate.h"
29 #include "ui/gtk/gtk_util.h"
30 #include "ui/gtk/select_file_dialog_impl.h"
31 #include "ui/shell_dialogs/select_file_dialog.h"
32 #include "ui/strings/grit/ui_strings.h"
33 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h"
34 
35 namespace {
36 
37 #if GTK_CHECK_VERSION(3, 90, 0)
38 // GTK stock items have been deprecated.  The docs say to switch to using the
39 // strings "_Open", etc.  However this breaks i18n.  We could supply our own
40 // internationalized strings, but the "_" in these strings is significant: it's
41 // the keyboard shortcut to select these actions.  TODO(thomasanderson): Provide
42 // internationalized strings when GTK provides support for it.
43 const char kCancelLabel[] = "_Cancel";
44 const char kOpenLabel[] = "_Open";
45 const char kSaveLabel[] = "_Save";
46 #else
47 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
48 const char* const kCancelLabel = GTK_STOCK_CANCEL;
49 const char* const kOpenLabel = GTK_STOCK_OPEN;
50 const char* const kSaveLabel = GTK_STOCK_SAVE;
51 G_GNUC_END_IGNORE_DEPRECATIONS
52 #endif
53 
54 // Makes sure that .jpg also shows .JPG.
FileFilterCaseInsensitive(const GtkFileFilterInfo * file_info,std::string * file_extension)55 gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info,
56                                    std::string* file_extension) {
57   return base::EndsWith(file_info->filename, *file_extension,
58                         base::CompareCase::INSENSITIVE_ASCII);
59 }
60 
61 // Deletes |data| when gtk_file_filter_add_custom() is done with it.
OnFileFilterDataDestroyed(std::string * file_extension)62 void OnFileFilterDataDestroyed(std::string* file_extension) {
63   delete file_extension;
64 }
65 
66 // Runs DesktopWindowTreeHostLinux::EnableEventListening() when the file-picker
67 // is closed.
OnFilePickerDestroy(base::OnceClosure * callback_raw)68 void OnFilePickerDestroy(base::OnceClosure* callback_raw) {
69   std::unique_ptr<base::OnceClosure> callback = base::WrapUnique(callback_raw);
70   std::move(*callback).Run();
71 }
72 
73 }  // namespace
74 
75 namespace gtk {
76 
77 // The size of the preview we display for selected image files. We set height
78 // larger than width because generally there is more free space vertically
79 // than horiztonally (setting the preview image will alway expand the width of
80 // the dialog, but usually not the height). The image's aspect ratio will always
81 // be preserved.
82 static const int kPreviewWidth = 256;
83 static const int kPreviewHeight = 512;
84 
NewSelectFileDialogImplGTK(Listener * listener,std::unique_ptr<ui::SelectFilePolicy> policy)85 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
86     Listener* listener,
87     std::unique_ptr<ui::SelectFilePolicy> policy) {
88   return new SelectFileDialogImplGTK(listener, std::move(policy));
89 }
90 
SelectFileDialogImplGTK(Listener * listener,std::unique_ptr<ui::SelectFilePolicy> policy)91 SelectFileDialogImplGTK::SelectFileDialogImplGTK(
92     Listener* listener,
93     std::unique_ptr<ui::SelectFilePolicy> policy)
94     : SelectFileDialogImpl(listener, std::move(policy)), preview_(nullptr) {}
95 
~SelectFileDialogImplGTK()96 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() {
97   for (std::set<aura::Window*>::iterator iter = parents_.begin();
98        iter != parents_.end(); ++iter) {
99     (*iter)->RemoveObserver(this);
100   }
101   while (dialogs_.begin() != dialogs_.end()) {
102     gtk_widget_destroy(*(dialogs_.begin()));
103   }
104 }
105 
IsRunning(gfx::NativeWindow parent_window) const106 bool SelectFileDialogImplGTK::IsRunning(gfx::NativeWindow parent_window) const {
107   return parents_.find(parent_window) != parents_.end();
108 }
109 
HasMultipleFileTypeChoicesImpl()110 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
111   return file_types_.extensions.size() > 1;
112 }
113 
OnWindowDestroying(aura::Window * window)114 void SelectFileDialogImplGTK::OnWindowDestroying(aura::Window* window) {
115   // Remove the |parent| property associated with the |dialog|.
116   for (std::set<GtkWidget*>::iterator it = dialogs_.begin();
117        it != dialogs_.end(); ++it) {
118     aura::Window* parent = GetAuraTransientParent(*it);
119     if (parent == window)
120       ClearAuraTransientParent(*it);
121   }
122 
123   std::set<aura::Window*>::iterator iter = parents_.find(window);
124   if (iter != parents_.end()) {
125     (*iter)->RemoveObserver(this);
126     parents_.erase(iter);
127   }
128 }
129 
130 // We ignore |default_extension|.
SelectFileImpl(Type type,const base::string16 & title,const base::FilePath & default_path,const FileTypeInfo * file_types,int file_type_index,const base::FilePath::StringType & default_extension,gfx::NativeWindow owning_window,void * params)131 void SelectFileDialogImplGTK::SelectFileImpl(
132     Type type,
133     const base::string16& title,
134     const base::FilePath& default_path,
135     const FileTypeInfo* file_types,
136     int file_type_index,
137     const base::FilePath::StringType& default_extension,
138     gfx::NativeWindow owning_window,
139     void* params) {
140   type_ = type;
141   if (owning_window) {
142     owning_window->AddObserver(this);
143     parents_.insert(owning_window);
144   }
145 
146   std::string title_string = base::UTF16ToUTF8(title);
147 
148   file_type_index_ = file_type_index;
149   if (file_types)
150     file_types_ = *file_types;
151 
152   GtkWidget* dialog = nullptr;
153   switch (type) {
154     case SELECT_FOLDER:
155     case SELECT_UPLOAD_FOLDER:
156     case SELECT_EXISTING_FOLDER:
157       dialog = CreateSelectFolderDialog(type, title_string, default_path,
158                                         owning_window);
159       break;
160     case SELECT_OPEN_FILE:
161       dialog = CreateFileOpenDialog(title_string, default_path, owning_window);
162       break;
163     case SELECT_OPEN_MULTI_FILE:
164       dialog =
165           CreateMultiFileOpenDialog(title_string, default_path, owning_window);
166       break;
167     case SELECT_SAVEAS_FILE:
168       dialog = CreateSaveAsDialog(title_string, default_path, owning_window);
169       break;
170     case SELECT_NONE:
171       NOTREACHED();
172       return;
173   }
174   g_signal_connect(dialog, "delete-event",
175                    G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
176   dialogs_.insert(dialog);
177 
178   preview_ = gtk_image_new();
179   g_signal_connect(dialog, "destroy", G_CALLBACK(OnFileChooserDestroyThunk),
180                    this);
181   g_signal_connect(dialog, "update-preview", G_CALLBACK(OnUpdatePreviewThunk),
182                    this);
183   gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview_);
184 
185   params_map_[dialog] = params;
186 
187   // Disable input events handling in the host window to make this dialog modal.
188   if (owning_window) {
189     views::DesktopWindowTreeHostLinux* host =
190         static_cast<views::DesktopWindowTreeHostLinux*>(
191             owning_window->GetHost());
192     if (host) {
193       // In some circumstances (e.g. dialog from flash plugin) the mouse has
194       // been captured and by turning off event listening, it is never
195       // released. So we manually ensure there is no current capture.
196       host->ReleaseCapture();
197       std::unique_ptr<base::OnceClosure> callback =
198           std::make_unique<base::OnceClosure>(host->DisableEventListening());
199       // OnFilePickerDestroy() is called when |dialog| destroyed, which allows
200       // to invoke the callback function to re-enable event handling on the
201       // owning window.
202       g_object_set_data_full(
203           G_OBJECT(dialog), "callback", callback.release(),
204           reinterpret_cast<GDestroyNotify>(OnFilePickerDestroy));
205       gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
206     }
207   }
208 
209 #if !GTK_CHECK_VERSION(3, 90, 0)
210   gtk_widget_show_all(dialog);
211 #endif
212   gtk::GtkUi::GetDelegate()->ShowGtkWindow(GTK_WINDOW(dialog));
213 }
214 
AddFilters(GtkFileChooser * chooser)215 void SelectFileDialogImplGTK::AddFilters(GtkFileChooser* chooser) {
216   for (size_t i = 0; i < file_types_.extensions.size(); ++i) {
217     GtkFileFilter* filter = nullptr;
218     std::set<std::string> fallback_labels;
219 
220     for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) {
221       const std::string& current_extension = file_types_.extensions[i][j];
222       if (!current_extension.empty()) {
223         if (!filter)
224           filter = gtk_file_filter_new();
225         std::unique_ptr<std::string> file_extension(
226             new std::string("." + current_extension));
227         fallback_labels.insert(std::string("*").append(*file_extension));
228         gtk_file_filter_add_custom(
229             filter, GTK_FILE_FILTER_FILENAME,
230             reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive),
231             file_extension.release(),
232             reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed));
233       }
234     }
235     // We didn't find any non-empty extensions to filter on.
236     if (!filter)
237       continue;
238 
239     // The description vector may be blank, in which case we are supposed to
240     // use some sort of default description based on the filter.
241     if (i < file_types_.extension_description_overrides.size()) {
242       gtk_file_filter_set_name(
243           filter,
244           base::UTF16ToUTF8(file_types_.extension_description_overrides[i])
245               .c_str());
246     } else {
247       // There is no system default filter description so we use
248       // the extensions themselves if the description is blank.
249       std::vector<std::string> fallback_labels_vector(fallback_labels.begin(),
250                                                       fallback_labels.end());
251       std::string fallback_label =
252           base::JoinString(fallback_labels_vector, ",");
253       gtk_file_filter_set_name(filter, fallback_label.c_str());
254     }
255 
256     gtk_file_chooser_add_filter(chooser, filter);
257     if (i == file_type_index_ - 1)
258       gtk_file_chooser_set_filter(chooser, filter);
259   }
260 
261   // Add the *.* filter, but only if we have added other filters (otherwise it
262   // is implied).
263   if (file_types_.include_all_files && !file_types_.extensions.empty()) {
264     GtkFileFilter* filter = gtk_file_filter_new();
265     gtk_file_filter_add_pattern(filter, "*");
266     gtk_file_filter_set_name(
267         filter, l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES).c_str());
268     gtk_file_chooser_add_filter(chooser, filter);
269   }
270 }
271 
FileSelected(GtkWidget * dialog,const base::FilePath & path)272 void SelectFileDialogImplGTK::FileSelected(GtkWidget* dialog,
273                                            const base::FilePath& path) {
274   if (type_ == SELECT_SAVEAS_FILE) {
275     *last_saved_path_ = path.DirName();
276   } else if (type_ == SELECT_OPEN_FILE || type_ == SELECT_FOLDER ||
277              type_ == SELECT_UPLOAD_FOLDER || type_ == SELECT_EXISTING_FOLDER) {
278     *last_opened_path_ = path.DirName();
279   } else {
280     NOTREACHED();
281   }
282 
283   if (listener_) {
284     GtkFileFilter* selected_filter =
285         gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
286     GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog));
287     int idx = g_slist_index(filters, selected_filter);
288     g_slist_free(filters);
289     listener_->FileSelected(path, idx + 1, PopParamsForDialog(dialog));
290   }
291   gtk_widget_destroy(dialog);
292 }
293 
MultiFilesSelected(GtkWidget * dialog,const std::vector<base::FilePath> & files)294 void SelectFileDialogImplGTK::MultiFilesSelected(
295     GtkWidget* dialog,
296     const std::vector<base::FilePath>& files) {
297   *last_opened_path_ = files[0].DirName();
298 
299   if (listener_)
300     listener_->MultiFilesSelected(files, PopParamsForDialog(dialog));
301   gtk_widget_destroy(dialog);
302 }
303 
FileNotSelected(GtkWidget * dialog)304 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget* dialog) {
305   void* params = PopParamsForDialog(dialog);
306   if (listener_)
307     listener_->FileSelectionCanceled(params);
308   gtk_widget_destroy(dialog);
309 }
310 
CreateFileOpenHelper(const std::string & title,const base::FilePath & default_path,gfx::NativeWindow parent)311 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenHelper(
312     const std::string& title,
313     const base::FilePath& default_path,
314     gfx::NativeWindow parent) {
315   GtkWidget* dialog = gtk_file_chooser_dialog_new(
316       title.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, kCancelLabel,
317       GTK_RESPONSE_CANCEL, kOpenLabel, GTK_RESPONSE_ACCEPT, nullptr);
318   SetGtkTransientForAura(dialog, parent);
319   AddFilters(GTK_FILE_CHOOSER(dialog));
320 
321   if (!default_path.empty()) {
322     if (CallDirectoryExistsOnUIThread(default_path)) {
323       gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
324                                           default_path.value().c_str());
325     } else {
326       // If the file doesn't exist, this will just switch to the correct
327       // directory. That's good enough.
328       gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
329                                     default_path.value().c_str());
330     }
331   } else if (!last_opened_path_->empty()) {
332     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
333                                         last_opened_path_->value().c_str());
334   }
335   return dialog;
336 }
337 
CreateSelectFolderDialog(Type type,const std::string & title,const base::FilePath & default_path,gfx::NativeWindow parent)338 GtkWidget* SelectFileDialogImplGTK::CreateSelectFolderDialog(
339     Type type,
340     const std::string& title,
341     const base::FilePath& default_path,
342     gfx::NativeWindow parent) {
343   std::string title_string = title;
344   if (title_string.empty()) {
345     title_string =
346         (type == SELECT_UPLOAD_FOLDER)
347             ? l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE)
348             : l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
349   }
350   std::string accept_button_label =
351       (type == SELECT_UPLOAD_FOLDER)
352           ? l10n_util::GetStringUTF8(
353                 IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON)
354           : kOpenLabel;
355 
356   GtkWidget* dialog = gtk_file_chooser_dialog_new(
357       title_string.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
358       kCancelLabel, GTK_RESPONSE_CANCEL, accept_button_label.c_str(),
359       GTK_RESPONSE_ACCEPT, nullptr);
360   SetGtkTransientForAura(dialog, parent);
361   GtkFileChooser* chooser = GTK_FILE_CHOOSER(dialog);
362   if (type == SELECT_UPLOAD_FOLDER || type == SELECT_EXISTING_FOLDER)
363     gtk_file_chooser_set_create_folders(chooser, FALSE);
364   if (!default_path.empty()) {
365     gtk_file_chooser_set_filename(chooser, default_path.value().c_str());
366   } else if (!last_opened_path_->empty()) {
367     gtk_file_chooser_set_current_folder(chooser,
368                                         last_opened_path_->value().c_str());
369   }
370   GtkFileFilter* only_folders = gtk_file_filter_new();
371   gtk_file_filter_set_name(
372       only_folders,
373       l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE).c_str());
374   gtk_file_filter_add_mime_type(only_folders, "application/x-directory");
375   gtk_file_filter_add_mime_type(only_folders, "inode/directory");
376   gtk_file_filter_add_mime_type(only_folders, "text/directory");
377   gtk_file_chooser_add_filter(chooser, only_folders);
378   gtk_file_chooser_set_select_multiple(chooser, FALSE);
379   g_signal_connect(dialog, "response",
380                    G_CALLBACK(OnSelectSingleFolderDialogResponseThunk), this);
381   return dialog;
382 }
383 
CreateFileOpenDialog(const std::string & title,const base::FilePath & default_path,gfx::NativeWindow parent)384 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenDialog(
385     const std::string& title,
386     const base::FilePath& default_path,
387     gfx::NativeWindow parent) {
388   std::string title_string =
389       !title.empty() ? title
390                      : l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE);
391 
392   GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
393   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
394   g_signal_connect(dialog, "response",
395                    G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
396   return dialog;
397 }
398 
CreateMultiFileOpenDialog(const std::string & title,const base::FilePath & default_path,gfx::NativeWindow parent)399 GtkWidget* SelectFileDialogImplGTK::CreateMultiFileOpenDialog(
400     const std::string& title,
401     const base::FilePath& default_path,
402     gfx::NativeWindow parent) {
403   std::string title_string =
404       !title.empty() ? title
405                      : l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE);
406 
407   GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
408   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
409   g_signal_connect(dialog, "response",
410                    G_CALLBACK(OnSelectMultiFileDialogResponseThunk), this);
411   return dialog;
412 }
413 
CreateSaveAsDialog(const std::string & title,const base::FilePath & default_path,gfx::NativeWindow parent)414 GtkWidget* SelectFileDialogImplGTK::CreateSaveAsDialog(
415     const std::string& title,
416     const base::FilePath& default_path,
417     gfx::NativeWindow parent) {
418   std::string title_string =
419       !title.empty() ? title
420                      : l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
421 
422   GtkWidget* dialog = gtk_file_chooser_dialog_new(
423       title_string.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_SAVE, kCancelLabel,
424       GTK_RESPONSE_CANCEL, kSaveLabel, GTK_RESPONSE_ACCEPT, nullptr);
425   SetGtkTransientForAura(dialog, parent);
426 
427   AddFilters(GTK_FILE_CHOOSER(dialog));
428   if (!default_path.empty()) {
429     if (CallDirectoryExistsOnUIThread(default_path)) {
430       // If this is an existing directory, navigate to that directory, with no
431       // filename.
432       gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
433                                           default_path.value().c_str());
434     } else {
435       // The default path does not exist, or is an existing file. We use
436       // set_current_folder() followed by set_current_name(), as per the
437       // recommendation of the GTK docs.
438       gtk_file_chooser_set_current_folder(
439           GTK_FILE_CHOOSER(dialog), default_path.DirName().value().c_str());
440       gtk_file_chooser_set_current_name(
441           GTK_FILE_CHOOSER(dialog), default_path.BaseName().value().c_str());
442     }
443   } else if (!last_saved_path_->empty()) {
444     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
445                                         last_saved_path_->value().c_str());
446   }
447   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
448   gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
449                                                  TRUE);
450   g_signal_connect(dialog, "response",
451                    G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
452   return dialog;
453 }
454 
PopParamsForDialog(GtkWidget * dialog)455 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget* dialog) {
456   std::map<GtkWidget*, void*>::iterator iter = params_map_.find(dialog);
457   DCHECK(iter != params_map_.end());
458   void* params = iter->second;
459   params_map_.erase(iter);
460   return params;
461 }
462 
IsCancelResponse(gint response_id)463 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id) {
464   bool is_cancel = response_id == GTK_RESPONSE_CANCEL ||
465                    response_id == GTK_RESPONSE_DELETE_EVENT;
466   if (is_cancel)
467     return true;
468 
469   DCHECK(response_id == GTK_RESPONSE_ACCEPT);
470   return false;
471 }
472 
SelectSingleFileHelper(GtkWidget * dialog,gint response_id,bool allow_folder)473 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget* dialog,
474                                                      gint response_id,
475                                                      bool allow_folder) {
476   if (IsCancelResponse(response_id)) {
477     FileNotSelected(dialog);
478     return;
479   }
480 
481   gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
482   if (!filename) {
483     FileNotSelected(dialog);
484     return;
485   }
486 
487   base::FilePath path(filename);
488   g_free(filename);
489 
490   if (allow_folder) {
491     FileSelected(dialog, path);
492     return;
493   }
494 
495   if (CallDirectoryExistsOnUIThread(path))
496     FileNotSelected(dialog);
497   else
498     FileSelected(dialog, path);
499 }
500 
OnSelectSingleFileDialogResponse(GtkWidget * dialog,int response_id)501 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
502     GtkWidget* dialog,
503     int response_id) {
504   SelectSingleFileHelper(dialog, response_id, false);
505 }
506 
OnSelectSingleFolderDialogResponse(GtkWidget * dialog,int response_id)507 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
508     GtkWidget* dialog,
509     int response_id) {
510   SelectSingleFileHelper(dialog, response_id, true);
511 }
512 
OnSelectMultiFileDialogResponse(GtkWidget * dialog,int response_id)513 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget* dialog,
514                                                               int response_id) {
515   if (IsCancelResponse(response_id)) {
516     FileNotSelected(dialog);
517     return;
518   }
519 
520   GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
521   if (!filenames) {
522     FileNotSelected(dialog);
523     return;
524   }
525 
526   std::vector<base::FilePath> filenames_fp;
527   for (GSList* iter = filenames; iter != nullptr; iter = g_slist_next(iter)) {
528     base::FilePath path(static_cast<char*>(iter->data));
529     g_free(iter->data);
530     if (CallDirectoryExistsOnUIThread(path))
531       continue;
532     filenames_fp.push_back(path);
533   }
534   g_slist_free(filenames);
535 
536   if (filenames_fp.empty()) {
537     FileNotSelected(dialog);
538     return;
539   }
540   MultiFilesSelected(dialog, filenames_fp);
541 }
542 
OnFileChooserDestroy(GtkWidget * dialog)543 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget* dialog) {
544   dialogs_.erase(dialog);
545 
546   // |parent| can be nullptr when closing the host window
547   // while opening the file-picker.
548   aura::Window* parent = GetAuraTransientParent(dialog);
549   if (!parent)
550     return;
551   std::set<aura::Window*>::iterator iter = parents_.find(parent);
552   if (iter != parents_.end()) {
553     (*iter)->RemoveObserver(this);
554     parents_.erase(iter);
555   } else {
556     NOTREACHED();
557   }
558 }
559 
OnUpdatePreview(GtkWidget * chooser)560 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget* chooser) {
561   gchar* filename =
562       gtk_file_chooser_get_preview_filename(GTK_FILE_CHOOSER(chooser));
563   if (!filename) {
564     gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
565                                                FALSE);
566     return;
567   }
568 
569   // Don't attempt to open anything which isn't a regular file. If a named pipe,
570   // this may hang. See https://crbug.com/534754.
571   struct stat stat_buf;
572   if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) {
573     g_free(filename);
574     gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
575                                                FALSE);
576     return;
577   }
578 
579   // This will preserve the image's aspect ratio.
580   GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
581                                                        kPreviewHeight, nullptr);
582   g_free(filename);
583   if (pixbuf) {
584     gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
585     g_object_unref(pixbuf);
586   }
587   gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
588                                              pixbuf ? TRUE : FALSE);
589 }
590 
591 }  // namespace gtk
592