Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(57)

Side by Side Diff: chrome/browser/ui/libgtk2ui/select_file_dialog_impl_gtk2.cc

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

Powered by Google App Engine
This is Rietveld 408576698