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