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.
10 // Xlib defines RootWindow
13 #include "base/logging.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/sys_string_conversions.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/threading/thread.h"
20 #include "base/threading/thread_restrictions.h"
21 #include "chrome/browser/ui/libgtk2ui/gtk2_signal.h"
22 #include "chrome/browser/ui/libgtk2ui/gtk2_util.h"
23 #include "chrome/browser/ui/libgtk2ui/select_file_dialog_impl.h"
24 #include "ui/aura/window_observer.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/shell_dialogs/select_file_dialog.h"
27 #include "ui/strings/grit/ui_strings.h"
28 #include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
32 // Makes sure that .jpg also shows .JPG.
33 gboolean
FileFilterCaseInsensitive(const GtkFileFilterInfo
* file_info
,
34 std::string
* file_extension
) {
35 return base::EndsWith(file_info
->filename
, *file_extension
,
36 base::CompareCase::INSENSITIVE_ASCII
);
39 // Deletes |data| when gtk_file_filter_add_custom() is done with it.
40 void OnFileFilterDataDestroyed(std::string
* file_extension
) {
41 delete file_extension
;
48 // Implementation of SelectFileDialog that shows a Gtk common dialog for
49 // choosing a file or folder. This acts as a modal dialog.
50 class SelectFileDialogImplGTK
: public SelectFileDialogImpl
,
51 public aura::WindowObserver
{
53 explicit SelectFileDialogImplGTK(Listener
* listener
,
54 ui::SelectFilePolicy
* policy
);
57 ~SelectFileDialogImplGTK() override
;
59 // BaseShellDialog implementation:
60 bool IsRunning(gfx::NativeWindow parent_window
) const override
;
62 // SelectFileDialog implementation.
63 // |params| is user data we pass back via the Listener interface.
64 void SelectFileImpl(Type type
,
65 const base::string16
& title
,
66 const base::FilePath
& default_path
,
67 const FileTypeInfo
* file_types
,
69 const base::FilePath::StringType
& default_extension
,
70 gfx::NativeWindow owning_window
,
71 void* params
) override
;
74 bool HasMultipleFileTypeChoicesImpl() override
;
76 // Overridden from aura::WindowObserver:
77 void OnWindowDestroying(aura::Window
* window
) override
;
79 // Add the filters from |file_types_| to |chooser|.
80 void AddFilters(GtkFileChooser
* chooser
);
82 // Notifies the listener that a single file was chosen.
83 void FileSelected(GtkWidget
* dialog
, const base::FilePath
& path
);
85 // Notifies the listener that multiple files were chosen.
86 void MultiFilesSelected(GtkWidget
* dialog
,
87 const std::vector
<base::FilePath
>& files
);
89 // Notifies the listener that no file was chosen (the action was canceled).
90 // Dialog is passed so we can find that |params| pointer that was passed to
91 // us when we were told to show the dialog.
92 void FileNotSelected(GtkWidget
* dialog
);
94 GtkWidget
* CreateSelectFolderDialog(
96 const std::string
& title
,
97 const base::FilePath
& default_path
,
98 gfx::NativeWindow parent
);
100 GtkWidget
* CreateFileOpenDialog(const std::string
& title
,
101 const base::FilePath
& default_path
, gfx::NativeWindow parent
);
103 GtkWidget
* CreateMultiFileOpenDialog(const std::string
& title
,
104 const base::FilePath
& default_path
, gfx::NativeWindow parent
);
106 GtkWidget
* CreateSaveAsDialog(const std::string
& title
,
107 const base::FilePath
& default_path
, gfx::NativeWindow parent
);
109 // Removes and returns the |params| associated with |dialog| from
111 void* PopParamsForDialog(GtkWidget
* dialog
);
113 // Take care of internal data structures when a file dialog is destroyed.
114 void FileDialogDestroyed(GtkWidget
* dialog
);
116 // Check whether response_id corresponds to the user cancelling/closing the
117 // dialog. Used as a helper for the below callbacks.
118 bool IsCancelResponse(gint response_id
);
120 // Common function for OnSelectSingleFileDialogResponse and
121 // OnSelectSingleFolderDialogResponse.
122 void SelectSingleFileHelper(GtkWidget
* dialog
,
126 // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
127 GtkWidget
* CreateFileOpenHelper(const std::string
& title
,
128 const base::FilePath
& default_path
,
129 gfx::NativeWindow parent
);
131 // Callback for when the user responds to a Save As or Open File dialog.
132 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK
, void,
133 OnSelectSingleFileDialogResponse
, int);
135 // Callback for when the user responds to a Select Folder dialog.
136 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK
, void,
137 OnSelectSingleFolderDialogResponse
, int);
139 // Callback for when the user responds to a Open Multiple Files dialog.
140 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK
, void,
141 OnSelectMultiFileDialogResponse
, int);
143 // Callback for when the file chooser gets destroyed.
144 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK
, void, OnFileChooserDestroy
);
146 // Callback for when we update the preview for the selection.
147 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK
, void, OnUpdatePreview
);
149 // A map from dialog windows to the |params| user data associated with them.
150 std::map
<GtkWidget
*, void*> params_map_
;
152 // The GtkImage widget for showing previews of selected images.
156 std::set
<GtkWidget
*> dialogs_
;
158 // The set of all parent windows for which we are currently running dialogs.
159 std::set
<aura::Window
*> parents_
;
161 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplGTK
);
164 // The size of the preview we display for selected image files. We set height
165 // larger than width because generally there is more free space vertically
166 // than horiztonally (setting the preview image will alway expand the width of
167 // the dialog, but usually not the height). The image's aspect ratio will always
169 static const int kPreviewWidth
= 256;
170 static const int kPreviewHeight
= 512;
172 SelectFileDialogImpl
* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
173 Listener
* listener
, ui::SelectFilePolicy
* policy
) {
174 return new SelectFileDialogImplGTK(listener
, policy
);
177 SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener
* listener
,
178 ui::SelectFilePolicy
* policy
)
179 : SelectFileDialogImpl(listener
, policy
),
183 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() {
184 for (std::set
<aura::Window
*>::iterator iter
= parents_
.begin();
185 iter
!= parents_
.end(); ++iter
) {
186 (*iter
)->RemoveObserver(this);
188 while (dialogs_
.begin() != dialogs_
.end()) {
189 gtk_widget_destroy(*(dialogs_
.begin()));
193 bool SelectFileDialogImplGTK::IsRunning(gfx::NativeWindow parent_window
) const {
194 return parents_
.find(parent_window
) != parents_
.end();
197 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
198 return file_types_
.extensions
.size() > 1;
201 void SelectFileDialogImplGTK::OnWindowDestroying(aura::Window
* window
) {
202 // Remove the |parent| property associated with the |dialog|.
203 for (std::set
<GtkWidget
*>::iterator it
= dialogs_
.begin();
204 it
!= dialogs_
.end(); ++it
) {
205 aura::Window
* parent
= GetAuraTransientParent(*it
);
206 if (parent
== window
)
207 ClearAuraTransientParent(*it
);
210 std::set
<aura::Window
*>::iterator iter
= parents_
.find(window
);
211 if (iter
!= parents_
.end()) {
212 (*iter
)->RemoveObserver(this);
213 parents_
.erase(iter
);
217 // We ignore |default_extension|.
218 void SelectFileDialogImplGTK::SelectFileImpl(
220 const base::string16
& title
,
221 const base::FilePath
& default_path
,
222 const FileTypeInfo
* file_types
,
224 const base::FilePath::StringType
& default_extension
,
225 gfx::NativeWindow owning_window
,
228 // |owning_window| can be null when user right-clicks on a downloadable item
229 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
230 // before downloading.' preference is turned on. (http://crbug.com/29213)
232 owning_window
->AddObserver(this);
233 parents_
.insert(owning_window
);
236 std::string title_string
= base::UTF16ToUTF8(title
);
238 file_type_index_
= file_type_index
;
240 file_types_
= *file_types
;
242 GtkWidget
* dialog
= NULL
;
245 case SELECT_UPLOAD_FOLDER
:
246 dialog
= CreateSelectFolderDialog(type
, title_string
, default_path
,
249 case SELECT_OPEN_FILE
:
250 dialog
= CreateFileOpenDialog(title_string
, default_path
, owning_window
);
252 case SELECT_OPEN_MULTI_FILE
:
253 dialog
= CreateMultiFileOpenDialog(title_string
, default_path
,
256 case SELECT_SAVEAS_FILE
:
257 dialog
= CreateSaveAsDialog(title_string
, default_path
, owning_window
);
263 g_signal_connect(dialog
, "delete-event",
264 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
265 dialogs_
.insert(dialog
);
267 preview_
= gtk_image_new();
268 g_signal_connect(dialog
, "destroy",
269 G_CALLBACK(OnFileChooserDestroyThunk
), this);
270 g_signal_connect(dialog
, "update-preview",
271 G_CALLBACK(OnUpdatePreviewThunk
), this);
272 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog
), preview_
);
274 params_map_
[dialog
] = params
;
276 // TODO(erg): Figure out how to fake GTK window-to-parent modality without
277 // having the parent be a real GtkWindow.
278 gtk_window_set_modal(GTK_WINDOW(dialog
), TRUE
);
280 gtk_widget_show_all(dialog
);
282 // We need to call gtk_window_present after making the widgets visible to make
283 // sure window gets correctly raised and gets focus.
284 int time
= views::X11DesktopHandler::get()->wm_user_time_ms();
285 gtk_window_present_with_time(GTK_WINDOW(dialog
), time
);
288 void SelectFileDialogImplGTK::AddFilters(GtkFileChooser
* chooser
) {
289 for (size_t i
= 0; i
< file_types_
.extensions
.size(); ++i
) {
290 GtkFileFilter
* filter
= NULL
;
291 std::set
<std::string
> fallback_labels
;
293 for (size_t j
= 0; j
< file_types_
.extensions
[i
].size(); ++j
) {
294 const std::string
& current_extension
= file_types_
.extensions
[i
][j
];
295 if (!current_extension
.empty()) {
297 filter
= gtk_file_filter_new();
298 scoped_ptr
<std::string
> file_extension(
299 new std::string("." + current_extension
));
300 fallback_labels
.insert(std::string("*").append(*file_extension
));
301 gtk_file_filter_add_custom(
303 GTK_FILE_FILTER_FILENAME
,
304 reinterpret_cast<GtkFileFilterFunc
>(FileFilterCaseInsensitive
),
305 file_extension
.release(),
306 reinterpret_cast<GDestroyNotify
>(OnFileFilterDataDestroyed
));
309 // We didn't find any non-empty extensions to filter on.
313 // The description vector may be blank, in which case we are supposed to
314 // use some sort of default description based on the filter.
315 if (i
< file_types_
.extension_description_overrides
.size()) {
316 gtk_file_filter_set_name(filter
, base::UTF16ToUTF8(
317 file_types_
.extension_description_overrides
[i
]).c_str());
319 // There is no system default filter description so we use
320 // the extensions themselves if the description is blank.
321 std::vector
<std::string
> fallback_labels_vector(fallback_labels
.begin(),
322 fallback_labels
.end());
323 std::string fallback_label
=
324 base::JoinString(fallback_labels_vector
, ",");
325 gtk_file_filter_set_name(filter
, fallback_label
.c_str());
328 gtk_file_chooser_add_filter(chooser
, filter
);
329 if (i
== file_type_index_
- 1)
330 gtk_file_chooser_set_filter(chooser
, filter
);
333 // Add the *.* filter, but only if we have added other filters (otherwise it
335 if (file_types_
.include_all_files
&& !file_types_
.extensions
.empty()) {
336 GtkFileFilter
* filter
= gtk_file_filter_new();
337 gtk_file_filter_add_pattern(filter
, "*");
338 gtk_file_filter_set_name(filter
,
339 l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES
).c_str());
340 gtk_file_chooser_add_filter(chooser
, filter
);
344 void SelectFileDialogImplGTK::FileSelected(GtkWidget
* dialog
,
345 const base::FilePath
& path
) {
346 if (type_
== SELECT_SAVEAS_FILE
) {
347 *last_saved_path_
= path
.DirName();
348 } else if (type_
== SELECT_OPEN_FILE
|| type_
== SELECT_FOLDER
||
349 type_
== SELECT_UPLOAD_FOLDER
) {
350 *last_opened_path_
= path
.DirName();
356 GtkFileFilter
* selected_filter
=
357 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog
));
358 GSList
* filters
= gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog
));
359 int idx
= g_slist_index(filters
, selected_filter
);
360 g_slist_free(filters
);
361 listener_
->FileSelected(path
, idx
+ 1, PopParamsForDialog(dialog
));
363 gtk_widget_destroy(dialog
);
366 void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget
* dialog
,
367 const std::vector
<base::FilePath
>& files
) {
368 *last_opened_path_
= files
[0].DirName();
371 listener_
->MultiFilesSelected(files
, PopParamsForDialog(dialog
));
372 gtk_widget_destroy(dialog
);
375 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget
* dialog
) {
376 void* params
= PopParamsForDialog(dialog
);
378 listener_
->FileSelectionCanceled(params
);
379 gtk_widget_destroy(dialog
);
382 GtkWidget
* SelectFileDialogImplGTK::CreateFileOpenHelper(
383 const std::string
& title
,
384 const base::FilePath
& default_path
,
385 gfx::NativeWindow parent
) {
387 gtk_file_chooser_dialog_new(title
.c_str(), NULL
,
388 GTK_FILE_CHOOSER_ACTION_OPEN
,
389 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
390 GTK_STOCK_OPEN
, GTK_RESPONSE_ACCEPT
,
392 SetGtkTransientForAura(dialog
, parent
);
393 AddFilters(GTK_FILE_CHOOSER(dialog
));
395 if (!default_path
.empty()) {
396 if (CallDirectoryExistsOnUIThread(default_path
)) {
397 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
398 default_path
.value().c_str());
400 // If the file doesn't exist, this will just switch to the correct
401 // directory. That's good enough.
402 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog
),
403 default_path
.value().c_str());
405 } else if (!last_opened_path_
->empty()) {
406 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
407 last_opened_path_
->value().c_str());
412 GtkWidget
* SelectFileDialogImplGTK::CreateSelectFolderDialog(
414 const std::string
& title
,
415 const base::FilePath
& default_path
,
416 gfx::NativeWindow parent
) {
417 std::string title_string
= title
;
418 if (title_string
.empty()) {
419 title_string
= (type
== SELECT_UPLOAD_FOLDER
) ?
420 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE
) :
421 l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE
);
423 std::string accept_button_label
= (type
== SELECT_UPLOAD_FOLDER
) ?
424 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON
) :
428 gtk_file_chooser_dialog_new(title_string
.c_str(), NULL
,
429 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
,
430 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
431 accept_button_label
.c_str(),
434 SetGtkTransientForAura(dialog
, parent
);
436 if (!default_path
.empty()) {
437 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog
),
438 default_path
.value().c_str());
439 } else if (!last_opened_path_
->empty()) {
440 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
441 last_opened_path_
->value().c_str());
443 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog
), FALSE
);
444 g_signal_connect(dialog
, "response",
445 G_CALLBACK(OnSelectSingleFolderDialogResponseThunk
), this);
449 GtkWidget
* SelectFileDialogImplGTK::CreateFileOpenDialog(
450 const std::string
& title
,
451 const base::FilePath
& default_path
,
452 gfx::NativeWindow parent
) {
453 std::string title_string
= !title
.empty() ? title
:
454 l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE
);
456 GtkWidget
* dialog
= CreateFileOpenHelper(title_string
, default_path
, parent
);
457 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog
), FALSE
);
458 g_signal_connect(dialog
, "response",
459 G_CALLBACK(OnSelectSingleFileDialogResponseThunk
), this);
463 GtkWidget
* SelectFileDialogImplGTK::CreateMultiFileOpenDialog(
464 const std::string
& title
,
465 const base::FilePath
& default_path
,
466 gfx::NativeWindow parent
) {
467 std::string title_string
= !title
.empty() ? title
:
468 l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE
);
470 GtkWidget
* dialog
= CreateFileOpenHelper(title_string
, default_path
, parent
);
471 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog
), TRUE
);
472 g_signal_connect(dialog
, "response",
473 G_CALLBACK(OnSelectMultiFileDialogResponseThunk
), this);
477 GtkWidget
* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string
& title
,
478 const base::FilePath
& default_path
, gfx::NativeWindow parent
) {
479 std::string title_string
= !title
.empty() ? title
:
480 l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE
);
483 gtk_file_chooser_dialog_new(title_string
.c_str(), NULL
,
484 GTK_FILE_CHOOSER_ACTION_SAVE
,
485 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
486 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
488 SetGtkTransientForAura(dialog
, parent
);
490 AddFilters(GTK_FILE_CHOOSER(dialog
));
491 if (!default_path
.empty()) {
492 // Since the file may not already exist, we use
493 // set_current_folder() followed by set_current_name(), as per the
494 // recommendation of the GTK docs.
495 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
496 default_path
.DirName().value().c_str());
497 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog
),
498 default_path
.BaseName().value().c_str());
499 } else if (!last_saved_path_
->empty()) {
500 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
501 last_saved_path_
->value().c_str());
503 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog
), FALSE
);
504 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog
),
506 g_signal_connect(dialog
, "response",
507 G_CALLBACK(OnSelectSingleFileDialogResponseThunk
), this);
511 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget
* dialog
) {
512 std::map
<GtkWidget
*, void*>::iterator iter
= params_map_
.find(dialog
);
513 DCHECK(iter
!= params_map_
.end());
514 void* params
= iter
->second
;
515 params_map_
.erase(iter
);
519 void SelectFileDialogImplGTK::FileDialogDestroyed(GtkWidget
* dialog
) {
520 dialogs_
.erase(dialog
);
522 // Parent may be NULL in a few cases: 1) on shutdown when
523 // AllBrowsersClosed() trigger this handler after all the browser
524 // windows got destroyed, or 2) when the parent tab has been opened by
525 // 'Open Link in New Tab' context menu on a downloadable item and
526 // the tab has no content (see the comment in SelectFile as well).
527 aura::Window
* parent
= GetAuraTransientParent(dialog
);
530 std::set
<aura::Window
*>::iterator iter
= parents_
.find(parent
);
531 if (iter
!= parents_
.end()) {
532 (*iter
)->RemoveObserver(this);
533 parents_
.erase(iter
);
539 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id
) {
540 bool is_cancel
= response_id
== GTK_RESPONSE_CANCEL
||
541 response_id
== GTK_RESPONSE_DELETE_EVENT
;
545 DCHECK(response_id
== GTK_RESPONSE_ACCEPT
);
549 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget
* dialog
,
552 if (IsCancelResponse(response_id
)) {
553 FileNotSelected(dialog
);
557 gchar
* filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog
));
559 FileNotSelected(dialog
);
563 base::FilePath
path(filename
);
567 FileSelected(dialog
, path
);
571 if (CallDirectoryExistsOnUIThread(path
))
572 FileNotSelected(dialog
);
574 FileSelected(dialog
, path
);
577 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
578 GtkWidget
* dialog
, int response_id
) {
579 SelectSingleFileHelper(dialog
, response_id
, false);
582 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
583 GtkWidget
* dialog
, int response_id
) {
584 SelectSingleFileHelper(dialog
, response_id
, true);
587 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget
* dialog
,
589 if (IsCancelResponse(response_id
)) {
590 FileNotSelected(dialog
);
594 GSList
* filenames
= gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog
));
596 FileNotSelected(dialog
);
600 std::vector
<base::FilePath
> filenames_fp
;
601 for (GSList
* iter
= filenames
; iter
!= NULL
; iter
= g_slist_next(iter
)) {
602 base::FilePath
path(static_cast<char*>(iter
->data
));
604 if (CallDirectoryExistsOnUIThread(path
))
606 filenames_fp
.push_back(path
);
608 g_slist_free(filenames
);
610 if (filenames_fp
.empty()) {
611 FileNotSelected(dialog
);
614 MultiFilesSelected(dialog
, filenames_fp
);
617 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget
* dialog
) {
618 FileDialogDestroyed(dialog
);
621 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget
* chooser
) {
622 gchar
* filename
= gtk_file_chooser_get_preview_filename(
623 GTK_FILE_CHOOSER(chooser
));
626 // This will preserve the image's aspect ratio.
627 GdkPixbuf
* pixbuf
= gdk_pixbuf_new_from_file_at_size(filename
, kPreviewWidth
,
628 kPreviewHeight
, NULL
);
631 gtk_image_set_from_pixbuf(GTK_IMAGE(preview_
), pixbuf
);
632 g_object_unref(pixbuf
);
634 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser
),
635 pixbuf
? TRUE
: FALSE
);
638 } // namespace libgtk2ui