1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 2 -*- */
3 /* GTK+: gtkfilechooserbutton.c
5 * Copyright (c) 2004 James M. Cape <jcape@ignore-your.tv>
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
25 #include <sys/types.h>
38 #include "gtkicontheme.h"
39 #include "gtkiconfactory.h"
43 #include "gtktogglebutton.h"
44 #include "gtkvseparator.h"
45 #include "gtkfilechooserdialog.h"
46 #include "gtkfilechooserentry.h"
47 #include "gtkfilechooserprivate.h"
48 #include "gtkfilechooserutils.h"
50 #include "gtkfilechooserbutton.h"
57 #define GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE(object) (GTK_FILE_CHOOSER_BUTTON ((object))->priv)
59 #define DEFAULT_FILENAME N_("(None)")
60 #define MIN_LABEL_WIDTH 100
61 #define ENTRY_BUTTON_SPACING 0
62 #define FALLBACK_ICON_SIZE 20
63 #define FALLBACK_ICON_NAME "stock_unknown"
64 #define NEW_FILE_ICON_NAME "stock_new"
65 #define NEW_DIR_ICON_NAME "stock_new-dir"
67 /* ********************** *
68 * Private Enumerations *
69 * ********************** */
83 /* ******************** *
84 * Private Structures *
85 * ******************** */
87 struct _GtkFileChooserButtonPrivate
90 GtkWidget *accept_button;
92 GtkWidget *entry_image;
95 GtkWidget *label_image;
100 gulong entry_changed_id;
101 gulong dialog_file_activated_id;
102 gulong dialog_folder_changed_id;
103 gulong dialog_selection_changed_id;
104 gulong dialog_selection_changed_proxy_id;
105 gulong settings_signal_id;
121 /* ********************* *
122 * Function Prototypes *
123 * ********************* */
125 /* GObject Functions */
126 static GObject *gtk_file_chooser_button_constructor (GType type,
128 GObjectConstructParam *params);
129 static void gtk_file_chooser_button_set_property (GObject *object,
133 static void gtk_file_chooser_button_get_property (GObject *object,
138 /* GtkObject Functions */
139 static void gtk_file_chooser_button_destroy (GtkObject *object);
141 /* GtkWidget Functions */
142 static void gtk_file_chooser_button_drag_data_received (GtkWidget *widget,
143 GdkDragContext *context,
146 GtkSelectionData *data,
149 static void gtk_file_chooser_button_show_all (GtkWidget *widget);
150 static void gtk_file_chooser_button_hide_all (GtkWidget *widget);
151 static void gtk_file_chooser_button_show (GtkWidget *widget);
152 static void gtk_file_chooser_button_hide (GtkWidget *widget);
153 static gboolean gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget,
154 gboolean group_cycling);
155 static void gtk_file_chooser_button_style_set (GtkWidget *widget,
156 GtkStyle *old_style);
157 static void gtk_file_chooser_button_screen_changed (GtkWidget *widget,
158 GdkScreen *old_screen);
160 /* Child Widget Callbacks */
161 static void dialog_update_preview_cb (GtkFileChooser *dialog,
163 static void dialog_selection_changed_cb (GtkFileChooser *dialog,
165 static void dialog_selection_changed_proxy_cb (GtkFileChooser *dialog,
167 static void dialog_file_activated_cb (GtkFileChooser *dialog,
169 static void dialog_current_folder_changed_cb (GtkFileChooser *dialog,
171 static void dialog_notify_cb (GObject *dialog,
174 static gboolean dialog_delete_event_cb (GtkWidget *dialog,
177 static void dialog_response_cb (GtkFileChooser *dialog,
181 static void button_toggled_cb (GtkToggleButton *real_button,
183 static void button_notify_active_cb (GObject *real_button,
187 static void entry_size_allocate_cb (GtkWidget *entry,
188 GtkAllocation *allocation,
190 static void entry_changed_cb (GtkEditable *editable,
193 /* Utility Functions */
194 static void remove_settings_signal (GtkFileChooserButton *button,
197 static void update_dialog (GtkFileChooserButton *button);
198 static void update_entry (GtkFileChooserButton *button);
199 static void update_label (GtkFileChooserButton *button);
200 static void update_icons (GtkFileChooserButton *button);
203 /* ******************* *
204 * GType Declaration *
205 * ******************* */
207 G_DEFINE_TYPE_WITH_CODE (GtkFileChooserButton, gtk_file_chooser_button, GTK_TYPE_HBOX, { \
208 G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, _gtk_file_chooser_delegate_iface_init) \
212 /* ***************** *
214 * ***************** */
217 gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class)
219 GObjectClass *gobject_class;
220 GtkObjectClass *gtkobject_class;
221 GtkWidgetClass *widget_class;
223 gobject_class = G_OBJECT_CLASS (class);
224 gtkobject_class = GTK_OBJECT_CLASS (class);
225 widget_class = GTK_WIDGET_CLASS (class);
227 gobject_class->constructor = gtk_file_chooser_button_constructor;
228 gobject_class->set_property = gtk_file_chooser_button_set_property;
229 gobject_class->get_property = gtk_file_chooser_button_get_property;
231 gtkobject_class->destroy = gtk_file_chooser_button_destroy;
233 widget_class->drag_data_received = gtk_file_chooser_button_drag_data_received;
234 widget_class->show_all = gtk_file_chooser_button_show_all;
235 widget_class->hide_all = gtk_file_chooser_button_hide_all;
236 widget_class->show = gtk_file_chooser_button_show;
237 widget_class->hide = gtk_file_chooser_button_hide;
238 widget_class->style_set = gtk_file_chooser_button_style_set;
239 widget_class->screen_changed = gtk_file_chooser_button_screen_changed;
240 widget_class->mnemonic_activate = gtk_file_chooser_button_mnemonic_activate;
242 g_object_class_install_property (gobject_class, PROP_DIALOG,
243 g_param_spec_object ("dialog",
245 P_("The file chooser dialog to use."),
246 GTK_TYPE_FILE_CHOOSER_DIALOG,
248 G_PARAM_CONSTRUCT_ONLY)));
249 g_object_class_install_property (gobject_class, PROP_TITLE,
250 g_param_spec_string ("title",
252 P_("The title of the file chooser dialog."),
255 g_object_class_install_property (gobject_class, PROP_ACTIVE,
256 g_param_spec_boolean ("active",
258 P_("Whether the browse dialog is visible or not."),
259 FALSE, G_PARAM_READWRITE));
260 g_object_class_install_property (gobject_class, PROP_WIDTH_CHARS,
261 g_param_spec_int ("width-chars",
262 P_("Width In Characters"),
263 P_("The desired width of the button widget, in characters."),
267 _gtk_file_chooser_install_properties (gobject_class);
269 g_type_class_add_private (class, sizeof (GtkFileChooserButtonPrivate));
274 gtk_file_chooser_button_init (GtkFileChooserButton *button)
276 GtkFileChooserButtonPrivate *priv;
277 GtkWidget *box, *image, *sep;
279 gtk_box_set_spacing (GTK_BOX (button), ENTRY_BUTTON_SPACING);
281 priv = G_TYPE_INSTANCE_GET_PRIVATE (button, GTK_TYPE_FILE_CHOOSER_BUTTON,
282 GtkFileChooserButtonPrivate);
285 priv->icon_size = FALLBACK_ICON_SIZE;
287 gtk_widget_push_composite_child ();
289 priv->entry_box = gtk_hbox_new (FALSE, 4);
290 gtk_container_add (GTK_CONTAINER (button), priv->entry_box);
292 priv->entry_image = gtk_image_new ();
293 gtk_box_pack_start (GTK_BOX (priv->entry_box), priv->entry_image,
295 gtk_widget_show (priv->entry_image);
297 priv->entry = _gtk_file_chooser_entry_new (FALSE);
298 gtk_container_add (GTK_CONTAINER (priv->entry_box), priv->entry);
299 gtk_widget_show (priv->entry);
301 priv->button = gtk_toggle_button_new ();
302 g_signal_connect (priv->button, "toggled",
303 G_CALLBACK (button_toggled_cb), button);
304 g_signal_connect (priv->button, "notify::active",
305 G_CALLBACK (button_notify_active_cb), button);
306 g_signal_connect (priv->entry, "size-allocate",
307 G_CALLBACK (entry_size_allocate_cb), priv->button);
308 gtk_box_pack_start (GTK_BOX (button), priv->button, TRUE, TRUE, 0);
309 gtk_widget_show (priv->button);
311 box = gtk_hbox_new (FALSE, 4);
312 gtk_container_add (GTK_CONTAINER (priv->button), box);
313 gtk_widget_show (box);
315 priv->label_box = gtk_hbox_new (FALSE, 6);
316 gtk_container_add (GTK_CONTAINER (box), priv->label_box);
317 gtk_widget_show (priv->label_box);
319 priv->label_image = gtk_image_new ();
320 gtk_box_pack_start (GTK_BOX (priv->label_box), priv->label_image,
322 gtk_widget_show (priv->label_image);
324 priv->label = gtk_label_new (_(DEFAULT_FILENAME));
325 gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_START);
326 gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
327 gtk_container_add (GTK_CONTAINER (priv->label_box), priv->label);
328 gtk_widget_show (priv->label);
330 sep = gtk_vseparator_new ();
331 gtk_box_pack_end (GTK_BOX (priv->label_box), sep, FALSE, FALSE, 0);
332 gtk_widget_show (sep);
334 image = gtk_image_new_from_stock (GTK_STOCK_OPEN,
335 GTK_ICON_SIZE_SMALL_TOOLBAR);
336 gtk_box_pack_end (GTK_BOX (box), image, FALSE, FALSE, 0);
337 gtk_widget_show (image);
339 gtk_widget_pop_composite_child ();
342 gtk_drag_dest_unset (priv->entry);
343 gtk_drag_dest_set (GTK_WIDGET (button),
344 (GTK_DEST_DEFAULT_ALL),
347 gtk_drag_dest_add_text_targets (GTK_WIDGET (button));
348 gtk_target_list_add_uri_targets (gtk_drag_dest_get_target_list (GTK_WIDGET (button)),
353 /* ******************* *
354 * GObject Functions *
355 * ******************* */
358 gtk_file_chooser_button_constructor (GType type,
360 GObjectConstructParam *params)
363 GtkFileChooserButtonPrivate *priv;
366 object = (*G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructor) (type,
369 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
374 priv->dialog = gtk_file_chooser_dialog_new_with_backend (NULL, NULL,
375 GTK_FILE_CHOOSER_ACTION_OPEN,
376 priv->backend, NULL);
378 priv->dialog = gtk_file_chooser_dialog_new (NULL, NULL,
379 GTK_FILE_CHOOSER_ACTION_OPEN,
382 gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
383 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
384 priv->accept_button = gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
386 GTK_RESPONSE_ACCEPT);
387 gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog),
388 GTK_RESPONSE_ACCEPT);
390 gtk_dialog_set_alternative_button_order (GTK_DIALOG (priv->dialog),
396 g_free (priv->backend);
397 priv->backend = NULL;
399 g_signal_connect (priv->dialog, "delete-event",
400 G_CALLBACK (dialog_delete_event_cb), object);
401 g_signal_connect (priv->dialog, "response",
402 G_CALLBACK (dialog_response_cb), object);
404 /* This is used, instead of the standard delegate, to ensure that signals are only
405 * delegated when the OK button is pressed. */
406 g_object_set_qdata (object, GTK_FILE_CHOOSER_DELEGATE_QUARK, priv->dialog);
407 priv->dialog_folder_changed_id =
408 g_signal_connect (priv->dialog, "current-folder-changed",
409 G_CALLBACK (dialog_current_folder_changed_cb), object);
410 priv->dialog_file_activated_id =
411 g_signal_connect (priv->dialog, "file-activated",
412 G_CALLBACK (dialog_file_activated_cb), object);
413 priv->dialog_selection_changed_id =
414 g_signal_connect (priv->dialog, "selection-changed",
415 G_CALLBACK (dialog_selection_changed_cb), object);
416 priv->dialog_selection_changed_proxy_id =
417 g_signal_connect (priv->dialog, "selection-changed",
418 G_CALLBACK (dialog_selection_changed_proxy_cb), object);
419 g_signal_connect (priv->dialog, "update-preview",
420 G_CALLBACK (dialog_update_preview_cb), object);
421 g_signal_connect (priv->dialog, "notify",
422 G_CALLBACK (dialog_notify_cb), object);
423 g_object_add_weak_pointer (G_OBJECT (priv->dialog),
424 (gpointer *) (&priv->dialog));
426 _gtk_file_chooser_entry_set_file_system (GTK_FILE_CHOOSER_ENTRY (priv->entry),
427 _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)));
428 path = gtk_file_path_new_steal ("/");
429 _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->entry),
431 priv->entry_changed_id = g_signal_connect (priv->entry, "changed",
432 G_CALLBACK (entry_changed_cb),
435 update_label (GTK_FILE_CHOOSER_BUTTON (object));
441 gtk_file_chooser_button_set_property (GObject *object,
446 GtkFileChooserButtonPrivate *priv;
448 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
454 priv->dialog = g_value_get_object (value);
457 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button),
458 g_value_get_boolean (value));
460 case PROP_WIDTH_CHARS:
461 gtk_file_chooser_button_set_width_chars (GTK_FILE_CHOOSER_BUTTON (object),
462 g_value_get_int (value));
465 case GTK_FILE_CHOOSER_PROP_ACTION:
466 g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
467 _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (priv->entry),
468 (GtkFileChooserAction) g_value_get_enum (value));
469 update_icons (GTK_FILE_CHOOSER_BUTTON (object));
471 switch (g_value_get_enum (value))
473 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
474 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
475 /* Fall through to set the widget states */
476 case GTK_FILE_CHOOSER_ACTION_OPEN:
477 gtk_widget_hide (priv->entry_box);
478 gtk_widget_show (priv->label_box);
479 gtk_box_set_child_packing (GTK_BOX (object), priv->button,
480 TRUE, TRUE, 0, GTK_PACK_START);
481 gtk_button_set_label (GTK_BUTTON (priv->accept_button),
485 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
486 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
487 /* Fall through to set the widget states */
488 case GTK_FILE_CHOOSER_ACTION_SAVE:
489 gtk_widget_show (priv->entry_box);
490 gtk_widget_hide (priv->label_box);
491 gtk_box_set_child_packing (GTK_BOX (object), priv->button,
492 FALSE, FALSE, 0, GTK_PACK_START);
493 gtk_button_set_label (GTK_BUTTON (priv->accept_button),
500 case GTK_FILE_CHOOSER_PROP_FILTER:
501 case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
502 case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
503 case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
504 case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
505 case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
506 case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
507 g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
510 case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
512 priv->backend = g_value_dup_string (value);
515 case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
516 g_warning ("%s: Choosers of type `%s` do not support selecting multiple files.",
517 G_STRFUNC, G_OBJECT_TYPE_NAME (object));
520 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
526 gtk_file_chooser_button_get_property (GObject *object,
531 GtkFileChooserButtonPrivate *priv;
533 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
538 g_value_set_boolean (value,
539 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button)));
541 case PROP_WIDTH_CHARS:
542 g_value_set_int (value,
543 gtk_entry_get_width_chars (GTK_ENTRY (priv->entry)));
547 case GTK_FILE_CHOOSER_PROP_ACTION:
548 case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
549 case GTK_FILE_CHOOSER_PROP_FILTER:
550 case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
551 case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
552 case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
553 case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
554 case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
555 case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
556 case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
557 g_object_get_property (G_OBJECT (priv->dialog), pspec->name, value);
561 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
567 /* ********************* *
568 * GtkObject Functions *
569 * ********************* */
572 gtk_file_chooser_button_destroy (GtkObject * object)
574 GtkFileChooserButtonPrivate *priv;
576 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
579 g_source_remove (priv->update_id);
581 if (priv->dialog != NULL)
582 gtk_widget_destroy (priv->dialog);
584 remove_settings_signal (GTK_FILE_CHOOSER_BUTTON (object),
585 gtk_widget_get_screen (GTK_WIDGET (object)));
587 if (GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy != NULL)
588 (*GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy) (object);
592 /* ********************* *
593 * GtkWidget Functions *
594 * ********************* */
597 gtk_file_chooser_button_drag_data_received (GtkWidget *widget,
598 GdkDragContext *context,
601 GtkSelectionData *data,
605 GtkFileChooserButtonPrivate *priv;
608 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received != NULL)
609 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received) (widget,
615 if (widget == NULL || context == NULL || data == NULL || data->length < 0)
618 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
628 uris = gtk_selection_data_get_uris (data);
634 for (i = 0; !selected && uris[i] != NULL; i++)
637 GtkFilePath *path, *base_path;
639 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
640 path = gtk_file_system_uri_to_path (fs, uris[i]);
644 gtk_file_system_get_parent (fs, path, &base_path, NULL))
646 GtkFileFolder *folder;
649 folder = gtk_file_system_get_folder (fs, base_path,
650 GTK_FILE_INFO_IS_FOLDER,
653 info = gtk_file_folder_get_info (folder, path, NULL);
657 GtkFileChooserAction action;
659 g_object_get (priv->dialog, "action", &action, NULL);
662 ((((action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER ||
663 action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) &&
664 gtk_file_info_get_is_folder (info)) ||
665 ((action == GTK_FILE_CHOOSER_ACTION_OPEN ||
666 action == GTK_FILE_CHOOSER_ACTION_SAVE) &&
667 !gtk_file_info_get_is_folder (info))) &&
668 _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
671 gtk_file_info_free (info);
676 gtk_file_path_free (base_path);
679 gtk_file_path_free (path);
687 text = gtk_selection_data_get_text (data);
688 gtk_entry_set_text (GTK_ENTRY (priv->entry), text);
693 gtk_drag_finish (context, TRUE, FALSE, drag_time);
697 gtk_file_chooser_button_show_all (GtkWidget *widget)
699 gtk_widget_show (widget);
703 gtk_file_chooser_button_hide_all (GtkWidget *widget)
705 gtk_widget_hide (widget);
709 gtk_file_chooser_button_show (GtkWidget *widget)
711 GtkFileChooserButtonPrivate *priv;
713 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
715 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show)
716 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show) (widget);
718 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button)))
719 gtk_widget_show (priv->dialog);
723 gtk_file_chooser_button_hide (GtkWidget *widget)
725 GtkFileChooserButtonPrivate *priv;
727 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
729 gtk_widget_hide (priv->dialog);
731 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide)
732 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide) (widget);
736 gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget,
737 gboolean group_cycling)
739 GtkFileChooserButtonPrivate *priv;
741 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
743 switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
745 case GTK_FILE_CHOOSER_ACTION_OPEN:
746 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
747 gtk_widget_grab_focus (priv->button);
749 case GTK_FILE_CHOOSER_ACTION_SAVE:
750 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
751 gtk_widget_grab_focus (priv->entry);
758 /* Changes the icons wherever it is needed */
760 change_icon_theme (GtkFileChooserButton *button)
762 GtkSettings *settings;
765 settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
767 if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_SMALL_TOOLBAR,
769 button->priv->icon_size = MAX (width, height);
771 button->priv->icon_size = FALLBACK_ICON_SIZE;
773 update_icons (button);
776 /* Callback used when a GtkSettings value changes */
778 settings_notify_cb (GObject *object,
784 name = g_param_spec_get_name (pspec);
786 if (strcmp (name, "gtk-icon-theme-name") == 0
787 || strcmp (name, "gtk-icon-sizes") == 0)
788 change_icon_theme (user_data);
791 /* Installs a signal handler for GtkSettings so that we can monitor changes in
795 check_icon_theme (GtkFileChooserButton *button)
797 GtkSettings *settings;
799 if (button->priv->settings_signal_id)
802 if (gtk_widget_has_screen (GTK_WIDGET (button)))
804 settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
805 button->priv->settings_signal_id = g_signal_connect (settings, "notify",
806 G_CALLBACK (settings_notify_cb),
809 change_icon_theme (button);
814 gtk_file_chooser_button_style_set (GtkWidget *widget,
817 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set)
818 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set) (widget,
821 if (gtk_widget_has_screen (widget))
822 change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
826 gtk_file_chooser_button_screen_changed (GtkWidget *widget,
827 GdkScreen *old_screen)
829 GtkFileChooserButton *button;
831 button = GTK_FILE_CHOOSER_BUTTON (widget);
833 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed)
834 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed) (widget,
837 remove_settings_signal (button, old_screen);
838 check_icon_theme (button);
842 /* ************************************************************************** *
844 * ************************************************************************** */
847 * gtk_file_chooser_button_new:
848 * @title: the title of the browse dialog.
850 * Creates a new file-selecting button widget.
852 * Returns: a new button widget.
857 gtk_file_chooser_button_new (const gchar *title)
859 return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
865 * gtk_file_chooser_button_new_with_backend:
866 * @title: the title of the browse dialog.
867 * @backend: the name of the #GtkFileSystem backend to use.
869 * Creates a new file-selecting button widget using @backend.
871 * Returns: a new button widget.
876 gtk_file_chooser_button_new_with_backend (const gchar *title,
877 const gchar *backend)
879 return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
881 "file-system-backend", backend,
886 * gtk_file_chooser_button_new_with_dialog:
887 * @dialog: the #GtkDialog widget to use.
889 * Creates a #GtkFileChooserButton widget which uses @dialog as it's
890 * file-picking window. Note that @dialog must be a #GtkFileChooserDialog (or
893 * Returns: a new button widget.
898 gtk_file_chooser_button_new_with_dialog (GtkWidget *dialog)
900 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_DIALOG (dialog), NULL);
902 return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
908 * gtk_file_chooser_button_set_title:
909 * @button: the button widget to modify.
910 * @title: the new browse dialog title.
912 * Modifies the @title of the browse dialog used by @button.
917 gtk_file_chooser_button_set_title (GtkFileChooserButton *button,
920 g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
922 gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title);
923 g_object_notify (G_OBJECT (button), "title");
927 * gtk_file_chooser_button_get_title:
928 * @button: the button widget to examine.
930 * Retrieves the title of the browse dialog used by @button. The returned value
931 * should not be modified or freed.
933 * Returns: a pointer to the browse dialog's title.
937 G_CONST_RETURN gchar *
938 gtk_file_chooser_button_get_title (GtkFileChooserButton *button)
940 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), NULL);
942 return gtk_window_get_title (GTK_WINDOW (button->priv->dialog));
946 * gtk_file_chooser_button_set_active:
947 * @button: the button widget to modify.
948 * @is_active: whether or not the dialog is visible.
950 * Modifies whether or not the dialog attached to @button is visible or not.
955 gtk_file_chooser_button_set_active (GtkFileChooserButton *button,
958 g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
960 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button->priv->button), is_active);
964 * gtk_file_chooser_button_get_active:
965 * @button: the button widget to examine.
967 * Retrieves whether or not the dialog attached to @button is visible.
969 * Returns: a boolean whether the dialog is visible or not.
974 gtk_file_chooser_button_get_active (GtkFileChooserButton *button)
976 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), FALSE);
978 return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button->priv->button));
982 * gtk_file_chooser_button_get_width_chars:
983 * @button: the button widget to examine.
985 * Retrieves the width in characters of the @button widget's entry and/or label.
987 * Returns: an integer width (in characters) that the button will use to size itself.
992 gtk_file_chooser_button_get_width_chars (GtkFileChooserButton *button)
994 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), -1);
996 return gtk_entry_get_width_chars (GTK_ENTRY (button->priv->entry));
1000 * gtk_file_chooser_button_set_width_chars:
1001 * @button: the button widget to examine.
1002 * @n_chars: the new width, in chracters.
1004 * Sets the width (in characters) that @button will use to @n_chars.
1009 gtk_file_chooser_button_set_width_chars (GtkFileChooserButton *button,
1012 g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
1014 gtk_entry_set_width_chars (GTK_ENTRY (button->priv->entry), n_chars);
1015 gtk_label_set_width_chars (GTK_LABEL (button->priv->label), n_chars);
1016 g_object_notify (G_OBJECT (button), "width-chars");
1020 /* ******************* *
1021 * Utility Functions *
1022 * ******************* */
1024 /* Removes the settings signal handler. It's safe to call multiple times */
1026 remove_settings_signal (GtkFileChooserButton *button,
1029 if (button->priv->settings_signal_id)
1031 GtkSettings *settings;
1033 settings = gtk_settings_get_for_screen (screen);
1034 g_signal_handler_disconnect (settings,
1035 button->priv->settings_signal_id);
1036 button->priv->settings_signal_id = 0;
1040 static GtkIconTheme *
1041 get_icon_theme (GtkWidget *widget)
1043 if (gtk_widget_has_screen (widget))
1044 return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
1046 return gtk_icon_theme_get_default ();
1050 check_if_path_exists (GtkFileSystem *fs,
1051 const GtkFilePath *path)
1053 gboolean path_exists;
1054 GtkFilePath *parent_path;
1056 path_exists = FALSE;
1059 if (gtk_file_system_get_parent (fs, path, &parent_path, NULL))
1061 GtkFileFolder *folder;
1063 folder = gtk_file_system_get_folder (fs, parent_path, 0, NULL);
1068 info = gtk_file_folder_get_info (folder, path, NULL);
1072 gtk_file_info_free (info);
1075 g_object_unref (folder);
1078 gtk_file_path_free (parent_path);
1085 update_icons (GtkFileChooserButton *button)
1087 GtkFileChooserButtonPrivate *priv;
1091 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1093 paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
1101 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1103 switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
1105 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1107 GtkFileSystemVolume *volume;
1109 volume = gtk_file_system_get_volume_for_path (fs, path);
1112 GtkFilePath *base_path;
1114 base_path = gtk_file_system_volume_get_base_path (fs, volume);
1116 if (base_path && gtk_file_path_compare (base_path, path) == 0)
1117 pixbuf = gtk_file_system_volume_render_icon (fs, volume,
1118 GTK_WIDGET (button),
1123 gtk_file_path_free (base_path);
1125 gtk_file_system_volume_free (fs, volume);
1129 case GTK_FILE_CHOOSER_ACTION_OPEN:
1131 pixbuf = gtk_file_system_render_icon (fs, path, GTK_WIDGET (button),
1132 priv->icon_size, NULL);
1135 case GTK_FILE_CHOOSER_ACTION_SAVE:
1136 if (check_if_path_exists (fs, path))
1137 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1138 GTK_STOCK_DIALOG_WARNING,
1139 priv->icon_size, 0, NULL);
1141 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1143 priv->icon_size, 0, NULL);
1145 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1146 if (check_if_path_exists (fs, path))
1147 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1148 GTK_STOCK_DIALOG_WARNING,
1149 priv->icon_size, 0, NULL);
1151 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1153 priv->icon_size, 0, NULL);
1157 gtk_file_paths_free (paths);
1161 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1163 priv->icon_size, 0, NULL);
1165 gtk_image_set_from_pixbuf (GTK_IMAGE (priv->entry_image), pixbuf);
1166 gtk_image_set_from_pixbuf (GTK_IMAGE (priv->label_image), pixbuf);
1169 g_object_unref (pixbuf);
1174 update_label (GtkFileChooserButton *button)
1176 GtkFileChooserButtonPrivate *priv;
1180 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1181 paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (button->priv->dialog));
1188 GtkFileSystemVolume *volume;
1192 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1194 volume = gtk_file_system_get_volume_for_path (fs, path);
1197 GtkFilePath *base_path;
1199 base_path = gtk_file_system_volume_get_base_path (fs, volume);
1200 if (base_path && gtk_file_path_compare (base_path, path) == 0)
1201 label_text = gtk_file_system_volume_get_display_name (fs, volume);
1204 gtk_file_path_free (base_path);
1206 gtk_file_system_volume_free (fs, volume);
1212 if (gtk_file_system_path_is_local (fs, path))
1218 filename = gtk_file_system_path_to_filename (fs, path);
1223 home = g_get_home_dir ();
1225 /* Munging for psuedo-volumes and files in the user's home tree */
1228 if (strcmp (filename, home) == 0)
1230 label_text = g_strdup (_("Home"));
1234 tmp = g_build_filename (home, "Desktop", NULL);
1236 if (strcmp (filename, tmp) == 0)
1237 label_text = g_strdup (_("Desktop"));
1244 if (g_str_has_prefix (filename, home))
1246 label_text = g_strconcat ("~", filename + strlen (home), NULL);
1252 label_text = g_strdup (filename);
1261 uri = gtk_file_system_path_to_uri (fs, path);
1268 gtk_file_paths_free (paths);
1273 gtk_label_set_text (GTK_LABEL (priv->label), label_text);
1274 g_free (label_text);
1277 gtk_label_set_text (GTK_LABEL (priv->label), _(DEFAULT_FILENAME));
1281 update_entry (GtkFileChooserButton *button)
1283 GtkFileChooserButtonPrivate *priv;
1287 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1289 paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
1297 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1299 if (gtk_file_system_path_is_local (fs, path))
1301 filename = gtk_file_system_path_to_filename (fs, path);
1308 if (g_file_test (filename, G_FILE_TEST_IS_DIR))
1310 tmp = g_strconcat (filename, "/", NULL);
1315 home = g_get_home_dir ();
1317 if (home && g_str_has_prefix (filename, home))
1319 tmp = g_strconcat ("~", filename + strlen (home), NULL);
1326 filename = gtk_file_system_path_to_uri (fs, path);
1335 entry_text = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
1338 gtk_entry_set_text (GTK_ENTRY (priv->entry), entry_text);
1339 g_free (entry_text);
1342 gtk_entry_set_text (GTK_ENTRY (priv->entry), "");
1346 update_dialog (GtkFileChooserButton *button)
1348 GtkFileChooserButtonPrivate *priv;
1349 GtkFilePath *current_folder;
1351 GtkFilePath *folder_part, *full_path;
1354 GtkFilePath *base_path;
1356 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1359 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1361 text = gtk_entry_get_text (GTK_ENTRY (priv->entry));
1363 base_path = gtk_file_path_new_dup ("/");
1364 gtk_file_system_parse (fs, base_path, text, &folder_part, &file_part, NULL);
1365 gtk_file_path_free (base_path);
1367 switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
1369 case GTK_FILE_CHOOSER_ACTION_OPEN:
1370 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
1373 GtkFileFolder *folder;
1376 folder = gtk_file_system_get_folder (fs, folder_part,
1377 GTK_FILE_INFO_IS_FOLDER, NULL);
1379 full_path = gtk_file_system_make_path (fs, folder_part, file_part, NULL);
1380 info = gtk_file_folder_get_info (folder, full_path, NULL);
1382 /* Entry contents don't exist. */
1384 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1386 /* Entry contents are a folder */
1387 else if (gtk_file_info_get_is_folder (info))
1388 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1390 /* Entry contents must be a file. */
1392 _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
1396 gtk_file_info_free (info);
1398 gtk_file_path_free (full_path);
1401 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1402 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
1405 full_path = gtk_file_system_make_path (fs, folder_part, file_part, NULL);
1407 /* Entry contents don't exist. */
1409 _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
1412 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1415 gtk_file_path_free (full_path);
1419 case GTK_FILE_CHOOSER_ACTION_SAVE:
1420 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1421 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
1424 current_folder = _gtk_file_chooser_get_current_folder_path (GTK_FILE_CHOOSER (priv->dialog));
1426 if (!current_folder ||
1427 gtk_file_path_compare (current_folder, folder_part) != 0)
1429 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1431 g_signal_emit_by_name (button, "current-folder-changed");
1435 gtk_file_path_free (current_folder);
1438 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (priv->dialog),
1440 g_signal_emit_by_name (button, "selection-changed");
1445 /* ************************ *
1446 * Child-Widget Callbacks *
1447 * ************************ */
1450 dialog_current_folder_changed_cb (GtkFileChooser *dialog,
1453 g_signal_emit_by_name (user_data, "current-folder-changed");
1457 dialog_file_activated_cb (GtkFileChooser *dialog,
1460 g_signal_emit_by_name (user_data, "file-activated");
1464 dialog_selection_changed_cb (GtkFileChooser *dialog,
1467 GtkFileChooserButtonPrivate *priv;
1469 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1471 g_signal_handler_block (priv->entry, priv->entry_changed_id);
1472 update_entry (user_data);
1473 g_signal_handler_unblock (priv->entry, priv->entry_changed_id);
1474 update_icons (user_data);
1475 update_label (user_data);
1479 dialog_selection_changed_proxy_cb (GtkFileChooser *dialog,
1482 GtkFileChooserButtonPrivate *priv;
1484 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1486 g_signal_emit_by_name (user_data, "selection-changed");
1490 dialog_update_preview_cb (GtkFileChooser *dialog,
1493 g_signal_emit_by_name (user_data, "update-preview");
1497 dialog_notify_cb (GObject *dialog,
1503 iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (dialog)),
1504 GTK_TYPE_FILE_CHOOSER);
1505 if (g_object_interface_find_property (iface, pspec->name))
1506 g_object_notify (user_data, pspec->name);
1510 dialog_delete_event_cb (GtkWidget *dialog,
1514 g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_DELETE_EVENT);
1520 dialog_response_cb (GtkFileChooser *dialog,
1524 GtkFileChooserButtonPrivate *priv;
1526 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1528 if (response == GTK_RESPONSE_ACCEPT)
1530 g_signal_handler_block (priv->entry, priv->entry_changed_id);
1531 update_entry (user_data);
1532 g_signal_handler_unblock (priv->entry, priv->entry_changed_id);
1533 update_label (user_data);
1534 update_icons (user_data);
1536 g_signal_emit_by_name (user_data, "current-folder-changed");
1537 g_signal_emit_by_name (user_data, "selection-changed");
1541 update_dialog (user_data);
1544 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE);
1549 button_toggled_cb (GtkToggleButton *real_button,
1552 GtkFileChooserButtonPrivate *priv;
1554 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1556 if (gtk_toggle_button_get_active (real_button))
1558 /* Setup the dialog parent to be chooser button's toplevel, and be modal
1560 if (!GTK_WIDGET_VISIBLE (priv->dialog))
1562 GtkWidget *toplevel;
1564 toplevel = gtk_widget_get_toplevel (user_data);
1566 if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_IS_WINDOW (toplevel))
1568 if (GTK_WINDOW (toplevel) != gtk_window_get_transient_for (GTK_WINDOW (priv->dialog)))
1569 gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), GTK_WINDOW (toplevel));
1571 gtk_window_set_modal (GTK_WINDOW (priv->dialog),
1572 gtk_window_get_modal (GTK_WINDOW (toplevel)));
1576 g_signal_handler_block (priv->dialog,
1577 priv->dialog_folder_changed_id);
1578 g_signal_handler_block (priv->dialog,
1579 priv->dialog_file_activated_id);
1580 g_signal_handler_block (priv->dialog,
1581 priv->dialog_selection_changed_proxy_id);
1582 gtk_widget_set_sensitive (priv->entry, FALSE);
1583 gtk_window_present (GTK_WINDOW (priv->dialog));
1587 g_signal_handler_unblock (priv->dialog,
1588 priv->dialog_folder_changed_id);
1589 g_signal_handler_unblock (priv->dialog,
1590 priv->dialog_file_activated_id);
1591 g_signal_handler_unblock (priv->dialog,
1592 priv->dialog_selection_changed_proxy_id);
1593 gtk_widget_set_sensitive (priv->entry, TRUE);
1594 gtk_widget_hide (priv->dialog);
1599 button_notify_active_cb (GObject *real_button,
1603 g_object_notify (user_data, "active");
1608 update_idler (gpointer user_data)
1610 GtkFileChooserButtonPrivate *priv;
1613 GDK_THREADS_ENTER ();
1615 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1617 if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (priv->entry),
1620 g_signal_handler_block (priv->dialog,
1621 priv->dialog_selection_changed_id);
1622 update_dialog (user_data);
1623 g_signal_handler_unblock (priv->dialog,
1624 priv->dialog_selection_changed_id);
1625 update_icons (user_data);
1626 update_label (user_data);
1627 priv->update_id = 0;
1633 GDK_THREADS_LEAVE ();
1639 entry_changed_cb (GtkEditable *editable,
1642 GtkFileChooserButtonPrivate *priv;
1644 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1646 if (priv->update_id)
1647 g_source_remove (priv->update_id);
1649 priv->update_id = g_idle_add_full (G_PRIORITY_LOW, update_idler,
1653 /* Ensure the button height == entry height */
1655 entry_size_allocate_cb (GtkWidget *entry,
1656 GtkAllocation *allocation,
1659 gtk_widget_set_size_request (user_data, -1, allocation->height);