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 EndsWith(file_info
->filename
, *file_extension
, false);
38 // Deletes |data| when gtk_file_filter_add_custom() is done with it.
39 void OnFileFilterDataDestroyed(std::string
* file_extension
) {
40 delete file_extension
;
47 // Implementation of SelectFileDialog that shows a Gtk common dialog for
48 // choosing a file or folder. This acts as a modal dialog.
49 class SelectFileDialogImplGTK
: public SelectFileDialogImpl
,
50 public aura::WindowObserver
{
52 explicit SelectFileDialogImplGTK(Listener
* listener
,
53 ui::SelectFilePolicy
* policy
);
56 ~SelectFileDialogImplGTK() override
;
58 // BaseShellDialog implementation:
59 bool IsRunning(gfx::NativeWindow parent_window
) const override
;
61 // SelectFileDialog implementation.
62 // |params| is user data we pass back via the Listener interface.
63 void SelectFileImpl(Type type
,
64 const base::string16
& title
,
65 const base::FilePath
& default_path
,
66 const FileTypeInfo
* file_types
,
68 const base::FilePath::StringType
& default_extension
,
69 gfx::NativeWindow owning_window
,
70 void* params
) override
;
73 bool HasMultipleFileTypeChoicesImpl() override
;
75 // Overridden from aura::WindowObserver:
76 void OnWindowDestroying(aura::Window
* window
) override
;
78 // Add the filters from |file_types_| to |chooser|.
79 void AddFilters(GtkFileChooser
* chooser
);
81 // Notifies the listener that a single file was chosen.
82 void FileSelected(GtkWidget
* dialog
, const base::FilePath
& path
);
84 // Notifies the listener that multiple files were chosen.
85 void MultiFilesSelected(GtkWidget
* dialog
,
86 const std::vector
<base::FilePath
>& files
);
88 // Notifies the listener that no file was chosen (the action was canceled).
89 // Dialog is passed so we can find that |params| pointer that was passed to
90 // us when we were told to show the dialog.
91 void FileNotSelected(GtkWidget
* dialog
);
93 GtkWidget
* CreateSelectFolderDialog(
95 const std::string
& title
,
96 const base::FilePath
& default_path
,
97 gfx::NativeWindow parent
);
99 GtkWidget
* CreateFileOpenDialog(const std::string
& title
,
100 const base::FilePath
& default_path
, gfx::NativeWindow parent
);
102 GtkWidget
* CreateMultiFileOpenDialog(const std::string
& title
,
103 const base::FilePath
& default_path
, gfx::NativeWindow parent
);
105 GtkWidget
* CreateSaveAsDialog(const std::string
& title
,
106 const base::FilePath
& default_path
, gfx::NativeWindow parent
);
108 // Removes and returns the |params| associated with |dialog| from
110 void* PopParamsForDialog(GtkWidget
* dialog
);
112 // Take care of internal data structures when a file dialog is destroyed.
113 void FileDialogDestroyed(GtkWidget
* dialog
);
115 // Check whether response_id corresponds to the user cancelling/closing the
116 // dialog. Used as a helper for the below callbacks.
117 bool IsCancelResponse(gint response_id
);
119 // Common function for OnSelectSingleFileDialogResponse and
120 // OnSelectSingleFolderDialogResponse.
121 void SelectSingleFileHelper(GtkWidget
* dialog
,
125 // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
126 GtkWidget
* CreateFileOpenHelper(const std::string
& title
,
127 const base::FilePath
& default_path
,
128 gfx::NativeWindow parent
);
130 // Callback for when the user responds to a Save As or Open File dialog.
131 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK
, void,
132 OnSelectSingleFileDialogResponse
, int);
134 // Callback for when the user responds to a Select Folder dialog.
135 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK
, void,
136 OnSelectSingleFolderDialogResponse
, int);
138 // Callback for when the user responds to a Open Multiple Files dialog.
139 CHROMEGTK_CALLBACK_1(SelectFileDialogImplGTK
, void,
140 OnSelectMultiFileDialogResponse
, int);
142 // Callback for when the file chooser gets destroyed.
143 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK
, void, OnFileChooserDestroy
);
145 // Callback for when we update the preview for the selection.
146 CHROMEGTK_CALLBACK_0(SelectFileDialogImplGTK
, void, OnUpdatePreview
);
148 // A map from dialog windows to the |params| user data associated with them.
149 std::map
<GtkWidget
*, void*> params_map_
;
151 // The GtkImage widget for showing previews of selected images.
155 std::set
<GtkWidget
*> dialogs_
;
157 // The set of all parent windows for which we are currently running dialogs.
158 std::set
<aura::Window
*> parents_
;
160 DISALLOW_COPY_AND_ASSIGN(SelectFileDialogImplGTK
);
163 // The size of the preview we display for selected image files. We set height
164 // larger than width because generally there is more free space vertically
165 // than horiztonally (setting the preview image will alway expand the width of
166 // the dialog, but usually not the height). The image's aspect ratio will always
168 static const int kPreviewWidth
= 256;
169 static const int kPreviewHeight
= 512;
171 SelectFileDialogImpl
* SelectFileDialogImpl::NewSelectFileDialogImplGTK(
172 Listener
* listener
, ui::SelectFilePolicy
* policy
) {
173 return new SelectFileDialogImplGTK(listener
, policy
);
176 SelectFileDialogImplGTK::SelectFileDialogImplGTK(Listener
* listener
,
177 ui::SelectFilePolicy
* policy
)
178 : SelectFileDialogImpl(listener
, policy
),
182 SelectFileDialogImplGTK::~SelectFileDialogImplGTK() {
183 for (std::set
<aura::Window
*>::iterator iter
= parents_
.begin();
184 iter
!= parents_
.end(); ++iter
) {
185 (*iter
)->RemoveObserver(this);
187 while (dialogs_
.begin() != dialogs_
.end()) {
188 gtk_widget_destroy(*(dialogs_
.begin()));
192 bool SelectFileDialogImplGTK::IsRunning(gfx::NativeWindow parent_window
) const {
193 return parents_
.find(parent_window
) != parents_
.end();
196 bool SelectFileDialogImplGTK::HasMultipleFileTypeChoicesImpl() {
197 return file_types_
.extensions
.size() > 1;
200 void SelectFileDialogImplGTK::OnWindowDestroying(aura::Window
* window
) {
201 // Remove the |parent| property associated with the |dialog|.
202 for (std::set
<GtkWidget
*>::iterator it
= dialogs_
.begin();
203 it
!= dialogs_
.end(); ++it
) {
204 aura::Window
* parent
= GetAuraTransientParent(*it
);
205 if (parent
== window
)
206 ClearAuraTransientParent(*it
);
209 std::set
<aura::Window
*>::iterator iter
= parents_
.find(window
);
210 if (iter
!= parents_
.end()) {
211 (*iter
)->RemoveObserver(this);
212 parents_
.erase(iter
);
216 // We ignore |default_extension|.
217 void SelectFileDialogImplGTK::SelectFileImpl(
219 const base::string16
& title
,
220 const base::FilePath
& default_path
,
221 const FileTypeInfo
* file_types
,
223 const base::FilePath::StringType
& default_extension
,
224 gfx::NativeWindow owning_window
,
227 // |owning_window| can be null when user right-clicks on a downloadable item
228 // and chooses 'Open Link in New Tab' when 'Ask where to save each file
229 // before downloading.' preference is turned on. (http://crbug.com/29213)
231 owning_window
->AddObserver(this);
232 parents_
.insert(owning_window
);
235 std::string title_string
= base::UTF16ToUTF8(title
);
237 file_type_index_
= file_type_index
;
239 file_types_
= *file_types
;
241 GtkWidget
* dialog
= NULL
;
244 case SELECT_UPLOAD_FOLDER
:
245 dialog
= CreateSelectFolderDialog(type
, title_string
, default_path
,
248 case SELECT_OPEN_FILE
:
249 dialog
= CreateFileOpenDialog(title_string
, default_path
, owning_window
);
251 case SELECT_OPEN_MULTI_FILE
:
252 dialog
= CreateMultiFileOpenDialog(title_string
, default_path
,
255 case SELECT_SAVEAS_FILE
:
256 dialog
= CreateSaveAsDialog(title_string
, default_path
, owning_window
);
262 g_signal_connect(dialog
, "delete-event",
263 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
264 dialogs_
.insert(dialog
);
266 preview_
= gtk_image_new();
267 g_signal_connect(dialog
, "destroy",
268 G_CALLBACK(OnFileChooserDestroyThunk
), this);
269 g_signal_connect(dialog
, "update-preview",
270 G_CALLBACK(OnUpdatePreviewThunk
), this);
271 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog
), preview_
);
273 params_map_
[dialog
] = params
;
275 // TODO(erg): Figure out how to fake GTK window-to-parent modality without
276 // having the parent be a real GtkWindow.
277 gtk_window_set_modal(GTK_WINDOW(dialog
), TRUE
);
279 gtk_widget_show_all(dialog
);
281 // We need to call gtk_window_present after making the widgets visible to make
282 // sure window gets correctly raised and gets focus.
283 int time
= views::X11DesktopHandler::get()->wm_user_time_ms();
284 gtk_window_present_with_time(GTK_WINDOW(dialog
), time
);
287 void SelectFileDialogImplGTK::AddFilters(GtkFileChooser
* chooser
) {
288 for (size_t i
= 0; i
< file_types_
.extensions
.size(); ++i
) {
289 GtkFileFilter
* filter
= NULL
;
290 std::set
<std::string
> fallback_labels
;
292 for (size_t j
= 0; j
< file_types_
.extensions
[i
].size(); ++j
) {
293 const std::string
& current_extension
= file_types_
.extensions
[i
][j
];
294 if (!current_extension
.empty()) {
296 filter
= gtk_file_filter_new();
297 scoped_ptr
<std::string
> file_extension(
298 new std::string("." + current_extension
));
299 fallback_labels
.insert(std::string("*").append(*file_extension
));
300 gtk_file_filter_add_custom(
302 GTK_FILE_FILTER_FILENAME
,
303 reinterpret_cast<GtkFileFilterFunc
>(FileFilterCaseInsensitive
),
304 file_extension
.release(),
305 reinterpret_cast<GDestroyNotify
>(OnFileFilterDataDestroyed
));
308 // We didn't find any non-empty extensions to filter on.
312 // The description vector may be blank, in which case we are supposed to
313 // use some sort of default description based on the filter.
314 if (i
< file_types_
.extension_description_overrides
.size()) {
315 gtk_file_filter_set_name(filter
, base::UTF16ToUTF8(
316 file_types_
.extension_description_overrides
[i
]).c_str());
318 // There is no system default filter description so we use
319 // the extensions themselves if the description is blank.
320 std::vector
<std::string
> fallback_labels_vector(fallback_labels
.begin(),
321 fallback_labels
.end());
322 std::string fallback_label
= JoinString(fallback_labels_vector
, ',');
323 gtk_file_filter_set_name(filter
, fallback_label
.c_str());
326 gtk_file_chooser_add_filter(chooser
, filter
);
327 if (i
== file_type_index_
- 1)
328 gtk_file_chooser_set_filter(chooser
, filter
);
331 // Add the *.* filter, but only if we have added other filters (otherwise it
333 if (file_types_
.include_all_files
&& !file_types_
.extensions
.empty()) {
334 GtkFileFilter
* filter
= gtk_file_filter_new();
335 gtk_file_filter_add_pattern(filter
, "*");
336 gtk_file_filter_set_name(filter
,
337 l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES
).c_str());
338 gtk_file_chooser_add_filter(chooser
, filter
);
342 void SelectFileDialogImplGTK::FileSelected(GtkWidget
* dialog
,
343 const base::FilePath
& path
) {
344 if (type_
== SELECT_SAVEAS_FILE
) {
345 *last_saved_path_
= path
.DirName();
346 } else if (type_
== SELECT_OPEN_FILE
|| type_
== SELECT_FOLDER
||
347 type_
== SELECT_UPLOAD_FOLDER
) {
348 *last_opened_path_
= path
.DirName();
354 GtkFileFilter
* selected_filter
=
355 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog
));
356 GSList
* filters
= gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog
));
357 int idx
= g_slist_index(filters
, selected_filter
);
358 g_slist_free(filters
);
359 listener_
->FileSelected(path
, idx
+ 1, PopParamsForDialog(dialog
));
361 gtk_widget_destroy(dialog
);
364 void SelectFileDialogImplGTK::MultiFilesSelected(GtkWidget
* dialog
,
365 const std::vector
<base::FilePath
>& files
) {
366 *last_opened_path_
= files
[0].DirName();
369 listener_
->MultiFilesSelected(files
, PopParamsForDialog(dialog
));
370 gtk_widget_destroy(dialog
);
373 void SelectFileDialogImplGTK::FileNotSelected(GtkWidget
* dialog
) {
374 void* params
= PopParamsForDialog(dialog
);
376 listener_
->FileSelectionCanceled(params
);
377 gtk_widget_destroy(dialog
);
380 GtkWidget
* SelectFileDialogImplGTK::CreateFileOpenHelper(
381 const std::string
& title
,
382 const base::FilePath
& default_path
,
383 gfx::NativeWindow parent
) {
385 gtk_file_chooser_dialog_new(title
.c_str(), NULL
,
386 GTK_FILE_CHOOSER_ACTION_OPEN
,
387 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
388 GTK_STOCK_OPEN
, GTK_RESPONSE_ACCEPT
,
390 SetGtkTransientForAura(dialog
, parent
);
391 AddFilters(GTK_FILE_CHOOSER(dialog
));
393 if (!default_path
.empty()) {
394 if (CallDirectoryExistsOnUIThread(default_path
)) {
395 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
396 default_path
.value().c_str());
398 // If the file doesn't exist, this will just switch to the correct
399 // directory. That's good enough.
400 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog
),
401 default_path
.value().c_str());
403 } else if (!last_opened_path_
->empty()) {
404 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
405 last_opened_path_
->value().c_str());
410 GtkWidget
* SelectFileDialogImplGTK::CreateSelectFolderDialog(
412 const std::string
& title
,
413 const base::FilePath
& default_path
,
414 gfx::NativeWindow parent
) {
415 std::string title_string
= title
;
416 if (title_string
.empty()) {
417 title_string
= (type
== SELECT_UPLOAD_FOLDER
) ?
418 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE
) :
419 l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE
);
421 std::string accept_button_label
= (type
== SELECT_UPLOAD_FOLDER
) ?
422 l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON
) :
426 gtk_file_chooser_dialog_new(title_string
.c_str(), NULL
,
427 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
,
428 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
429 accept_button_label
.c_str(),
432 SetGtkTransientForAura(dialog
, parent
);
434 if (!default_path
.empty()) {
435 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog
),
436 default_path
.value().c_str());
437 } else if (!last_opened_path_
->empty()) {
438 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
439 last_opened_path_
->value().c_str());
441 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog
), FALSE
);
442 g_signal_connect(dialog
, "response",
443 G_CALLBACK(OnSelectSingleFolderDialogResponseThunk
), this);
447 GtkWidget
* SelectFileDialogImplGTK::CreateFileOpenDialog(
448 const std::string
& title
,
449 const base::FilePath
& default_path
,
450 gfx::NativeWindow parent
) {
451 std::string title_string
= !title
.empty() ? title
:
452 l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE
);
454 GtkWidget
* dialog
= CreateFileOpenHelper(title_string
, default_path
, parent
);
455 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog
), FALSE
);
456 g_signal_connect(dialog
, "response",
457 G_CALLBACK(OnSelectSingleFileDialogResponseThunk
), this);
461 GtkWidget
* SelectFileDialogImplGTK::CreateMultiFileOpenDialog(
462 const std::string
& title
,
463 const base::FilePath
& default_path
,
464 gfx::NativeWindow parent
) {
465 std::string title_string
= !title
.empty() ? title
:
466 l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE
);
468 GtkWidget
* dialog
= CreateFileOpenHelper(title_string
, default_path
, parent
);
469 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog
), TRUE
);
470 g_signal_connect(dialog
, "response",
471 G_CALLBACK(OnSelectMultiFileDialogResponseThunk
), this);
475 GtkWidget
* SelectFileDialogImplGTK::CreateSaveAsDialog(const std::string
& title
,
476 const base::FilePath
& default_path
, gfx::NativeWindow parent
) {
477 std::string title_string
= !title
.empty() ? title
:
478 l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE
);
481 gtk_file_chooser_dialog_new(title_string
.c_str(), NULL
,
482 GTK_FILE_CHOOSER_ACTION_SAVE
,
483 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
484 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
486 SetGtkTransientForAura(dialog
, parent
);
488 AddFilters(GTK_FILE_CHOOSER(dialog
));
489 if (!default_path
.empty()) {
490 // Since the file may not already exist, we use
491 // set_current_folder() followed by set_current_name(), as per the
492 // recommendation of the GTK docs.
493 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
494 default_path
.DirName().value().c_str());
495 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog
),
496 default_path
.BaseName().value().c_str());
497 } else if (!last_saved_path_
->empty()) {
498 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog
),
499 last_saved_path_
->value().c_str());
501 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog
), FALSE
);
502 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog
),
504 g_signal_connect(dialog
, "response",
505 G_CALLBACK(OnSelectSingleFileDialogResponseThunk
), this);
509 void* SelectFileDialogImplGTK::PopParamsForDialog(GtkWidget
* dialog
) {
510 std::map
<GtkWidget
*, void*>::iterator iter
= params_map_
.find(dialog
);
511 DCHECK(iter
!= params_map_
.end());
512 void* params
= iter
->second
;
513 params_map_
.erase(iter
);
517 void SelectFileDialogImplGTK::FileDialogDestroyed(GtkWidget
* dialog
) {
518 dialogs_
.erase(dialog
);
520 // Parent may be NULL in a few cases: 1) on shutdown when
521 // AllBrowsersClosed() trigger this handler after all the browser
522 // windows got destroyed, or 2) when the parent tab has been opened by
523 // 'Open Link in New Tab' context menu on a downloadable item and
524 // the tab has no content (see the comment in SelectFile as well).
525 aura::Window
* parent
= GetAuraTransientParent(dialog
);
528 std::set
<aura::Window
*>::iterator iter
= parents_
.find(parent
);
529 if (iter
!= parents_
.end()) {
530 (*iter
)->RemoveObserver(this);
531 parents_
.erase(iter
);
537 bool SelectFileDialogImplGTK::IsCancelResponse(gint response_id
) {
538 bool is_cancel
= response_id
== GTK_RESPONSE_CANCEL
||
539 response_id
== GTK_RESPONSE_DELETE_EVENT
;
543 DCHECK(response_id
== GTK_RESPONSE_ACCEPT
);
547 void SelectFileDialogImplGTK::SelectSingleFileHelper(GtkWidget
* dialog
,
550 if (IsCancelResponse(response_id
)) {
551 FileNotSelected(dialog
);
555 gchar
* filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog
));
557 FileNotSelected(dialog
);
561 base::FilePath
path(filename
);
565 FileSelected(dialog
, path
);
569 if (CallDirectoryExistsOnUIThread(path
))
570 FileNotSelected(dialog
);
572 FileSelected(dialog
, path
);
575 void SelectFileDialogImplGTK::OnSelectSingleFileDialogResponse(
576 GtkWidget
* dialog
, int response_id
) {
577 SelectSingleFileHelper(dialog
, response_id
, false);
580 void SelectFileDialogImplGTK::OnSelectSingleFolderDialogResponse(
581 GtkWidget
* dialog
, int response_id
) {
582 SelectSingleFileHelper(dialog
, response_id
, true);
585 void SelectFileDialogImplGTK::OnSelectMultiFileDialogResponse(GtkWidget
* dialog
,
587 if (IsCancelResponse(response_id
)) {
588 FileNotSelected(dialog
);
592 GSList
* filenames
= gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog
));
594 FileNotSelected(dialog
);
598 std::vector
<base::FilePath
> filenames_fp
;
599 for (GSList
* iter
= filenames
; iter
!= NULL
; iter
= g_slist_next(iter
)) {
600 base::FilePath
path(static_cast<char*>(iter
->data
));
602 if (CallDirectoryExistsOnUIThread(path
))
604 filenames_fp
.push_back(path
);
606 g_slist_free(filenames
);
608 if (filenames_fp
.empty()) {
609 FileNotSelected(dialog
);
612 MultiFilesSelected(dialog
, filenames_fp
);
615 void SelectFileDialogImplGTK::OnFileChooserDestroy(GtkWidget
* dialog
) {
616 FileDialogDestroyed(dialog
);
619 void SelectFileDialogImplGTK::OnUpdatePreview(GtkWidget
* chooser
) {
620 gchar
* filename
= gtk_file_chooser_get_preview_filename(
621 GTK_FILE_CHOOSER(chooser
));
624 // This will preserve the image's aspect ratio.
625 GdkPixbuf
* pixbuf
= gdk_pixbuf_new_from_file_at_size(filename
, kPreviewWidth
,
626 kPreviewHeight
, NULL
);
629 gtk_image_set_from_pixbuf(GTK_IMAGE(preview_
), pixbuf
);
630 g_object_unref(pixbuf
);
632 gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser
),
633 pixbuf
? TRUE
: FALSE
);
636 } // namespace libgtk2ui