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"
42 #include "gtktogglebutton.h"
43 #include "gtkvseparator.h"
44 #include "gtkfilechooserdialog.h"
45 #include "gtkfilechooserentry.h"
46 #include "gtkfilechooserprivate.h"
47 #include "gtkfilechooserutils.h"
49 #include "gtkfilechooserbutton.h"
56 #define GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE(object) (GTK_FILE_CHOOSER_BUTTON ((object))->priv)
58 #define DEFAULT_FILENAME N_("(None)")
59 #define MIN_LABEL_WIDTH 100
60 #define ENTRY_BUTTON_SPACING 0
61 #define FALLBACK_ICON_SIZE 20
62 #define FALLBACK_ICON_NAME "stock_unknown"
63 #define NEW_FILE_ICON_NAME "stock_new"
64 #define NEW_DIR_ICON_NAME "stock_new-dir"
66 /* ********************** *
67 * Private Enumerations *
68 * ********************** */
82 /* ******************** *
83 * Private Structures *
84 * ******************** */
86 struct _GtkFileChooserButtonPrivate
89 GtkWidget *accept_button;
91 GtkWidget *entry_image;
94 GtkWidget *label_image;
99 gulong entry_changed_id;
100 gulong dialog_file_activated_id;
101 gulong dialog_folder_changed_id;
102 gulong dialog_selection_changed_id;
103 gulong dialog_selection_changed_proxy_id;
104 gulong settings_signal_id;
120 /* ********************* *
121 * Function Prototypes *
122 * ********************* */
124 /* GObject Functions */
125 static GObject *gtk_file_chooser_button_constructor (GType type,
127 GObjectConstructParam *params);
128 static void gtk_file_chooser_button_set_property (GObject *object,
132 static void gtk_file_chooser_button_get_property (GObject *object,
137 /* GtkObject Functions */
138 static void gtk_file_chooser_button_destroy (GtkObject *object);
140 /* GtkWidget Functions */
141 static void gtk_file_chooser_button_drag_data_received (GtkWidget *widget,
142 GdkDragContext *context,
145 GtkSelectionData *data,
148 static void gtk_file_chooser_button_show_all (GtkWidget *widget);
149 static void gtk_file_chooser_button_hide_all (GtkWidget *widget);
150 static void gtk_file_chooser_button_show (GtkWidget *widget);
151 static void gtk_file_chooser_button_hide (GtkWidget *widget);
152 static gboolean gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget,
153 gboolean group_cycling);
154 static void gtk_file_chooser_button_style_set (GtkWidget *widget,
155 GtkStyle *old_style);
156 static void gtk_file_chooser_button_screen_changed (GtkWidget *widget,
157 GdkScreen *old_screen);
159 /* Child Widget Callbacks */
160 static void dialog_update_preview_cb (GtkFileChooser *dialog,
162 static void dialog_selection_changed_cb (GtkFileChooser *dialog,
164 static void dialog_selection_changed_proxy_cb (GtkFileChooser *dialog,
166 static void dialog_file_activated_cb (GtkFileChooser *dialog,
168 static void dialog_current_folder_changed_cb (GtkFileChooser *dialog,
170 static void dialog_notify_cb (GObject *dialog,
173 static gboolean dialog_delete_event_cb (GtkWidget *dialog,
176 static void dialog_response_cb (GtkFileChooser *dialog,
180 static void button_toggled_cb (GtkToggleButton *real_button,
182 static void button_notify_active_cb (GObject *real_button,
186 static void entry_size_allocate_cb (GtkWidget *entry,
187 GtkAllocation *allocation,
189 static void entry_changed_cb (GtkEditable *editable,
192 /* Utility Functions */
193 static void remove_settings_signal (GtkFileChooserButton *button,
196 static void update_dialog (GtkFileChooserButton *button);
197 static void update_entry (GtkFileChooserButton *button);
198 static void update_label (GtkFileChooserButton *button);
199 static void update_icons (GtkFileChooserButton *button);
202 /* ******************* *
203 * GType Declaration *
204 * ******************* */
206 G_DEFINE_TYPE_WITH_CODE (GtkFileChooserButton, gtk_file_chooser_button, GTK_TYPE_HBOX, { \
207 G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, _gtk_file_chooser_delegate_iface_init) \
211 /* ***************** *
213 * ***************** */
216 gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class)
218 GObjectClass *gobject_class;
219 GtkObjectClass *gtkobject_class;
220 GtkWidgetClass *widget_class;
222 gobject_class = G_OBJECT_CLASS (class);
223 gtkobject_class = GTK_OBJECT_CLASS (class);
224 widget_class = GTK_WIDGET_CLASS (class);
226 gobject_class->constructor = gtk_file_chooser_button_constructor;
227 gobject_class->set_property = gtk_file_chooser_button_set_property;
228 gobject_class->get_property = gtk_file_chooser_button_get_property;
230 gtkobject_class->destroy = gtk_file_chooser_button_destroy;
232 widget_class->drag_data_received = gtk_file_chooser_button_drag_data_received;
233 widget_class->show_all = gtk_file_chooser_button_show_all;
234 widget_class->hide_all = gtk_file_chooser_button_hide_all;
235 widget_class->show = gtk_file_chooser_button_show;
236 widget_class->hide = gtk_file_chooser_button_hide;
237 widget_class->style_set = gtk_file_chooser_button_style_set;
238 widget_class->screen_changed = gtk_file_chooser_button_screen_changed;
239 widget_class->mnemonic_activate = gtk_file_chooser_button_mnemonic_activate;
241 g_object_class_install_property (gobject_class, PROP_DIALOG,
242 g_param_spec_object ("dialog",
244 P_("The file chooser dialog to use."),
245 GTK_TYPE_FILE_CHOOSER_DIALOG,
247 G_PARAM_CONSTRUCT_ONLY)));
248 g_object_class_install_property (gobject_class, PROP_TITLE,
249 g_param_spec_string ("title",
251 P_("The title of the file chooser dialog."),
254 g_object_class_install_property (gobject_class, PROP_ACTIVE,
255 g_param_spec_boolean ("active",
257 P_("Whether the browse dialog is visible or not."),
258 FALSE, G_PARAM_READWRITE));
259 g_object_class_install_property (gobject_class, PROP_WIDTH_CHARS,
260 g_param_spec_int ("width-chars",
261 P_("Width In Characters"),
262 P_("The desired width of the button widget, in characters."),
266 _gtk_file_chooser_install_properties (gobject_class);
268 g_type_class_add_private (class, sizeof (GtkFileChooserButtonPrivate));
273 gtk_file_chooser_button_init (GtkFileChooserButton *button)
275 GtkFileChooserButtonPrivate *priv;
276 GtkWidget *box, *image, *sep;
278 gtk_box_set_spacing (GTK_BOX (button), ENTRY_BUTTON_SPACING);
280 priv = G_TYPE_INSTANCE_GET_PRIVATE (button, GTK_TYPE_FILE_CHOOSER_BUTTON,
281 GtkFileChooserButtonPrivate);
284 priv->icon_size = FALLBACK_ICON_SIZE;
286 gtk_widget_push_composite_child ();
288 priv->entry_box = gtk_hbox_new (FALSE, 4);
289 gtk_container_add (GTK_CONTAINER (button), priv->entry_box);
291 priv->entry_image = gtk_image_new ();
292 gtk_box_pack_start (GTK_BOX (priv->entry_box), priv->entry_image,
294 gtk_widget_show (priv->entry_image);
296 priv->entry = _gtk_file_chooser_entry_new (FALSE);
297 gtk_container_add (GTK_CONTAINER (priv->entry_box), priv->entry);
298 gtk_widget_show (priv->entry);
300 priv->button = gtk_toggle_button_new ();
301 g_signal_connect (priv->button, "toggled",
302 G_CALLBACK (button_toggled_cb), button);
303 g_signal_connect (priv->button, "notify::active",
304 G_CALLBACK (button_notify_active_cb), button);
305 g_signal_connect (priv->entry, "size-allocate",
306 G_CALLBACK (entry_size_allocate_cb), priv->button);
307 gtk_box_pack_start (GTK_BOX (button), priv->button, TRUE, TRUE, 0);
308 gtk_widget_show (priv->button);
310 box = gtk_hbox_new (FALSE, 4);
311 gtk_container_add (GTK_CONTAINER (priv->button), box);
312 gtk_widget_show (box);
314 priv->label_box = gtk_hbox_new (FALSE, 6);
315 gtk_container_add (GTK_CONTAINER (box), priv->label_box);
316 gtk_widget_show (priv->label_box);
318 priv->label_image = gtk_image_new ();
319 gtk_box_pack_start (GTK_BOX (priv->label_box), priv->label_image,
321 gtk_widget_show (priv->label_image);
323 priv->label = gtk_label_new (_(DEFAULT_FILENAME));
324 gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_START);
325 gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
326 gtk_container_add (GTK_CONTAINER (priv->label_box), priv->label);
327 gtk_widget_show (priv->label);
329 sep = gtk_vseparator_new ();
330 gtk_box_pack_end (GTK_BOX (priv->label_box), sep, FALSE, FALSE, 0);
331 gtk_widget_show (sep);
333 image = gtk_image_new_from_stock (GTK_STOCK_OPEN,
334 GTK_ICON_SIZE_SMALL_TOOLBAR);
335 gtk_box_pack_end (GTK_BOX (box), image, FALSE, FALSE, 0);
336 gtk_widget_show (image);
338 gtk_widget_pop_composite_child ();
341 gtk_drag_dest_unset (priv->entry);
342 gtk_drag_dest_set (GTK_WIDGET (button),
343 (GTK_DEST_DEFAULT_ALL),
346 gtk_drag_dest_add_text_targets (GTK_WIDGET (button));
347 gtk_target_list_add_uri_targets (gtk_drag_dest_get_target_list (GTK_WIDGET (button)),
352 /* ******************* *
353 * GObject Functions *
354 * ******************* */
357 gtk_file_chooser_button_constructor (GType type,
359 GObjectConstructParam *params)
362 GtkFileChooserButtonPrivate *priv;
365 object = (*G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructor) (type,
368 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
373 priv->dialog = gtk_file_chooser_dialog_new_with_backend (NULL, NULL,
374 GTK_FILE_CHOOSER_ACTION_OPEN,
375 priv->backend, NULL);
377 priv->dialog = gtk_file_chooser_dialog_new (NULL, NULL,
378 GTK_FILE_CHOOSER_ACTION_OPEN,
381 gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
382 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
383 priv->accept_button = gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
385 GTK_RESPONSE_ACCEPT);
386 gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog),
387 GTK_RESPONSE_ACCEPT);
389 gtk_dialog_set_alternative_button_order (GTK_DIALOG (priv->dialog),
395 g_free (priv->backend);
396 priv->backend = NULL;
398 g_signal_connect (priv->dialog, "delete-event",
399 G_CALLBACK (dialog_delete_event_cb), object);
400 g_signal_connect (priv->dialog, "response",
401 G_CALLBACK (dialog_response_cb), object);
403 /* This is used, instead of the standard delegate, to ensure that signals are only
404 * delegated when the OK button is pressed. */
405 g_object_set_qdata (object, GTK_FILE_CHOOSER_DELEGATE_QUARK, priv->dialog);
406 priv->dialog_folder_changed_id =
407 g_signal_connect (priv->dialog, "current-folder-changed",
408 G_CALLBACK (dialog_current_folder_changed_cb), object);
409 priv->dialog_file_activated_id =
410 g_signal_connect (priv->dialog, "file-activated",
411 G_CALLBACK (dialog_file_activated_cb), object);
412 priv->dialog_selection_changed_id =
413 g_signal_connect (priv->dialog, "selection-changed",
414 G_CALLBACK (dialog_selection_changed_cb), object);
415 priv->dialog_selection_changed_proxy_id =
416 g_signal_connect (priv->dialog, "selection-changed",
417 G_CALLBACK (dialog_selection_changed_proxy_cb), object);
418 g_signal_connect (priv->dialog, "update-preview",
419 G_CALLBACK (dialog_update_preview_cb), object);
420 g_signal_connect (priv->dialog, "notify",
421 G_CALLBACK (dialog_notify_cb), object);
422 g_object_add_weak_pointer (G_OBJECT (priv->dialog),
423 (gpointer *) (&priv->dialog));
425 _gtk_file_chooser_entry_set_file_system (GTK_FILE_CHOOSER_ENTRY (priv->entry),
426 _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)));
427 path = gtk_file_path_new_steal ("/");
428 _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->entry),
430 priv->entry_changed_id = g_signal_connect (priv->entry, "changed",
431 G_CALLBACK (entry_changed_cb),
434 update_label (GTK_FILE_CHOOSER_BUTTON (object));
440 gtk_file_chooser_button_set_property (GObject *object,
445 GtkFileChooserButtonPrivate *priv;
447 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
453 priv->dialog = g_value_get_object (value);
456 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button),
457 g_value_get_boolean (value));
459 case PROP_WIDTH_CHARS:
460 gtk_file_chooser_button_set_width_chars (GTK_FILE_CHOOSER_BUTTON (object),
461 g_value_get_int (value));
464 case GTK_FILE_CHOOSER_PROP_ACTION:
465 g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
466 _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (priv->entry),
467 (GtkFileChooserAction) g_value_get_enum (value));
468 update_icons (GTK_FILE_CHOOSER_BUTTON (object));
470 switch (g_value_get_enum (value))
472 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
473 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
474 /* Fall through to set the widget states */
475 case GTK_FILE_CHOOSER_ACTION_OPEN:
476 gtk_widget_hide (priv->entry_box);
477 gtk_widget_show (priv->label_box);
478 gtk_box_set_child_packing (GTK_BOX (object), priv->button,
479 TRUE, TRUE, 0, GTK_PACK_START);
480 gtk_button_set_label (GTK_BUTTON (priv->accept_button),
484 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
485 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
486 /* Fall through to set the widget states */
487 case GTK_FILE_CHOOSER_ACTION_SAVE:
488 gtk_widget_show (priv->entry_box);
489 gtk_widget_hide (priv->label_box);
490 gtk_box_set_child_packing (GTK_BOX (object), priv->button,
491 FALSE, FALSE, 0, GTK_PACK_START);
492 gtk_button_set_label (GTK_BUTTON (priv->accept_button),
499 case GTK_FILE_CHOOSER_PROP_FILTER:
500 case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
501 case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
502 case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
503 case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
504 case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
505 case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
506 g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
509 case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
511 priv->backend = g_value_dup_string (value);
514 case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
515 g_warning ("%s: Choosers of type `%s` do not support selecting multiple files.",
516 G_STRFUNC, G_OBJECT_TYPE_NAME (object));
519 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
525 gtk_file_chooser_button_get_property (GObject *object,
530 GtkFileChooserButtonPrivate *priv;
532 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
537 g_value_set_boolean (value,
538 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button)));
540 case PROP_WIDTH_CHARS:
541 g_value_set_int (value,
542 gtk_entry_get_width_chars (GTK_ENTRY (priv->entry)));
546 case GTK_FILE_CHOOSER_PROP_ACTION:
547 case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
548 case GTK_FILE_CHOOSER_PROP_FILTER:
549 case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
550 case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
551 case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
552 case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
553 case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
554 case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
555 case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
556 g_object_get_property (G_OBJECT (priv->dialog), pspec->name, value);
560 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
566 /* ********************* *
567 * GtkObject Functions *
568 * ********************* */
571 gtk_file_chooser_button_destroy (GtkObject * object)
573 GtkFileChooserButtonPrivate *priv;
575 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
578 g_source_remove (priv->update_id);
580 if (priv->dialog != NULL)
581 gtk_widget_destroy (priv->dialog);
583 remove_settings_signal (GTK_FILE_CHOOSER_BUTTON (object),
584 gtk_widget_get_screen (GTK_WIDGET (object)));
586 if (GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy != NULL)
587 (*GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy) (object);
591 /* ********************* *
592 * GtkWidget Functions *
593 * ********************* */
596 gtk_file_chooser_button_drag_data_received (GtkWidget *widget,
597 GdkDragContext *context,
600 GtkSelectionData *data,
604 GtkFileChooserButtonPrivate *priv;
607 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received != NULL)
608 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received) (widget,
614 if (widget == NULL || context == NULL || data == NULL || data->length < 0)
617 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
627 uris = gtk_selection_data_get_uris (data);
633 for (i = 0; !selected && uris[i] != NULL; i++)
636 GtkFilePath *path, *base_path;
638 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
639 path = gtk_file_system_uri_to_path (fs, uris[i]);
643 gtk_file_system_get_parent (fs, path, &base_path, NULL))
645 GtkFileFolder *folder;
648 folder = gtk_file_system_get_folder (fs, base_path,
649 GTK_FILE_INFO_IS_FOLDER,
652 info = gtk_file_folder_get_info (folder, path, NULL);
656 GtkFileChooserAction action;
658 g_object_get (priv->dialog, "action", &action, NULL);
661 ((((action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER ||
662 action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) &&
663 gtk_file_info_get_is_folder (info)) ||
664 ((action == GTK_FILE_CHOOSER_ACTION_OPEN ||
665 action == GTK_FILE_CHOOSER_ACTION_SAVE) &&
666 !gtk_file_info_get_is_folder (info))) &&
667 _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
670 gtk_file_info_free (info);
675 gtk_file_path_free (base_path);
678 gtk_file_path_free (path);
686 text = gtk_selection_data_get_text (data);
687 gtk_entry_set_text (GTK_ENTRY (priv->entry), text);
692 gtk_drag_finish (context, TRUE, FALSE, drag_time);
696 gtk_file_chooser_button_show_all (GtkWidget *widget)
698 gtk_widget_show (widget);
702 gtk_file_chooser_button_hide_all (GtkWidget *widget)
704 gtk_widget_hide (widget);
708 gtk_file_chooser_button_show (GtkWidget *widget)
710 GtkFileChooserButtonPrivate *priv;
712 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
714 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show)
715 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show) (widget);
717 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button)))
718 gtk_widget_show (priv->dialog);
722 gtk_file_chooser_button_hide (GtkWidget *widget)
724 GtkFileChooserButtonPrivate *priv;
726 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
728 gtk_widget_hide (priv->dialog);
730 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide)
731 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide) (widget);
735 gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget,
736 gboolean group_cycling)
738 GtkFileChooserButtonPrivate *priv;
740 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
742 switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
744 case GTK_FILE_CHOOSER_ACTION_OPEN:
745 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
746 gtk_widget_grab_focus (priv->button);
748 case GTK_FILE_CHOOSER_ACTION_SAVE:
749 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
750 gtk_widget_grab_focus (priv->entry);
757 /* Changes the icons wherever it is needed */
759 change_icon_theme (GtkFileChooserButton *button)
761 GtkSettings *settings;
764 settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
766 if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_SMALL_TOOLBAR,
768 button->priv->icon_size = MAX (width, height);
770 button->priv->icon_size = FALLBACK_ICON_SIZE;
772 update_icons (button);
775 /* Callback used when a GtkSettings value changes */
777 settings_notify_cb (GObject *object,
783 name = g_param_spec_get_name (pspec);
785 if (strcmp (name, "gtk-icon-theme-name") == 0
786 || strcmp (name, "gtk-icon-sizes") == 0)
787 change_icon_theme (user_data);
790 /* Installs a signal handler for GtkSettings so that we can monitor changes in
794 check_icon_theme (GtkFileChooserButton *button)
796 GtkSettings *settings;
798 if (button->priv->settings_signal_id)
801 if (gtk_widget_has_screen (GTK_WIDGET (button)))
803 settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
804 button->priv->settings_signal_id = g_signal_connect (settings, "notify",
805 G_CALLBACK (settings_notify_cb),
808 change_icon_theme (button);
813 gtk_file_chooser_button_style_set (GtkWidget *widget,
816 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set)
817 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set) (widget,
820 if (gtk_widget_has_screen (widget))
821 change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
825 gtk_file_chooser_button_screen_changed (GtkWidget *widget,
826 GdkScreen *old_screen)
828 GtkFileChooserButton *button;
830 button = GTK_FILE_CHOOSER_BUTTON (widget);
832 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed)
833 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed) (widget,
836 remove_settings_signal (button, old_screen);
837 check_icon_theme (button);
841 /* ************************************************************************** *
843 * ************************************************************************** */
846 * gtk_file_chooser_button_new:
847 * @title: the title of the browse dialog.
849 * Creates a new file-selecting button widget.
851 * Returns: a new button widget.
856 gtk_file_chooser_button_new (const gchar *title)
858 return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
864 * gtk_file_chooser_button_new_with_backend:
865 * @title: the title of the browse dialog.
866 * @backend: the name of the #GtkFileSystem backend to use.
868 * Creates a new file-selecting button widget using @backend.
870 * Returns: a new button widget.
875 gtk_file_chooser_button_new_with_backend (const gchar *title,
876 const gchar *backend)
878 return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
880 "file-system-backend", backend,
885 * gtk_file_chooser_button_new_with_dialog:
886 * @dialog: the #GtkDialog widget to use.
888 * Creates a #GtkFileChooserButton widget which uses @dialog as it's
889 * file-picking window. Note that @dialog must be a #GtkFileChooserDialog (or
892 * Returns: a new button widget.
897 gtk_file_chooser_button_new_with_dialog (GtkWidget *dialog)
899 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_DIALOG (dialog), NULL);
901 return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
907 * gtk_file_chooser_button_set_title:
908 * @button: the button widget to modify.
909 * @title: the new browse dialog title.
911 * Modifies the @title of the browse dialog used by @button.
916 gtk_file_chooser_button_set_title (GtkFileChooserButton *button,
919 g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
921 gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title);
922 g_object_notify (G_OBJECT (button), "title");
926 * gtk_file_chooser_button_get_title:
927 * @button: the button widget to examine.
929 * Retrieves the title of the browse dialog used by @button. The returned value
930 * should not be modified or freed.
932 * Returns: a pointer to the browse dialog's title.
936 G_CONST_RETURN gchar *
937 gtk_file_chooser_button_get_title (GtkFileChooserButton *button)
939 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), NULL);
941 return gtk_window_get_title (GTK_WINDOW (button->priv->dialog));
945 * gtk_file_chooser_button_set_active:
946 * @button: the button widget to modify.
947 * @is_active: whether or not the dialog is visible.
949 * Modifies whether or not the dialog attached to @button is visible or not.
954 gtk_file_chooser_button_set_active (GtkFileChooserButton *button,
957 g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
959 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button->priv->button), is_active);
963 * gtk_file_chooser_button_get_active:
964 * @button: the button widget to examine.
966 * Retrieves whether or not the dialog attached to @button is visible.
968 * Returns: a boolean whether the dialog is visible or not.
973 gtk_file_chooser_button_get_active (GtkFileChooserButton *button)
975 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), FALSE);
977 return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button->priv->button));
981 * gtk_file_chooser_button_get_width_chars:
982 * @button: the button widget to examine.
984 * Retrieves the width in characters of the @button widget's entry and/or label.
986 * Returns: an integer width (in characters) that the button will use to size itself.
991 gtk_file_chooser_button_get_width_chars (GtkFileChooserButton *button)
993 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), -1);
995 return gtk_entry_get_width_chars (GTK_ENTRY (button->priv->entry));
999 * gtk_file_chooser_button_set_width_chars:
1000 * @button: the button widget to examine.
1001 * @n_chars: the new width, in chracters.
1003 * Sets the width (in characters) that @button will use to @n_chars.
1008 gtk_file_chooser_button_set_width_chars (GtkFileChooserButton *button,
1011 g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
1013 gtk_entry_set_width_chars (GTK_ENTRY (button->priv->entry), n_chars);
1014 gtk_label_set_width_chars (GTK_LABEL (button->priv->label), n_chars);
1015 g_object_notify (G_OBJECT (button), "width-chars");
1019 /* ******************* *
1020 * Utility Functions *
1021 * ******************* */
1023 /* Removes the settings signal handler. It's safe to call multiple times */
1025 remove_settings_signal (GtkFileChooserButton *button,
1028 if (button->priv->settings_signal_id)
1030 GtkSettings *settings;
1032 settings = gtk_settings_get_for_screen (screen);
1033 g_signal_handler_disconnect (settings,
1034 button->priv->settings_signal_id);
1035 button->priv->settings_signal_id = 0;
1039 static GtkIconTheme *
1040 get_icon_theme (GtkWidget *widget)
1042 if (gtk_widget_has_screen (widget))
1043 return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
1045 return gtk_icon_theme_get_default ();
1049 check_if_path_exists (GtkFileSystem *fs,
1050 const GtkFilePath *path)
1052 gboolean path_exists;
1053 GtkFilePath *parent_path;
1055 path_exists = FALSE;
1058 if (gtk_file_system_get_parent (fs, path, &parent_path, NULL))
1060 GtkFileFolder *folder;
1062 folder = gtk_file_system_get_folder (fs, parent_path, 0, NULL);
1067 info = gtk_file_folder_get_info (folder, path, NULL);
1071 gtk_file_info_free (info);
1074 g_object_unref (folder);
1077 gtk_file_path_free (parent_path);
1084 update_icons (GtkFileChooserButton *button)
1086 GtkFileChooserButtonPrivate *priv;
1090 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1092 paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
1100 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1102 switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
1104 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1106 GtkFileSystemVolume *volume;
1108 volume = gtk_file_system_get_volume_for_path (fs, path);
1111 GtkFilePath *base_path;
1113 base_path = gtk_file_system_volume_get_base_path (fs, volume);
1115 if (base_path && gtk_file_path_compare (base_path, path) == 0)
1116 pixbuf = gtk_file_system_volume_render_icon (fs, volume,
1117 GTK_WIDGET (button),
1122 gtk_file_path_free (base_path);
1124 gtk_file_system_volume_free (fs, volume);
1128 case GTK_FILE_CHOOSER_ACTION_OPEN:
1130 pixbuf = gtk_file_system_render_icon (fs, path, GTK_WIDGET (button),
1131 priv->icon_size, NULL);
1134 case GTK_FILE_CHOOSER_ACTION_SAVE:
1135 if (check_if_path_exists (fs, path))
1136 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1137 GTK_STOCK_DIALOG_WARNING,
1138 priv->icon_size, 0, NULL);
1140 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1142 priv->icon_size, 0, NULL);
1144 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1145 if (check_if_path_exists (fs, path))
1146 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1147 GTK_STOCK_DIALOG_WARNING,
1148 priv->icon_size, 0, NULL);
1150 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1152 priv->icon_size, 0, NULL);
1156 gtk_file_paths_free (paths);
1160 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1162 priv->icon_size, 0, NULL);
1164 gtk_image_set_from_pixbuf (GTK_IMAGE (priv->entry_image), pixbuf);
1165 gtk_image_set_from_pixbuf (GTK_IMAGE (priv->label_image), pixbuf);
1168 g_object_unref (pixbuf);
1173 update_label (GtkFileChooserButton *button)
1175 GtkFileChooserButtonPrivate *priv;
1179 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1180 paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (button->priv->dialog));
1187 GtkFileSystemVolume *volume;
1191 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1193 volume = gtk_file_system_get_volume_for_path (fs, path);
1196 GtkFilePath *base_path;
1198 base_path = gtk_file_system_volume_get_base_path (fs, volume);
1199 if (base_path && gtk_file_path_compare (base_path, path) == 0)
1200 label_text = gtk_file_system_volume_get_display_name (fs, volume);
1203 gtk_file_path_free (base_path);
1205 gtk_file_system_volume_free (fs, volume);
1211 if (gtk_file_system_path_is_local (fs, path))
1217 filename = gtk_file_system_path_to_filename (fs, path);
1222 home = g_get_home_dir ();
1224 /* Munging for psuedo-volumes and files in the user's home tree */
1227 if (strcmp (filename, home) == 0)
1229 label_text = g_strdup (_("Home"));
1233 tmp = g_build_filename (home, "Desktop", NULL);
1235 if (strcmp (filename, tmp) == 0)
1236 label_text = g_strdup (_("Desktop"));
1243 if (g_str_has_prefix (filename, home))
1245 label_text = g_strconcat ("~", filename + strlen (home), NULL);
1251 label_text = g_strdup (filename);
1260 uri = gtk_file_system_path_to_uri (fs, path);
1267 gtk_file_paths_free (paths);
1272 gtk_label_set_text (GTK_LABEL (priv->label), label_text);
1273 g_free (label_text);
1276 gtk_label_set_text (GTK_LABEL (priv->label), _(DEFAULT_FILENAME));
1280 update_entry (GtkFileChooserButton *button)
1282 GtkFileChooserButtonPrivate *priv;
1286 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1288 paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
1296 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1298 if (gtk_file_system_path_is_local (fs, path))
1300 filename = gtk_file_system_path_to_filename (fs, path);
1307 if (g_file_test (filename, G_FILE_TEST_IS_DIR))
1309 tmp = g_strconcat (filename, "/", NULL);
1314 home = g_get_home_dir ();
1316 if (home && g_str_has_prefix (filename, home))
1318 tmp = g_strconcat ("~", filename + strlen (home), NULL);
1325 filename = gtk_file_system_path_to_uri (fs, path);
1334 entry_text = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
1337 gtk_entry_set_text (GTK_ENTRY (priv->entry), entry_text);
1338 g_free (entry_text);
1341 gtk_entry_set_text (GTK_ENTRY (priv->entry), "");
1345 update_dialog (GtkFileChooserButton *button)
1347 GtkFileChooserButtonPrivate *priv;
1348 GtkFilePath *current_folder;
1350 GtkFilePath *folder_part, *full_path;
1353 GtkFilePath *base_path;
1355 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1358 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1360 text = gtk_entry_get_text (GTK_ENTRY (priv->entry));
1362 base_path = gtk_file_path_new_dup ("/");
1363 gtk_file_system_parse (fs, base_path, text, &folder_part, &file_part, NULL);
1364 gtk_file_path_free (base_path);
1366 switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
1368 case GTK_FILE_CHOOSER_ACTION_OPEN:
1369 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
1372 GtkFileFolder *folder;
1375 folder = gtk_file_system_get_folder (fs, folder_part,
1376 GTK_FILE_INFO_IS_FOLDER, NULL);
1378 full_path = gtk_file_system_make_path (fs, folder_part, file_part, NULL);
1379 info = gtk_file_folder_get_info (folder, full_path, NULL);
1381 /* Entry contents don't exist. */
1383 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1385 /* Entry contents are a folder */
1386 else if (gtk_file_info_get_is_folder (info))
1387 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1389 /* Entry contents must be a file. */
1391 _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
1395 gtk_file_info_free (info);
1397 gtk_file_path_free (full_path);
1400 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1401 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
1404 full_path = gtk_file_system_make_path (fs, folder_part, file_part, NULL);
1406 /* Entry contents don't exist. */
1408 _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
1411 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1414 gtk_file_path_free (full_path);
1418 case GTK_FILE_CHOOSER_ACTION_SAVE:
1419 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1420 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
1423 current_folder = _gtk_file_chooser_get_current_folder_path (GTK_FILE_CHOOSER (priv->dialog));
1425 if (!current_folder ||
1426 gtk_file_path_compare (current_folder, folder_part) != 0)
1428 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1430 g_signal_emit_by_name (button, "current-folder-changed");
1434 gtk_file_path_free (current_folder);
1437 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (priv->dialog),
1439 g_signal_emit_by_name (button, "selection-changed");
1444 /* ************************ *
1445 * Child-Widget Callbacks *
1446 * ************************ */
1449 dialog_current_folder_changed_cb (GtkFileChooser *dialog,
1452 g_signal_emit_by_name (user_data, "current-folder-changed");
1456 dialog_file_activated_cb (GtkFileChooser *dialog,
1459 g_signal_emit_by_name (user_data, "file-activated");
1463 dialog_selection_changed_cb (GtkFileChooser *dialog,
1466 GtkFileChooserButtonPrivate *priv;
1468 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1470 g_signal_handler_block (priv->entry, priv->entry_changed_id);
1471 update_entry (user_data);
1472 g_signal_handler_unblock (priv->entry, priv->entry_changed_id);
1473 update_icons (user_data);
1474 update_label (user_data);
1478 dialog_selection_changed_proxy_cb (GtkFileChooser *dialog,
1481 GtkFileChooserButtonPrivate *priv;
1483 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1485 g_signal_emit_by_name (user_data, "selection-changed");
1489 dialog_update_preview_cb (GtkFileChooser *dialog,
1492 g_signal_emit_by_name (user_data, "update-preview");
1496 dialog_notify_cb (GObject *dialog,
1502 iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (dialog)),
1503 GTK_TYPE_FILE_CHOOSER);
1504 if (g_object_interface_find_property (iface, pspec->name))
1505 g_object_notify (user_data, pspec->name);
1509 dialog_delete_event_cb (GtkWidget *dialog,
1513 g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_DELETE_EVENT);
1519 dialog_response_cb (GtkFileChooser *dialog,
1523 GtkFileChooserButtonPrivate *priv;
1525 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1527 if (response == GTK_RESPONSE_ACCEPT)
1529 g_signal_handler_block (priv->entry, priv->entry_changed_id);
1530 update_entry (user_data);
1531 g_signal_handler_unblock (priv->entry, priv->entry_changed_id);
1532 update_label (user_data);
1533 update_icons (user_data);
1535 g_signal_emit_by_name (user_data, "current-folder-changed");
1536 g_signal_emit_by_name (user_data, "selection-changed");
1540 update_dialog (user_data);
1543 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE);
1548 button_toggled_cb (GtkToggleButton *real_button,
1551 GtkFileChooserButtonPrivate *priv;
1553 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1555 if (gtk_toggle_button_get_active (real_button))
1557 /* Setup the dialog parent to be chooser button's toplevel, and be modal
1559 if (!GTK_WIDGET_VISIBLE (priv->dialog))
1561 GtkWidget *toplevel;
1563 toplevel = gtk_widget_get_toplevel (user_data);
1565 if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_IS_WINDOW (toplevel))
1567 if (GTK_WINDOW (toplevel) != gtk_window_get_transient_for (GTK_WINDOW (priv->dialog)))
1568 gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), GTK_WINDOW (toplevel));
1570 gtk_window_set_modal (GTK_WINDOW (priv->dialog),
1571 gtk_window_get_modal (GTK_WINDOW (toplevel)));
1575 g_signal_handler_block (priv->dialog,
1576 priv->dialog_folder_changed_id);
1577 g_signal_handler_block (priv->dialog,
1578 priv->dialog_file_activated_id);
1579 g_signal_handler_block (priv->dialog,
1580 priv->dialog_selection_changed_proxy_id);
1581 gtk_widget_set_sensitive (priv->entry, FALSE);
1582 gtk_window_present (GTK_WINDOW (priv->dialog));
1586 g_signal_handler_unblock (priv->dialog,
1587 priv->dialog_folder_changed_id);
1588 g_signal_handler_unblock (priv->dialog,
1589 priv->dialog_file_activated_id);
1590 g_signal_handler_unblock (priv->dialog,
1591 priv->dialog_selection_changed_proxy_id);
1592 gtk_widget_set_sensitive (priv->entry, TRUE);
1593 gtk_widget_hide (priv->dialog);
1598 button_notify_active_cb (GObject *real_button,
1602 g_object_notify (user_data, "active");
1607 update_idler (gpointer user_data)
1609 GtkFileChooserButtonPrivate *priv;
1612 GDK_THREADS_ENTER ();
1614 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1616 if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (priv->entry),
1619 g_signal_handler_block (priv->dialog,
1620 priv->dialog_selection_changed_id);
1621 update_dialog (user_data);
1622 g_signal_handler_unblock (priv->dialog,
1623 priv->dialog_selection_changed_id);
1624 update_icons (user_data);
1625 update_label (user_data);
1626 priv->update_id = 0;
1632 GDK_THREADS_LEAVE ();
1638 entry_changed_cb (GtkEditable *editable,
1641 GtkFileChooserButtonPrivate *priv;
1643 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1645 if (priv->update_id)
1646 g_source_remove (priv->update_id);
1648 priv->update_id = g_idle_add_full (G_PRIORITY_LOW, update_idler,
1652 /* Ensure the button height == entry height */
1654 entry_size_allocate_cb (GtkWidget *entry,
1655 GtkAllocation *allocation,
1658 gtk_widget_set_size_request (user_data, -1, allocation->height);