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