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

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

Issue 10829021: Use current gtk dialogs in linux_aura. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: derat nits Created 8 years, 4 months 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 | Annotate | Revision Log
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 <gtk/gtk.h>
6 #include <map>
7 #include <set>
8 #include <vector>
9
10 #include "base/file_util.h"
11 #include "base/logging.h"
12 #include "base/message_loop.h"
13 #include "base/string_util.h"
14 #include "base/sys_string_conversions.h"
15 #include "base/threading/thread.h"
16 #include "base/threading/thread_restrictions.h"
17 #include "base/utf_string_conversions.h"
18 //#include "chrome/browser/ui/gtk/select_file_dialog_impl.h"
19 #include "chrome/browser/ui/select_file_dialog.h"
20 #include "grit/generated_resources.h"
21 //#include "ui/base/gtk/gtk_signal.h"
22 #include "ui/base/l10n/l10n_util.h"
23
24 // Implementation of SelectFileDialog that shows a Gtk common dialog for
25 // choosing a file or folder. This acts as a modal dialog.
26 class SelectFileDialogImplGTK : public SelectFileDialogImpl {
27 public:
28 explicit SelectFileDialogImplGTK(Listener* listener,
29 ui::SelectFilePolicy* policy);
30
31 protected:
32 virtual ~SelectFileDialogImplGTK();
33
34 // SelectFileDialog implementation.
35 // |params| is user data we pass back via the Listener interface.
36 virtual void SelectFileImpl(Type type,
37 const string16& title,
38 const FilePath& default_path,
39 const FileTypeInfo* file_types,
40 int file_type_index,
41 const FilePath::StringType& default_extension,
42 gfx::NativeWindow owning_window,
43 void* params) OVERRIDE;
44
45 private:
46 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE;
47
48 // Add the filters from |file_types_| to |chooser|.
49 void AddFilters(GtkFileChooser* chooser);
50
51 // Notifies the listener that a single file was chosen.
52 void FileSelected(GtkWidget* dialog, const FilePath& path);
53
54 // Notifies the listener that multiple files were chosen.
55 void MultiFilesSelected(GtkWidget* dialog,
56 const std::vector<FilePath>& files);
57
58 // Notifies the listener that no file was chosen (the action was canceled).
59 // Dialog is passed so we can find that |params| pointer that was passed to
60 // us when we were told to show the dialog.
61 void FileNotSelected(GtkWidget* dialog);
62
63 GtkWidget* CreateSelectFolderDialog(const std::string& title,
64 const FilePath& default_path, gfx::NativeWindow parent);
65
66 GtkWidget* CreateFileOpenDialog(const std::string& title,
67 const FilePath& default_path, gfx::NativeWindow parent);
68
69 GtkWidget* CreateMultiFileOpenDialog(const std::string& title,
70 const FilePath& default_path, gfx::NativeWindow parent);
71
72 GtkWidget* CreateSaveAsDialog(const std::string& title,
73 const FilePath& default_path, gfx::NativeWindow parent);
74
75 // Removes and returns the |params| associated with |dialog| from
76 // |params_map_|.
77 void* PopParamsForDialog(GtkWidget* dialog);
78
79 // Take care of internal data structures when a file dialog is destroyed.
80 void FileDialogDestroyed(GtkWidget* dialog);
81
82 // Check whether response_id corresponds to the user cancelling/closing the
83 // dialog. Used as a helper for the below callbacks.
84 bool IsCancelResponse(gint response_id);
85
86 // Common function for OnSelectSingleFileDialogResponse and
87 // OnSelectSingleFolderDialogResponse.
88 void SelectSingleFileHelper(GtkWidget* dialog,
89 gint response_id,
90 bool allow_folder);
91
92 // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
93 GtkWidget* CreateFileOpenHelper(const std::string& title,
94 const FilePath& default_path,
95 gfx::NativeWindow parent);
96
97 // Callback for when the user responds to a Save As or Open File dialog.
98 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
99 OnSelectSingleFileDialogResponse, int);
100
101 // Callback for when the user responds to a Select Folder dialog.
102 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
103 OnSelectSingleFolderDialogResponse, int);
104
105 // Callback for when the user responds to a Open Multiple Files dialog.
106 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK, void,
107 OnSelectMultiFileDialogResponse, int);
108
109 // Callback for when the file chooser gets destroyed.
110 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnFileChooserDestroy);
111
112 // Callback for when we update the preview for the selection.
113 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK, void, OnUpdatePreview);
114
115 // A map from dialog windows to the |params| user data associated with them.
116 std::map<GtkWidget*, void*> params_map_;
117
118 // The GtkImage widget for showing previews of selected images.
119 GtkWidget* preview_;
120
121 // All our dialogs.
122 std::set<GtkWidget*> dialogs_;
123
124 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplGTK);
125 };
126
127 // The size of the preview we display for selected image files. We set height
128 // larger than width because generally there is more free space vertically
129 // than horiztonally (setting the preview image will alway expand the width of
130 // the dialog, but usually not the height). The image's aspect ratio will always
131 // be preserved.
132 static const int kPreviewWidth = 256;
133 static const int kPreviewHeight = 512;
134
135 SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
136 Listener* listener, ui::SelectFilePolicy* policy) {
137 return new SelectFileDialogImplGTK(listener, policy);
138 }
139
140 SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener* listener,
141 ui::SelectFilePolicy* policy)
142 : SelectFileDialogImpl(listener, policy),
143 preview_(NULL) {
144 }
145
146 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() {
147 while (dialogs_.begin() != dialogs_.end()) {
148 gtk_widget_destroy(*(dialogs_.begin()));
149 }
150 }
151
152 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
153 return file_types_.extensions.size() > 1;
154 }
155
156 // We ignore |default_extension|.
157 void SelectFileDialogImplGTK::SelectFileImpl(
158 Type type,
159 const string16& title,
160 const FilePath& default_path,
161 const FileTypeInfo* file_types,
162 int file_type_index,
163 const FilePath::StringType& default_extension,
164 gfx::NativeWindow owning_window,
165 void* params) {
166 type_ = type;
167 // |owning_window| can be null when user right-clicks on a downloadable item
168 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
169 // before downloading.' preference is turned on. (http://crbug.com/29213)
170 if (owning_window)
171 parents_.insert(owning_window);
172
173 std::string title_string = UTF16ToUTF8(title);
174
175 file_type_index_ = file_type_index;
176 if (file_types)
177 file_types_ = *file_types;
178 else
179 file_types_.include_all_files = true;
180
181 GtkWidget* dialog = NULL;
182 switch (type) {
183 case SELECT_FOLDER:
184 dialog = CreateSelectFolderDialog(title_string, default_path,
185 owning_window);
186 break;
187 case SELECT_OPEN_FILE:
188 dialog = CreateFileOpenDialog(title_string, default_path, owning_window);
189 break;
190 case SELECT_OPEN_MULTI_FILE:
191 dialog = CreateMultiFileOpenDialog(title_string, default_path,
192 owning_window);
193 break;
194 case SELECT_SAVEAS_FILE:
195 dialog = CreateSaveAsDialog(title_string, default_path, owning_window);
196 break;
197 default:
198 NOTREACHED();
199 return;
200 }
201 g_signal_connect(dialog, "delete-event",
202 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
203 dialogs_.insert(dialog);
204
205 preview_ = gtk_image_new();
206 g_signal_connect(dialog, "destroy",
207 G_CALLBACK(OnFileChooserDestroyThunk), this);
208 g_signal_connect(dialog, "update-preview",
209 G_CALLBACK(OnUpdatePreviewThunk), this);
210 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview_);
211
212 params_map_[dialog] = params;
213
214 // Set window-to-parent modality by adding the dialog to the same window
215 // group as the parent.
216 gtk_window_group_add_window(gtk_window_get_group(owning_window),
217 GTK_WINDOW(dialog));
218 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
219
220 gtk_widget_show_all(dialog);
221 }
222
223 void SelectFileDialogImplGTK::AddFilters(GtkFileChooser* chooser) {
224 for (size_t i = 0; i < file_types_.extensions.size(); ++i) {
225 GtkFileFilter* filter = NULL;
226 std::set<std::string> fallback_labels;
227
228 for (size_t j = 0; j < file_types_.extensions[i].size(); ++j) {
229 const std::string& current_extension = file_types_.extensions[i][j];
230 if (!current_extension.empty()) {
231 if (!filter)
232 filter = gtk_file_filter_new();
233 std::string pattern = "*." + current_extension;
234 gtk_file_filter_add_pattern(filter, pattern.c_str());
235 fallback_labels.insert(pattern);
236 }
237 }
238 // We didn't find any non-empty extensions to filter on.
239 if (!filter)
240 continue;
241
242 // The description vector may be blank, in which case we are supposed to
243 // use some sort of default description based on the filter.
244 if (i < file_types_.extension_description_overrides.size()) {
245 gtk_file_filter_set_name(filter, UTF16ToUTF8(
246 file_types_.extension_description_overrides[i]).c_str());
247 } else {
248 // There is no system default filter description so we use
249 // the extensions themselves if the description is blank.
250 std::vector<std::string> fallback_labels_vector(fallback_labels.begin(),
251 fallback_labels.end());
252 std::string fallback_label = 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(filter,
267 l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES).c_str());
268 gtk_file_chooser_add_filter(chooser, filter);
269 }
270 }
271
272 void SelectFileDialogImplGTK::FileSelected(GtkWidget* dialog,
273 const 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 *last_opened_path_ = path.DirName();
278 else
279 NOTREACHED();
280
281 if (listener_) {
282 GtkFileFilter* selected_filter =
283 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
284 GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog));
285 int idx = g_slist_index(filters, selected_filter);
286 g_slist_free(filters);
287 listener_->FileSelected(path, idx + 1, PopParamsForDialog(dialog));
288 }
289 gtk_widget_destroy(dialog);
290 }
291
292 void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget* dialog,
293 const std::vector<FilePath>& files) {
294 *last_opened_path_ = files[0].DirName();
295
296 if (listener_)
297 listener_->MultiFilesSelected(files, PopParamsForDialog(dialog));
298 gtk_widget_destroy(dialog);
299 }
300
301 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget* dialog) {
302 void* params = PopParamsForDialog(dialog);
303 if (listener_)
304 listener_->FileSelectionCanceled(params);
305 gtk_widget_destroy(dialog);
306 }
307
308 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenHelper(
309 const std::string& title,
310 const FilePath& default_path,
311 gfx::NativeWindow parent) {
312 GtkWidget* dialog =
313 gtk_file_chooser_dialog_new(title.c_str(), parent,
314 GTK_FILE_CHOOSER_ACTION_OPEN,
315 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
316 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
317 NULL);
318 AddFilters(GTK_FILE_CHOOSER(dialog));
319
320 if (!default_path.empty()) {
321 if (CallDirectoryExistsOnUIThread(default_path)) {
322 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
323 default_path.value().c_str());
324 } else {
325 // If the file doesn't exist, this will just switch to the correct
326 // directory. That's good enough.
327 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
328 default_path.value().c_str());
329 }
330 } else if (!last_opened_path_->empty()) {
331 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
332 last_opened_path_->value().c_str());
333 }
334 return dialog;
335 }
336
337 GtkWidget* SelectFileDialogImplGTK::CreateSelectFolderDialog(
338 const std::string& title,
339 const FilePath& default_path,
340 gfx::NativeWindow parent) {
341 std::string title_string = !title.empty() ? title :
342 l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
343
344 GtkWidget* dialog =
345 gtk_file_chooser_dialog_new(title_string.c_str(), parent,
346 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
347 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
348 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
349 NULL);
350
351 if (!default_path.empty()) {
352 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
353 default_path.value().c_str());
354 } else if (!last_opened_path_->empty()) {
355 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
356 last_opened_path_->value().c_str());
357 }
358 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
359 g_signal_connect(dialog, "response",
360 G_CALLBACK(OnSelectSingleFolderDialogResponseThunk), this);
361 return dialog;
362 }
363
364 GtkWidget* SelectFileDialogImplGTK::CreateFileOpenDialog(
365 const std::string& title,
366 const FilePath& default_path,
367 gfx::NativeWindow parent) {
368 std::string title_string = !title.empty() ? title :
369 l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE);
370
371 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
372 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
373 g_signal_connect(dialog, "response",
374 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
375 return dialog;
376 }
377
378 GtkWidget* SelectFileDialogImplGTK::CreateMultiFileOpenDialog(
379 const std::string& title,
380 const FilePath& default_path,
381 gfx::NativeWindow parent) {
382 std::string title_string = !title.empty() ? title :
383 l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE);
384
385 GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
386 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
387 g_signal_connect(dialog, "response",
388 G_CALLBACK(OnSelectMultiFileDialogResponseThunk), this);
389 return dialog;
390 }
391
392 GtkWidget* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string& title,
393 const FilePath& default_path, gfx::NativeWindow parent) {
394 std::string title_string = !title.empty() ? title :
395 l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
396
397 GtkWidget* dialog =
398 gtk_file_chooser_dialog_new(title_string.c_str(), parent,
399 GTK_FILE_CHOOSER_ACTION_SAVE,
400 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
401 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
402 NULL);
403
404 AddFilters(GTK_FILE_CHOOSER(dialog));
405 if (!default_path.empty()) {
406 // Since the file may not already exist, we use
407 // set_current_folder() followed by set_current_name(), as per the
408 // recommendation of the GTK docs.
409 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
410 default_path.DirName().value().c_str());
411 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),
412 default_path.BaseName().value().c_str());
413 } else if (!last_saved_path_->empty()) {
414 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
415 last_saved_path_->value().c_str());
416 }
417 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
418 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
419 TRUE);
420 g_signal_connect(dialog, "response",
421 G_CALLBACK(OnSelectSingleFileDialogResponseThunk), this);
422 return dialog;
423 }
424
425 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget* dialog) {
426 std::map<GtkWidget*, void*>::iterator iter = params_map_.find(dialog);
427 DCHECK(iter != params_map_.end());
428 void* params = iter->second;
429 params_map_.erase(iter);
430 return params;
431 }
432
433 void SelectFileDialogImplGTK::FileDialogDestroyed(GtkWidget* dialog) {
434 dialogs_.erase(dialog);
435
436 // Parent may be NULL in a few cases: 1) on shutdown when
437 // AllBrowsersClosed() trigger this handler after all the browser
438 // windows got destroyed, or 2) when the parent tab has been opened by
439 // 'Open Link in New Tab' context menu on a downloadable item and
440 // the tab has no content (see the comment in SelectFile as well).
441 GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(dialog));
442 if (!parent)
443 return;
444 std::set<GtkWindow*>::iterator iter = parents_.find(parent);
445 if (iter != parents_.end())
446 parents_.erase(iter);
447 else
448 NOTREACHED();
449 }
450
451 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id) {
452 bool is_cancel = response_id == GTK_RESPONSE_CANCEL ||
453 response_id == GTK_RESPONSE_DELETE_EVENT;
454 if (is_cancel)
455 return true;
456
457 DCHECK(response_id == GTK_RESPONSE_ACCEPT);
458 return false;
459 }
460
461 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget* dialog,
462 gint response_id,
463 bool allow_folder) {
464 if (IsCancelResponse(response_id)) {
465 FileNotSelected(dialog);
466 return;
467 }
468
469 gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
470 if (!filename) {
471 FileNotSelected(dialog);
472 return;
473 }
474
475 FilePath path(filename);
476 g_free(filename);
477
478 if (allow_folder) {
479 FileSelected(dialog, path);
480 return;
481 }
482
483 if (CallDirectoryExistsOnUIThread(path))
484 FileNotSelected(dialog);
485 else
486 FileSelected(dialog, path);
487 }
488
489 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
490 GtkWidget* dialog, int response_id) {
491 SelectSingleFileHelper(dialog, response_id, false);
492 }
493
494 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
495 GtkWidget* dialog, int response_id) {
496 SelectSingleFileHelper(dialog, response_id, true);
497 }
498
499 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget* dialog,
500 int response_id) {
501 if (IsCancelResponse(response_id)) {
502 FileNotSelected(dialog);
503 return;
504 }
505
506 GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
507 if (!filenames) {
508 FileNotSelected(dialog);
509 return;
510 }
511
512 std::vector<FilePath> filenames_fp;
513 for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) {
514 FilePath path(static_cast<char*>(iter->data));
515 g_free(iter->data);
516 if (CallDirectoryExistsOnUIThread(path))
517 continue;
518 filenames_fp.push_back(path);
519 }
520 g_slist_free(filenames);
521
522 if (filenames_fp.empty()) {
523 FileNotSelected(dialog);
524 return;
525 }
526 MultiFilesSelected(dialog, filenames_fp);
527 }
528
529 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget* dialog) {
530 FileDialogDestroyed(dialog);
531 }
532
533 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget* chooser) {
534 gchar* filename = gtk_file_chooser_get_preview_filename(
535 GTK_FILE_CHOOSER(chooser));
536 if (!filename)
537 return;
538 // This will preserve the image's aspect ratio.
539 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
540 kPreviewHeight, NULL);
541 g_free(filename);
542 if (pixbuf) {
543 gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
544 g_object_unref(pixbuf);
545 }
546 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
547 pixbuf ? TRUE : FALSE);
548 }
OLDNEW
« no previous file with comments | « chrome/browser/ui/libgtk2ui/select_file_dialog_impl.cc ('k') | chrome/browser/ui/libgtk2ui/select_file_dialog_impl_gtk2.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698