Revert 168224 - Update V8 to version 3.15.4.
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / select_file_dialog_impl_gtk2.cc
bloba48d45056ce336626b47a20a8773f40e6aee1ed2
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.
5 #include <gdk/gdk.h>
6 #include <gdk/gdkx.h>
7 #include <gtk/gtk.h>
8 #include <map>
9 #include <set>
10 #include <vector>
12 // Xlib defines RootWindow
13 #undef RootWindow
15 #include "base/file_util.h"
16 #include "base/logging.h"
17 #include "base/message_loop.h"
18 #include "base/string_util.h"
19 #include "base/sys_string_conversions.h"
20 #include "base/threading/thread.h"
21 #include "base/threading/thread_restrictions.h"
22 #include "base/utf_string_conversions.h"
23 #include "chrome/browser/ui/libgtk2ui/gtk2_signal.h"
24 #include "chrome/browser/ui/libgtk2ui/select_file_dialog_impl.h"
25 #include "grit/ui_strings.h"
26 #include "ui/aura/root_window.h"
27 #include "ui/aura/window_observer.h"
28 #include "ui/base/dialogs/select_file_dialog.h"
29 #include "ui/base/l10n/l10n_util.h"
31 namespace {
33 const char kAuraTransientParent[] = "aura-transient-parent";
35 // Set |dialog| as transient for |parent|, which will keep it on top and center
36 // it above |parent|.
37 void SetGtkTransientForAura(GtkWidget* dialog, aura::Window* parent) {
38 gtk_widget_realize(dialog);
39 GdkWindow* gdk_window = gtk_widget_get_window(dialog);
41 // TODO(erg): Check to make sure we're using X11 if wayland or some other
42 // display server ever happens. Otherwise, this will crash.
43 XSetTransientForHint(GDK_WINDOW_XDISPLAY(gdk_window),
44 GDK_WINDOW_XID(gdk_window),
45 parent->GetRootWindow()->GetAcceleratedWidget());
47 // We also set the |parent| as a property of |dialog|, so that we can unlink
48 // the two later.
49 g_object_set_data(G_OBJECT(dialog), kAuraTransientParent, parent);
52 } // namespace
54 namespace libgtk2ui {
56 // Implementation of SelectFileDialog that shows a Gtk common dialog for
57 // choosing a file or folder. This acts as a modal dialog.
58 class SelectFileDialogImplGTK : public SelectFileDialogImpl,
59 public aura::WindowObserver {
60 public:
61 explicit SelectFileDialogImplGTK(Listener* listener,
62 ui::SelectFilePolicy* policy);
64 protected:
65 virtual ~SelectFileDialogImplGTK();
67 // SelectFileDialog implementation.
68 // |params| is user data we pass back via the Listener interface.
69 virtual void SelectFileImpl(Type type,
70 const string16& title,
71 const FilePath& default_path,
72 const FileTypeInfo* file_types,
73 int file_type_index,
74 const FilePath::StringType& default_extension,
75 gfx::NativeWindow owning_window,
76 void* params) OVERRIDE;
78 private:
79 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE;
81 // Overridden from aura::WindowObserver:
82 virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
84 // Add the filters from |file_types_| to |chooser|.
85 void AddFilters(GtkFileChooser* chooser);
87 // Notifies the listener that a single file was chosen.
88 void FileSelected(GtkWidget* dialog, const FilePath& path);
90 // Notifies the listener that multiple files were chosen.
91 void MultiFilesSelected(GtkWidget* dialog,
92 const std::vector<FilePath>& files);
94 // Notifies the listener that no file was chosen (the action was canceled).
95 // Dialog is passed so we can find that |params| pointer that was passed to
96 // us when we were told to show the dialog.
97 void FileNotSelected(GtkWidget* dialog);
99 GtkWidget* CreateSelectFolderDialog(const std::string& title,
100 const FilePath& default_path, gfx::NativeWindow parent);
102 GtkWidget* CreateFileOpenDialog(const std::string& title,
103 const FilePath& default_path, gfx::NativeWindow parent);
105 GtkWidget* CreateMultiFileOpenDialog(const std::string& title,
106 const FilePath& default_path, gfx::NativeWindow parent);
108 GtkWidget* CreateSaveAsDialog(const std::string& title,
109 const FilePath& default_path, gfx::NativeWindow parent);
111 // Removes and returns the |params| associated with |dialog| from
112 // |params_map_|.
113 void* PopParamsForDialog(GtkWidget* dialog);
115 // Take care of internal data structures when a file dialog is destroyed.
116 void FileDialogDestroyed(GtkWidget* dialog);
118 // Check whether response_id corresponds to the user cancelling/closing the
119 // dialog. Used as a helper for the below callbacks.
120 bool IsCancelResponse(gint response_id);
122 // Common function for OnSelectSingleFileDialogResponse and
123 // OnSelectSingleFolderDialogResponse.
124 void SelectSingleFileHelper(GtkWidget* dialog,
125 gint response_id,
126 bool allow_folder);
128 // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
129 GtkWidget* CreateFileOpenHelper(const std::string& title,
130 const FilePath& default_path,
131 gfx::NativeWindow parent);
133 // Callback for when the user responds to a Save As or Open File dialog.
134 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
135 OnSelectSingleFileDialogResponse, int);
137 // Callback for when the user responds to a Select Folder dialog.
138 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
139 OnSelectSingleFolderDialogResponse, int);
141 // Callback for when the user responds to a Open Multiple Files dialog.
142 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
143 OnSelectMultiFileDialogResponse, int);
145 // Callback for when the file chooser gets destroyed.
146 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnFileChooserDestroy);
148 // Callback for when we update the preview for the selection.
149 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnUpdatePreview);
151 // A map from dialog windows to the |params| user data associated with them.
152 std::map<GtkWidget*, void*> params_map_;
154 // The GtkImage widget for showing previews of selected images.
155 GtkWidget* preview_;
157 // All our dialogs.
158 std::set<GtkWidget*> dialogs_;
160 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplGTK);
163 // The size of the preview we display for selected image files. We set height
164 // larger than width because generally there is more free space vertically
165 // than horiztonally (setting the preview image will alway expand the width of
166 // the dialog, but usually not the height). The image's aspect ratio will always
167 // be preserved.
168 static const int kPreviewWidth = 256;
169 static const int kPreviewHeight = 512;
171 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
172 Listener* listener, ui::SelectFilePolicy* policy) {
173 return new SelectFileDialogImplGTK(listener, policy);
176 SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener* listener,
177 ui::SelectFilePolicy* policy)
178 : SelectFileDialogImpl(listener, policy),
179 preview_(NULL) {
182 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() {
183 while (dialogs_.begin() != dialogs_.end()) {
184 gtk_widget_destroy(*(dialogs_.begin()));
188 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
189 return file_types_.extensions.size() > 1;
192 void SelectFileDialogImplGTK::OnWindowDestroying(aura::Window* window) {
193 std::set<aura::Window*>::iterator iter = parents_.find(window);
194 if (iter != parents_.end()) {
195 (*iter)->RemoveObserver(this);
196 parents_.erase(iter);
200 // We ignore |default_extension|.
201 void SelectFileDialogImplGTK::SelectFileImpl(
202 Type type,
203 const string16& title,
204 const FilePath& default_path,
205 const FileTypeInfo* file_types,
206 int file_type_index,
207 const FilePath::StringType& default_extension,
208 gfx::NativeWindow owning_window,
209 void* params) {
210 type_ = type;
211 // |owning_window| can be null when user right-clicks on a downloadable item
212 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
213 // before downloading.' preference is turned on. (http://crbug.com/29213)
214 if (owning_window) {
215 owning_window->AddObserver(this);
216 parents_.insert(owning_window);
219 std::string title_string = UTF16ToUTF8(title);
221 file_type_index_ = file_type_index;
222 if (file_types)
223 file_types_ = *file_types;
225 GtkWidget* dialog = NULL;
226 switch (type) {
227 case SELECT_FOLDER:
228 dialog = CreateSelectFolderDialog(title_string, default_path,
229 owning_window);
230 break;
231 case SELECT_OPEN_FILE:
232 dialog = CreateFileOpenDialog(title_string, default_path, owning_window);
233 break;
234 case SELECT_OPEN_MULTI_FILE:
235 dialog = CreateMultiFileOpenDialog(title_string, default_path,
236 owning_window);
237 break;
238 case SELECT_SAVEAS_FILE:
239 dialog = CreateSaveAsDialog(title_string, default_path, owning_window);
240 break;
241 default:
242 NOTREACHED();
243 return;
245 g_signal_connect(dialog, "delete-event",
246 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
247 dialogs_.insert(dialog);
249 preview_ = gtk_image_new();
250 g_signal_connect(dialog, "destroy",
251 G_CALLBACK(OnFileChooserDestroyThunk), this);
252 g_signal_connect(dialog, "update-preview",
253 G_CALLBACK(OnUpdatePreviewThunk), this);
254 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview_);
256 params_map_[dialog] = params;
258 // TODO(erg): Figure out how to fake GTK window-to-parent modality without
259 // having the parent be a real GtkWindow.
260 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
262 gtk_widget_show_all(dialog);
265 void SelectFileDialogImplGTK::AddFilters(GtkFileChooser* chooser) {
266 for (size_t i = 0; i < file_types_.extensions.size(); ++i) {
267 GtkFileFilter* filter = NULL;
268 std::set<std::string> fallback_labels;
270 for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) {
271 const std::string& current_extension = file_types_.extensions[i][j];
272 if (!current_extension.empty()) {
273 if (!filter)
274 filter = gtk_file_filter_new();
275 std::string pattern = "*." + current_extension;
276 gtk_file_filter_add_pattern(filter, pattern.c_str());
277 fallback_labels.insert(pattern);
280 // We didn't find any non-empty extensions to filter on.
281 if (!filter)
282 continue;
284 // The description vector may be blank, in which case we are supposed to
285 // use some sort of default description based on the filter.
286 if (i < file_types_.extension_description_overrides.size()) {
287 gtk_file_filter_set_name(filter, UTF16ToUTF8(
288 file_types_.extension_description_overrides[i]).c_str());
289 } else {
290 // There is no system default filter description so we use
291 // the extensions themselves if the description is blank.
292 std::vector<std::string> fallback_labels_vector(fallback_labels.begin(),
293 fallback_labels.end());
294 std::string fallback_label = JoinString(fallback_labels_vector, ',');
295 gtk_file_filter_set_name(filter, fallback_label.c_str());
298 gtk_file_chooser_add_filter(chooser, filter);
299 if (i == file_type_index_ - 1)
300 gtk_file_chooser_set_filter(chooser, filter);
303 // Add the *.* filter, but only if we have added other filters (otherwise it
304 // is implied).
305 if (file_types_.include_all_files && !file_types_.extensions.empty()) {
306 GtkFileFilter* filter = gtk_file_filter_new();
307 gtk_file_filter_add_pattern(filter, "*");
308 gtk_file_filter_set_name(filter,
309 l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES).c_str());
310 gtk_file_chooser_add_filter(chooser, filter);
314 void SelectFileDialogImplGTK::FileSelected(GtkWidget* dialog,
315 const FilePath& path) {
316 if (type_ == SELECT_SAVEAS_FILE)
317 *last_saved_path_ = path.DirName();
318 else if (type_ == SELECT_OPEN_FILE || type_ == SELECT_FOLDER)
319 *last_opened_path_ = path.DirName();
320 else
321 NOTREACHED();
323 if (listener_) {
324 GtkFileFilter* selected_filter =
325 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
326 GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog));
327 int idx = g_slist_index(filters, selected_filter);
328 g_slist_free(filters);
329 listener_->FileSelected(path, idx + 1, PopParamsForDialog(dialog));
331 gtk_widget_destroy(dialog);
334 void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget* dialog,
335 const std::vector<FilePath>& files) {
336 *last_opened_path_ = files[0].DirName();
338 if (listener_)
339 listener_->MultiFilesSelected(files, PopParamsForDialog(dialog));
340 gtk_widget_destroy(dialog);
343 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget* dialog) {
344 void* params = PopParamsForDialog(dialog);
345 if (listener_)
346 listener_->FileSelectionCanceled(params);
347 gtk_widget_destroy(dialog);
350 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenHelper(
351 const std::string& title,
352 const FilePath& default_path,
353 gfx::NativeWindow parent) {
354 GtkWidget* dialog =
355 gtk_file_chooser_dialog_new(title.c_str(), NULL,
356 GTK_FILE_CHOOSER_ACTION_OPEN,
357 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
358 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
359 NULL);
360 SetGtkTransientForAura(dialog, parent);
361 AddFilters(GTK_FILE_CHOOSER(dialog));
363 if (!default_path.empty()) {
364 if (CallDirectoryExistsOnUIThread(default_path)) {
365 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
366 default_path.value().c_str());
367 } else {
368 // If the file doesn't exist, this will just switch to the correct
369 // directory. That's good enough.
370 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
371 default_path.value().c_str());
373 } else if (!last_opened_path_->empty()) {
374 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
375 last_opened_path_->value().c_str());
377 return dialog;
380 GtkWidget* SelectFileDialogImplGTK::CreateSelectFolderDialog(
381 const std::string& title,
382 const FilePath& default_path,
383 gfx::NativeWindow parent) {
384 std::string title_string = !title.empty() ? title :
385 l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
387 GtkWidget* dialog =
388 gtk_file_chooser_dialog_new(title_string.c_str(), NULL,
389 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
390 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
391 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
392 NULL);
393 SetGtkTransientForAura(dialog, parent);
395 if (!default_path.empty()) {
396 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
397 default_path.value().c_str());
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());
402 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
403 g_signal_connect(dialog, "response",
404 G_CALLBACK(OnSelectSingleFolderDialogResponseThunk), this);
405 return dialog;
408 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenDialog(
409 const std::string& title,
410 const FilePath& default_path,
411 gfx::NativeWindow parent) {
412 std::string title_string = !title.empty() ? title :
413 l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE);
415 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
416 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
417 g_signal_connect(dialog, "response",
418 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
419 return dialog;
422 GtkWidget* SelectFileDialogImplGTK::CreateMultiFileOpenDialog(
423 const std::string& title,
424 const FilePath& default_path,
425 gfx::NativeWindow parent) {
426 std::string title_string = !title.empty() ? title :
427 l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE);
429 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
430 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
431 g_signal_connect(dialog, "response",
432 G_CALLBACK(OnSelectMultiFileDialogResponseThunk), this);
433 return dialog;
436 GtkWidget* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string& title,
437 const FilePath& default_path, gfx::NativeWindow parent) {
438 std::string title_string = !title.empty() ? title :
439 l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
441 GtkWidget* dialog =
442 gtk_file_chooser_dialog_new(title_string.c_str(), NULL,
443 GTK_FILE_CHOOSER_ACTION_SAVE,
444 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
445 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
446 NULL);
447 SetGtkTransientForAura(dialog, parent);
449 AddFilters(GTK_FILE_CHOOSER(dialog));
450 if (!default_path.empty()) {
451 // Since the file may not already exist, we use
452 // set_current_folder() followed by set_current_name(), as per the
453 // recommendation of the GTK docs.
454 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
455 default_path.DirName().value().c_str());
456 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),
457 default_path.BaseName().value().c_str());
458 } else if (!last_saved_path_->empty()) {
459 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
460 last_saved_path_->value().c_str());
462 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
463 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
464 TRUE);
465 g_signal_connect(dialog, "response",
466 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
467 return dialog;
470 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget* dialog) {
471 std::map<GtkWidget*, void*>::iterator iter = params_map_.find(dialog);
472 DCHECK(iter != params_map_.end());
473 void* params = iter->second;
474 params_map_.erase(iter);
475 return params;
478 void SelectFileDialogImplGTK::FileDialogDestroyed(GtkWidget* dialog) {
479 dialogs_.erase(dialog);
481 // Parent may be NULL in a few cases: 1) on shutdown when
482 // AllBrowsersClosed() trigger this handler after all the browser
483 // windows got destroyed, or 2) when the parent tab has been opened by
484 // 'Open Link in New Tab' context menu on a downloadable item and
485 // the tab has no content (see the comment in SelectFile as well).
486 aura::Window* parent = reinterpret_cast<aura::Window*>(
487 g_object_get_data(G_OBJECT(dialog), kAuraTransientParent));
488 if (!parent)
489 return;
490 std::set<aura::Window*>::iterator iter = parents_.find(parent);
491 if (iter != parents_.end()) {
492 (*iter)->RemoveObserver(this);
493 parents_.erase(iter);
494 } else {
495 NOTREACHED();
499 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id) {
500 bool is_cancel = response_id == GTK_RESPONSE_CANCEL ||
501 response_id == GTK_RESPONSE_DELETE_EVENT;
502 if (is_cancel)
503 return true;
505 DCHECK(response_id == GTK_RESPONSE_ACCEPT);
506 return false;
509 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget* dialog,
510 gint response_id,
511 bool allow_folder) {
512 if (IsCancelResponse(response_id)) {
513 FileNotSelected(dialog);
514 return;
517 gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
518 if (!filename) {
519 FileNotSelected(dialog);
520 return;
523 FilePath path(filename);
524 g_free(filename);
526 if (allow_folder) {
527 FileSelected(dialog, path);
528 return;
531 if (CallDirectoryExistsOnUIThread(path))
532 FileNotSelected(dialog);
533 else
534 FileSelected(dialog, path);
537 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
538 GtkWidget* dialog, int response_id) {
539 SelectSingleFileHelper(dialog, response_id, false);
542 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
543 GtkWidget* dialog, int response_id) {
544 SelectSingleFileHelper(dialog, response_id, true);
547 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget* dialog,
548 int response_id) {
549 if (IsCancelResponse(response_id)) {
550 FileNotSelected(dialog);
551 return;
554 GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
555 if (!filenames) {
556 FileNotSelected(dialog);
557 return;
560 std::vector<FilePath> filenames_fp;
561 for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) {
562 FilePath path(static_cast<char*>(iter->data));
563 g_free(iter->data);
564 if (CallDirectoryExistsOnUIThread(path))
565 continue;
566 filenames_fp.push_back(path);
568 g_slist_free(filenames);
570 if (filenames_fp.empty()) {
571 FileNotSelected(dialog);
572 return;
574 MultiFilesSelected(dialog, filenames_fp);
577 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget* dialog) {
578 FileDialogDestroyed(dialog);
581 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget* chooser) {
582 gchar* filename = gtk_file_chooser_get_preview_filename(
583 GTK_FILE_CHOOSER(chooser));
584 if (!filename)
585 return;
586 // This will preserve the image's aspect ratio.
587 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
588 kPreviewHeight, NULL);
589 g_free(filename);
590 if (pixbuf) {
591 gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
592 g_object_unref(pixbuf);
594 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
595 pixbuf ? TRUE : FALSE);
598 } // namespace libgtk2ui