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.
12 // Xlib defines RootWindow
15 #include "base/logging.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/message_loop/message_loop.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/sys_string_conversions.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/threading/thread.h"
22 #include "base/threading/thread_restrictions.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/l10n/l10n_util.h"
29 #include "ui/shell_dialogs/select_file_dialog.h"
33 const char kAuraTransientParent
[] = "aura-transient-parent";
35 // Set |dialog| as transient for |parent|, which will keep it on top and center
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
->GetDispatcher()->host()->GetAcceleratedWidget());
47 // We also set the |parent| as a property of |dialog|, so that we can unlink
49 g_object_set_data(G_OBJECT(dialog
), kAuraTransientParent
, parent
);
52 // Makes sure that .jpg also shows .JPG.
53 gboolean
FileFilterCaseInsensitive(const GtkFileFilterInfo
* file_info
,
54 std::string
* file_extension
) {
55 return EndsWith(file_info
->filename
, *file_extension
, false);
58 // Deletes |data| when gtk_file_filter_add_custom() is done with it.
59 void OnFileFilterDataDestroyed(std::string
* file_extension
) {
60 delete file_extension
;
67 // Implementation of SelectFileDialog that shows a Gtk common dialog for
68 // choosing a file or folder. This acts as a modal dialog.
69 class SelectFileDialogImplGTK
: public SelectFileDialogImpl
,
70 public aura::WindowObserver
{
72 explicit SelectFileDialogImplGTK(Listener
* listener
,
73 ui::SelectFilePolicy
* policy
);
76 virtual ~SelectFileDialogImplGTK();
78 // SelectFileDialog implementation.
79 // |params| is user data we pass back via the Listener interface.
80 virtual void SelectFileImpl(
82 const base::string16
& title
,
83 const base::FilePath
& default_path
,
84 const FileTypeInfo
* file_types
,
86 const base::FilePath::StringType
& default_extension
,
87 gfx::NativeWindow owning_window
,
88 void* params
) OVERRIDE
;
91 virtual bool HasMultipleFileTypeChoicesImpl() OVERRIDE
;
93 // Overridden from aura::WindowObserver:
94 virtual void OnWindowDestroying(aura::Window
* window
) OVERRIDE
;
96 // Add the filters from |file_types_| to |chooser|.
97 void AddFilters(GtkFileChooser
* chooser
);
99 // Notifies the listener that a single file was chosen.
100 void FileSelected(GtkWidget
* dialog
, const base::FilePath
& path
);
102 // Notifies the listener that multiple files were chosen.
103 void MultiFilesSelected(GtkWidget
* dialog
,
104 const std::vector
<base::FilePath
>& files
);
106 // Notifies the listener that no file was chosen (the action was canceled).
107 // Dialog is passed so we can find that |params| pointer that was passed to
108 // us when we were told to show the dialog.
109 void FileNotSelected(GtkWidget
* dialog
);
111 GtkWidget
* CreateSelectFolderDialog(
113 const std::string
& title
,
114 const base::FilePath
& default_path
,
115 gfx::NativeWindow parent
);
117 GtkWidget
* CreateFileOpenDialog(const std::string
& title
,
118 const base::FilePath
& default_path
, gfx::NativeWindow parent
);
120 GtkWidget
* CreateMultiFileOpenDialog(const std::string
& title
,
121 const base::FilePath
& default_path
, gfx::NativeWindow parent
);
123 GtkWidget
* CreateSaveAsDialog(const std::string
& title
,
124 const base::FilePath
& default_path
, gfx::NativeWindow parent
);
126 // Removes and returns the |params| associated with |dialog| from
128 void* PopParamsForDialog(GtkWidget
* dialog
);
130 // Take care of internal data structures when a file dialog is destroyed.
131 void FileDialogDestroyed(GtkWidget
* dialog
);
133 // Check whether response_id corresponds to the user cancelling/closing the
134 // dialog. Used as a helper for the below callbacks.
135 bool IsCancelResponse(gint response_id
);
137 // Common function for OnSelectSingleFileDialogResponse and
138 // OnSelectSingleFolderDialogResponse.
139 void SelectSingleFileHelper(GtkWidget
* dialog
,
143 // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
144 GtkWidget
* CreateFileOpenHelper(const std::string
& title
,
145 const base::FilePath
& default_path
,
146 gfx::NativeWindow parent
);
148 // Callback for when the user responds to a Save As or Open File dialog.
149 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK
, void,
150 OnSelectSingleFileDialogResponse
, int);
152 // Callback for when the user responds to a Select Folder dialog.
153 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK
, void,
154 OnSelectSingleFolderDialogResponse
, int);
156 // Callback for when the user responds to a Open Multiple Files dialog.
157 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK
, void,
158 OnSelectMultiFileDialogResponse
, int);
160 // Callback for when the file chooser gets destroyed.
161 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK
, void, OnFileChooserDestroy
);
163 // Callback for when we update the preview for the selection.
164 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK
, void, OnUpdatePreview
);
166 // A map from dialog windows to the |params| user data associated with them.
167 std::map
<GtkWidget
*, void*> params_map_
;
169 // The GtkImage widget for showing previews of selected images.
173 std::set
<GtkWidget
*> dialogs_
;
175 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplGTK
);
178 // The size of the preview we display for selected image files. We set height
179 // larger than width because generally there is more free space vertically
180 // than horiztonally (setting the preview image will alway expand the width of
181 // the dialog, but usually not the height). The image's aspect ratio will always
183 static const int kPreviewWidth
= 256;
184 static const int kPreviewHeight
= 512;
186 SelectFileDialogImpl
* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
187 Listener
* listener
, ui::SelectFilePolicy
* policy
) {
188 return new SelectFileDialogImplGTK(listener
, policy
);
191 SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener
* listener
,
192 ui::SelectFilePolicy
* policy
)
193 : SelectFileDialogImpl(listener
, policy
),
197 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() {
198 while (dialogs_
.begin() != dialogs_
.end()) {
199 gtk_widget_destroy(*(dialogs_
.begin()));
203 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
204 return file_types_
.extensions
.size() > 1;
207 void SelectFileDialogImplGTK::OnWindowDestroying(aura::Window
* window
) {
208 std::set
<aura::Window
*>::iterator iter
= parents_
.find(window
);
209 if (iter
!= parents_
.end()) {
210 (*iter
)->RemoveObserver(this);
211 parents_
.erase(iter
);
215 // We ignore |default_extension|.
216 void SelectFileDialogImplGTK::SelectFileImpl(
218 const base::string16
& title
,
219 const base::FilePath
& default_path
,
220 const FileTypeInfo
* file_types
,
222 const base::FilePath::StringType
& default_extension
,
223 gfx::NativeWindow owning_window
,
226 // |owning_window| can be null when user right-clicks on a downloadable item
227 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
228 // before downloading.' preference is turned on. (http://crbug.com/29213)
230 owning_window
->AddObserver(this);
231 parents_
.insert(owning_window
);
234 std::string title_string
= base::UTF16ToUTF8(title
);
236 file_type_index_
= file_type_index
;
238 file_types_
= *file_types
;
240 GtkWidget
* dialog
= NULL
;
243 case SELECT_UPLOAD_FOLDER
:
244 dialog
= CreateSelectFolderDialog(type
, title_string
, default_path
,
247 case SELECT_OPEN_FILE
:
248 dialog
= CreateFileOpenDialog(title_string
, default_path
, owning_window
);
250 case SELECT_OPEN_MULTI_FILE
:
251 dialog
= CreateMultiFileOpenDialog(title_string
, default_path
,
254 case SELECT_SAVEAS_FILE
:
255 dialog
= CreateSaveAsDialog(title_string
, default_path
, owning_window
);
261 g_signal_connect(dialog
, "delete-event",
262 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
263 dialogs_
.insert(dialog
);
265 preview_
= gtk_image_new();
266 g_signal_connect(dialog
, "destroy",
267 G_CALLBACK(OnFileChooserDestroyThunk
), this);
268 g_signal_connect(dialog
, "update-preview",
269 G_CALLBACK(OnUpdatePreviewThunk
), this);
270 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog
), preview_
);
272 params_map_
[dialog
] = params
;
274 // TODO(erg): Figure out how to fake GTK window-to-parent modality without
275 // having the parent be a real GtkWindow.
276 gtk_window_set_modal(GTK_WINDOW(dialog
), TRUE
);
278 gtk_widget_show_all(dialog
);
281 void SelectFileDialogImplGTK::AddFilters(GtkFileChooser
* chooser
) {
282 for (size_t i
= 0; i
< file_types_
.extensions
.size(); ++i
) {
283 GtkFileFilter
* filter
= NULL
;
284 std::set
<std::string
> fallback_labels
;
286 for (size_t j
= 0; j
< file_types_
.extensions
[i
].size(); ++j
) {
287 const std::string
& current_extension
= file_types_
.extensions
[i
][j
];
288 if (!current_extension
.empty()) {
290 filter
= gtk_file_filter_new();
291 scoped_ptr
<std::string
> file_extension(
292 new std::string("." + current_extension
));
293 fallback_labels
.insert(std::string("*").append(*file_extension
));
294 gtk_file_filter_add_custom(
296 GTK_FILE_FILTER_FILENAME
,
297 reinterpret_cast<GtkFileFilterFunc
>(FileFilterCaseInsensitive
),
298 file_extension
.release(),
299 reinterpret_cast<GDestroyNotify
>(OnFileFilterDataDestroyed
));
302 // We didn't find any non-empty extensions to filter on.
306 // The description vector may be blank, in which case we are supposed to
307 // use some sort of default description based on the filter.
308 if (i
< file_types_
.extension_description_overrides
.size()) {
309 gtk_file_filter_set_name(filter
, base::UTF16ToUTF8(
310 file_types_
.extension_description_overrides
[i
]).c_str());
312 // There is no system default filter description so we use
313 // the extensions themselves if the description is blank.
314 std::vector
<std::string
> fallback_labels_vector(fallback_labels
.begin(),
315 fallback_labels
.end());
316 std::string fallback_label
= JoinString(fallback_labels_vector
, ',');
317 gtk_file_filter_set_name(filter
, fallback_label
.c_str());
320 gtk_file_chooser_add_filter(chooser
, filter
);
321 if (i
== file_type_index_
- 1)
322 gtk_file_chooser_set_filter(chooser
, filter
);
325 // Add the *.* filter, but only if we have added other filters (otherwise it
327 if (file_types_
.include_all_files
&& !file_types_
.extensions
.empty()) {
328 GtkFileFilter
* filter
= gtk_file_filter_new();
329 gtk_file_filter_add_pattern(filter
, "*");
330 gtk_file_filter_set_name(filter
,
331 l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES
).c_str());
332 gtk_file_chooser_add_filter(chooser
, filter
);
336 void SelectFileDialogImplGTK::FileSelected(GtkWidget
* dialog
,
337 const base::FilePath
& path
) {
338 if (type_
== SELECT_SAVEAS_FILE
) {
339 *last_saved_path_
= path
.DirName();
340 } else if (type_
== SELECT_OPEN_FILE
|| type_
== SELECT_FOLDER
||
341 type_
== SELECT_UPLOAD_FOLDER
) {
342 *last_opened_path_
= path
.DirName();
348 GtkFileFilter
* selected_filter
=
349 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog
));
350 GSList
* filters
= gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog
));
351 int idx
= g_slist_index(filters
, selected_filter
);
352 g_slist_free(filters
);
353 listener_
->FileSelected(path
, idx
+ 1, PopParamsForDialog(dialog
));
355 gtk_widget_destroy(dialog
);
358 void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget
* dialog
,
359 const std::vector
<base::FilePath
>& files
) {
360 *last_opened_path_
= files
[0].DirName();
363 listener_
->MultiFilesSelected(files
, PopParamsForDialog(dialog
));
364 gtk_widget_destroy(dialog
);
367 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget
* dialog
) {
368 void* params
= PopParamsForDialog(dialog
);
370 listener_
->FileSelectionCanceled(params
);
371 gtk_widget_destroy(dialog
);
374 GtkWidget
* SelectFileDialogImplGTK::CreateFileOpenHelper(
375 const std::string
& title
,
376 const base::FilePath
& default_path
,
377 gfx::NativeWindow parent
) {
379 gtk_file_chooser_dialog_new(title
.c_str(), NULL
,
380 GTK_FILE_CHOOSER_ACTION_OPEN
,
381 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
382 GTK_STOCK_OPEN
, GTK_RESPONSE_ACCEPT
,
384 SetGtkTransientForAura(dialog
, parent
);
385 AddFilters(GTK_FILE_CHOOSER(dialog
));
387 if (!default_path
.empty()) {
388 if (CallDirectoryExistsOnUIThread(default_path
)) {
389 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
390 default_path
.value().c_str());
392 // If the file doesn't exist, this will just switch to the correct
393 // directory. That's good enough.
394 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog
),
395 default_path
.value().c_str());
397 } else if (!last_opened_path_
->empty()) {
398 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
399 last_opened_path_
->value().c_str());
404 GtkWidget
* SelectFileDialogImplGTK::CreateSelectFolderDialog(
406 const std::string
& title
,
407 const base::FilePath
& default_path
,
408 gfx::NativeWindow parent
) {
409 std::string title_string
= title
;
410 if (title_string
.empty()) {
411 title_string
= (type
== SELECT_UPLOAD_FOLDER
) ?
412 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE
) :
413 l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE
);
415 std::string accept_button_label
= (type
== SELECT_UPLOAD_FOLDER
) ?
416 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON
) :
420 gtk_file_chooser_dialog_new(title_string
.c_str(), NULL
,
421 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
,
422 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
423 accept_button_label
.c_str(),
426 SetGtkTransientForAura(dialog
, parent
);
428 if (!default_path
.empty()) {
429 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog
),
430 default_path
.value().c_str());
431 } else if (!last_opened_path_
->empty()) {
432 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
433 last_opened_path_
->value().c_str());
435 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog
), FALSE
);
436 g_signal_connect(dialog
, "response",
437 G_CALLBACK(OnSelectSingleFolderDialogResponseThunk
), this);
441 GtkWidget
* SelectFileDialogImplGTK::CreateFileOpenDialog(
442 const std::string
& title
,
443 const base::FilePath
& default_path
,
444 gfx::NativeWindow parent
) {
445 std::string title_string
= !title
.empty() ? title
:
446 l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE
);
448 GtkWidget
* dialog
= CreateFileOpenHelper(title_string
, default_path
, parent
);
449 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog
), FALSE
);
450 g_signal_connect(dialog
, "response",
451 G_CALLBACK(OnSelectSingleFileDialogResponseThunk
), this);
455 GtkWidget
* SelectFileDialogImplGTK::CreateMultiFileOpenDialog(
456 const std::string
& title
,
457 const base::FilePath
& default_path
,
458 gfx::NativeWindow parent
) {
459 std::string title_string
= !title
.empty() ? title
:
460 l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE
);
462 GtkWidget
* dialog
= CreateFileOpenHelper(title_string
, default_path
, parent
);
463 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog
), TRUE
);
464 g_signal_connect(dialog
, "response",
465 G_CALLBACK(OnSelectMultiFileDialogResponseThunk
), this);
469 GtkWidget
* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string
& title
,
470 const base::FilePath
& default_path
, gfx::NativeWindow parent
) {
471 std::string title_string
= !title
.empty() ? title
:
472 l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE
);
475 gtk_file_chooser_dialog_new(title_string
.c_str(), NULL
,
476 GTK_FILE_CHOOSER_ACTION_SAVE
,
477 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
478 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
480 SetGtkTransientForAura(dialog
, parent
);
482 AddFilters(GTK_FILE_CHOOSER(dialog
));
483 if (!default_path
.empty()) {
484 // Since the file may not already exist, we use
485 // set_current_folder() followed by set_current_name(), as per the
486 // recommendation of the GTK docs.
487 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
488 default_path
.DirName().value().c_str());
489 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog
),
490 default_path
.BaseName().value().c_str());
491 } else if (!last_saved_path_
->empty()) {
492 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
493 last_saved_path_
->value().c_str());
495 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog
), FALSE
);
496 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog
),
498 g_signal_connect(dialog
, "response",
499 G_CALLBACK(OnSelectSingleFileDialogResponseThunk
), this);
503 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget
* dialog
) {
504 std::map
<GtkWidget
*, void*>::iterator iter
= params_map_
.find(dialog
);
505 DCHECK(iter
!= params_map_
.end());
506 void* params
= iter
->second
;
507 params_map_
.erase(iter
);
511 void SelectFileDialogImplGTK::FileDialogDestroyed(GtkWidget
* dialog
) {
512 dialogs_
.erase(dialog
);
514 // Parent may be NULL in a few cases: 1) on shutdown when
515 // AllBrowsersClosed() trigger this handler after all the browser
516 // windows got destroyed, or 2) when the parent tab has been opened by
517 // 'Open Link in New Tab' context menu on a downloadable item and
518 // the tab has no content (see the comment in SelectFile as well).
519 aura::Window
* parent
= reinterpret_cast<aura::Window
*>(
520 g_object_get_data(G_OBJECT(dialog
), kAuraTransientParent
));
523 std::set
<aura::Window
*>::iterator iter
= parents_
.find(parent
);
524 if (iter
!= parents_
.end()) {
525 (*iter
)->RemoveObserver(this);
526 parents_
.erase(iter
);
532 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id
) {
533 bool is_cancel
= response_id
== GTK_RESPONSE_CANCEL
||
534 response_id
== GTK_RESPONSE_DELETE_EVENT
;
538 DCHECK(response_id
== GTK_RESPONSE_ACCEPT
);
542 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget
* dialog
,
545 if (IsCancelResponse(response_id
)) {
546 FileNotSelected(dialog
);
550 gchar
* filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog
));
552 FileNotSelected(dialog
);
556 base::FilePath
path(filename
);
560 FileSelected(dialog
, path
);
564 if (CallDirectoryExistsOnUIThread(path
))
565 FileNotSelected(dialog
);
567 FileSelected(dialog
, path
);
570 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
571 GtkWidget
* dialog
, int response_id
) {
572 SelectSingleFileHelper(dialog
, response_id
, false);
575 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
576 GtkWidget
* dialog
, int response_id
) {
577 SelectSingleFileHelper(dialog
, response_id
, true);
580 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget
* dialog
,
582 if (IsCancelResponse(response_id
)) {
583 FileNotSelected(dialog
);
587 GSList
* filenames
= gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog
));
589 FileNotSelected(dialog
);
593 std::vector
<base::FilePath
> filenames_fp
;
594 for (GSList
* iter
= filenames
; iter
!= NULL
; iter
= g_slist_next(iter
)) {
595 base::FilePath
path(static_cast<char*>(iter
->data
));
597 if (CallDirectoryExistsOnUIThread(path
))
599 filenames_fp
.push_back(path
);
601 g_slist_free(filenames
);
603 if (filenames_fp
.empty()) {
604 FileNotSelected(dialog
);
607 MultiFilesSelected(dialog
, filenames_fp
);
610 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget
* dialog
) {
611 FileDialogDestroyed(dialog
);
614 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget
* chooser
) {
615 gchar
* filename
= gtk_file_chooser_get_preview_filename(
616 GTK_FILE_CHOOSER(chooser
));
619 // This will preserve the image's aspect ratio.
620 GdkPixbuf
* pixbuf
= gdk_pixbuf_new_from_file_at_size(filename
, kPreviewWidth
,
621 kPreviewHeight
, NULL
);
624 gtk_image_set_from_pixbuf(GTK_IMAGE(preview_
), pixbuf
);
625 g_object_unref(pixbuf
);
627 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser
),
628 pixbuf
? TRUE
: FALSE
);
631 } // namespace libgtk2ui