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;
243 * GtkFileChooserButton:dialog:
245 * Instance of the #GtkFileChooserDialog associated with the button.
249 g_object_class_install_property (gobject_class, PROP_DIALOG,
250 g_param_spec_object ("dialog",
252 P_("The file chooser dialog to use."),
253 GTK_TYPE_FILE_CHOOSER_DIALOG,
255 G_PARAM_CONSTRUCT_ONLY)));
258 * GtkFileChooserButton:title:
260 * Title to put on the #GtkFileChooserDialog associated with the button.
264 g_object_class_install_property (gobject_class, PROP_TITLE,
265 g_param_spec_string ("title",
267 P_("The title of the file chooser dialog."),
272 * GtkFileChooserButton:active:
274 * %TRUE, if the #GtkFileChooserDialog associated with the button has been
275 * made visible. This can also be set by the application, though it is
276 * rarely useful to do so.
280 g_object_class_install_property (gobject_class, PROP_ACTIVE,
281 g_param_spec_boolean ("active",
283 P_("Whether the browse dialog is visible or not."),
284 FALSE, G_PARAM_READWRITE));
287 * GtkFileChooserButton:
289 * The width of the entry and label inside the button, in characters.
293 g_object_class_install_property (gobject_class, PROP_WIDTH_CHARS,
294 g_param_spec_int ("width-chars",
295 P_("Width In Characters"),
296 P_("The desired width of the button widget, in characters."),
300 _gtk_file_chooser_install_properties (gobject_class);
302 g_type_class_add_private (class, sizeof (GtkFileChooserButtonPrivate));
307 gtk_file_chooser_button_init (GtkFileChooserButton *button)
309 GtkFileChooserButtonPrivate *priv;
310 GtkWidget *box, *image, *sep;
312 gtk_box_set_spacing (GTK_BOX (button), ENTRY_BUTTON_SPACING);
314 priv = G_TYPE_INSTANCE_GET_PRIVATE (button, GTK_TYPE_FILE_CHOOSER_BUTTON,
315 GtkFileChooserButtonPrivate);
318 priv->icon_size = FALLBACK_ICON_SIZE;
320 gtk_widget_push_composite_child ();
322 priv->entry_box = gtk_hbox_new (FALSE, 4);
323 gtk_container_add (GTK_CONTAINER (button), priv->entry_box);
325 priv->entry_image = gtk_image_new ();
326 gtk_box_pack_start (GTK_BOX (priv->entry_box), priv->entry_image,
328 gtk_widget_show (priv->entry_image);
330 priv->entry = _gtk_file_chooser_entry_new (FALSE);
331 gtk_container_add (GTK_CONTAINER (priv->entry_box), priv->entry);
332 gtk_widget_show (priv->entry);
334 priv->button = gtk_toggle_button_new ();
335 g_signal_connect (priv->button, "toggled",
336 G_CALLBACK (button_toggled_cb), button);
337 g_signal_connect (priv->button, "notify::active",
338 G_CALLBACK (button_notify_active_cb), button);
339 g_signal_connect (priv->entry, "size-allocate",
340 G_CALLBACK (entry_size_allocate_cb), priv->button);
341 gtk_box_pack_start (GTK_BOX (button), priv->button, TRUE, TRUE, 0);
342 gtk_widget_show (priv->button);
344 box = gtk_hbox_new (FALSE, 4);
345 gtk_container_add (GTK_CONTAINER (priv->button), box);
346 gtk_widget_show (box);
348 priv->label_box = gtk_hbox_new (FALSE, 6);
349 gtk_container_add (GTK_CONTAINER (box), priv->label_box);
350 gtk_widget_show (priv->label_box);
352 priv->label_image = gtk_image_new ();
353 gtk_box_pack_start (GTK_BOX (priv->label_box), priv->label_image,
355 gtk_widget_show (priv->label_image);
357 priv->label = gtk_label_new (_(DEFAULT_FILENAME));
358 gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_START);
359 gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
360 gtk_container_add (GTK_CONTAINER (priv->label_box), priv->label);
361 gtk_widget_show (priv->label);
363 sep = gtk_vseparator_new ();
364 gtk_box_pack_end (GTK_BOX (priv->label_box), sep, FALSE, FALSE, 0);
365 gtk_widget_show (sep);
367 image = gtk_image_new_from_stock (GTK_STOCK_OPEN,
368 GTK_ICON_SIZE_SMALL_TOOLBAR);
369 gtk_box_pack_end (GTK_BOX (box), image, FALSE, FALSE, 0);
370 gtk_widget_show (image);
372 gtk_widget_pop_composite_child ();
375 gtk_drag_dest_unset (priv->entry);
376 gtk_drag_dest_set (GTK_WIDGET (button),
377 (GTK_DEST_DEFAULT_ALL),
380 gtk_drag_dest_add_text_targets (GTK_WIDGET (button));
381 gtk_target_list_add_uri_targets (gtk_drag_dest_get_target_list (GTK_WIDGET (button)),
386 /* ******************* *
387 * GObject Functions *
388 * ******************* */
391 gtk_file_chooser_button_constructor (GType type,
393 GObjectConstructParam *params)
396 GtkFileChooserButtonPrivate *priv;
399 object = (*G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructor) (type,
402 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
407 priv->dialog = gtk_file_chooser_dialog_new_with_backend (NULL, NULL,
408 GTK_FILE_CHOOSER_ACTION_OPEN,
409 priv->backend, NULL);
411 priv->dialog = gtk_file_chooser_dialog_new (NULL, NULL,
412 GTK_FILE_CHOOSER_ACTION_OPEN,
415 gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
416 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
417 priv->accept_button = gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
419 GTK_RESPONSE_ACCEPT);
420 gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog),
421 GTK_RESPONSE_ACCEPT);
423 gtk_dialog_set_alternative_button_order (GTK_DIALOG (priv->dialog),
429 g_free (priv->backend);
430 priv->backend = NULL;
432 g_signal_connect (priv->dialog, "delete-event",
433 G_CALLBACK (dialog_delete_event_cb), object);
434 g_signal_connect (priv->dialog, "response",
435 G_CALLBACK (dialog_response_cb), object);
437 /* This is used, instead of the standard delegate, to ensure that signals are only
438 * delegated when the OK button is pressed. */
439 g_object_set_qdata (object, GTK_FILE_CHOOSER_DELEGATE_QUARK, priv->dialog);
440 priv->dialog_folder_changed_id =
441 g_signal_connect (priv->dialog, "current-folder-changed",
442 G_CALLBACK (dialog_current_folder_changed_cb), object);
443 priv->dialog_file_activated_id =
444 g_signal_connect (priv->dialog, "file-activated",
445 G_CALLBACK (dialog_file_activated_cb), object);
446 priv->dialog_selection_changed_id =
447 g_signal_connect (priv->dialog, "selection-changed",
448 G_CALLBACK (dialog_selection_changed_cb), object);
449 priv->dialog_selection_changed_proxy_id =
450 g_signal_connect (priv->dialog, "selection-changed",
451 G_CALLBACK (dialog_selection_changed_proxy_cb), object);
452 g_signal_connect (priv->dialog, "update-preview",
453 G_CALLBACK (dialog_update_preview_cb), object);
454 g_signal_connect (priv->dialog, "notify",
455 G_CALLBACK (dialog_notify_cb), object);
456 g_object_add_weak_pointer (G_OBJECT (priv->dialog),
457 (gpointer *) (&priv->dialog));
459 _gtk_file_chooser_entry_set_file_system (GTK_FILE_CHOOSER_ENTRY (priv->entry),
460 _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)));
461 path = gtk_file_path_new_steal ("/");
462 _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->entry),
464 priv->entry_changed_id = g_signal_connect (priv->entry, "changed",
465 G_CALLBACK (entry_changed_cb),
468 update_label (GTK_FILE_CHOOSER_BUTTON (object));
474 gtk_file_chooser_button_set_property (GObject *object,
479 GtkFileChooserButtonPrivate *priv;
481 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
487 priv->dialog = g_value_get_object (value);
490 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button),
491 g_value_get_boolean (value));
493 case PROP_WIDTH_CHARS:
494 gtk_file_chooser_button_set_width_chars (GTK_FILE_CHOOSER_BUTTON (object),
495 g_value_get_int (value));
498 case GTK_FILE_CHOOSER_PROP_ACTION:
499 g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
500 _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (priv->entry),
501 (GtkFileChooserAction) g_value_get_enum (value));
502 update_icons (GTK_FILE_CHOOSER_BUTTON (object));
504 switch (g_value_get_enum (value))
506 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
507 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
508 /* Fall through to set the widget states */
509 case GTK_FILE_CHOOSER_ACTION_OPEN:
510 gtk_widget_hide (priv->entry_box);
511 gtk_widget_show (priv->label_box);
512 gtk_box_set_child_packing (GTK_BOX (object), priv->button,
513 TRUE, TRUE, 0, GTK_PACK_START);
514 gtk_button_set_label (GTK_BUTTON (priv->accept_button),
518 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
519 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
520 /* Fall through to set the widget states */
521 case GTK_FILE_CHOOSER_ACTION_SAVE:
522 gtk_widget_show (priv->entry_box);
523 gtk_widget_hide (priv->label_box);
524 gtk_box_set_child_packing (GTK_BOX (object), priv->button,
525 FALSE, FALSE, 0, GTK_PACK_START);
526 gtk_button_set_label (GTK_BUTTON (priv->accept_button),
533 case GTK_FILE_CHOOSER_PROP_FILTER:
534 case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
535 case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
536 case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
537 case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
538 case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
539 case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
540 g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
543 case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
545 priv->backend = g_value_dup_string (value);
548 case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
549 g_warning ("%s: Choosers of type `%s` do not support selecting multiple files.",
550 G_STRFUNC, G_OBJECT_TYPE_NAME (object));
553 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
559 gtk_file_chooser_button_get_property (GObject *object,
564 GtkFileChooserButtonPrivate *priv;
566 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
571 g_value_set_boolean (value,
572 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button)));
574 case PROP_WIDTH_CHARS:
575 g_value_set_int (value,
576 gtk_entry_get_width_chars (GTK_ENTRY (priv->entry)));
580 case GTK_FILE_CHOOSER_PROP_ACTION:
581 case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
582 case GTK_FILE_CHOOSER_PROP_FILTER:
583 case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
584 case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
585 case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
586 case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
587 case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
588 case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
589 case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
590 g_object_get_property (G_OBJECT (priv->dialog), pspec->name, value);
594 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
600 /* ********************* *
601 * GtkObject Functions *
602 * ********************* */
605 gtk_file_chooser_button_destroy (GtkObject * object)
607 GtkFileChooserButtonPrivate *priv;
609 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
612 g_source_remove (priv->update_id);
614 if (priv->dialog != NULL)
615 gtk_widget_destroy (priv->dialog);
617 remove_settings_signal (GTK_FILE_CHOOSER_BUTTON (object),
618 gtk_widget_get_screen (GTK_WIDGET (object)));
620 if (GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy != NULL)
621 (*GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy) (object);
625 /* ********************* *
626 * GtkWidget Functions *
627 * ********************* */
630 gtk_file_chooser_button_drag_data_received (GtkWidget *widget,
631 GdkDragContext *context,
634 GtkSelectionData *data,
638 GtkFileChooserButtonPrivate *priv;
641 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received != NULL)
642 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received) (widget,
648 if (widget == NULL || context == NULL || data == NULL || data->length < 0)
651 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
661 uris = gtk_selection_data_get_uris (data);
667 for (i = 0; !selected && uris[i] != NULL; i++)
670 GtkFilePath *path, *base_path;
672 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
673 path = gtk_file_system_uri_to_path (fs, uris[i]);
677 gtk_file_system_get_parent (fs, path, &base_path, NULL))
679 GtkFileFolder *folder;
682 folder = gtk_file_system_get_folder (fs, base_path,
683 GTK_FILE_INFO_IS_FOLDER,
686 info = gtk_file_folder_get_info (folder, path, NULL);
690 GtkFileChooserAction action;
692 g_object_get (priv->dialog, "action", &action, NULL);
695 ((((action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER ||
696 action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) &&
697 gtk_file_info_get_is_folder (info)) ||
698 ((action == GTK_FILE_CHOOSER_ACTION_OPEN ||
699 action == GTK_FILE_CHOOSER_ACTION_SAVE) &&
700 !gtk_file_info_get_is_folder (info))) &&
701 _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
704 gtk_file_info_free (info);
709 gtk_file_path_free (base_path);
712 gtk_file_path_free (path);
720 text = gtk_selection_data_get_text (data);
721 gtk_entry_set_text (GTK_ENTRY (priv->entry), text);
726 gtk_drag_finish (context, TRUE, FALSE, drag_time);
730 gtk_file_chooser_button_show_all (GtkWidget *widget)
732 gtk_widget_show (widget);
736 gtk_file_chooser_button_hide_all (GtkWidget *widget)
738 gtk_widget_hide (widget);
742 gtk_file_chooser_button_show (GtkWidget *widget)
744 GtkFileChooserButtonPrivate *priv;
746 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
748 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show)
749 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show) (widget);
751 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button)))
752 gtk_widget_show (priv->dialog);
756 gtk_file_chooser_button_hide (GtkWidget *widget)
758 GtkFileChooserButtonPrivate *priv;
760 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
762 gtk_widget_hide (priv->dialog);
764 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide)
765 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide) (widget);
769 gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget,
770 gboolean group_cycling)
772 GtkFileChooserButtonPrivate *priv;
774 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
776 switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
778 case GTK_FILE_CHOOSER_ACTION_OPEN:
779 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
780 gtk_widget_grab_focus (priv->button);
782 case GTK_FILE_CHOOSER_ACTION_SAVE:
783 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
784 gtk_widget_grab_focus (priv->entry);
791 /* Changes the icons wherever it is needed */
793 change_icon_theme (GtkFileChooserButton *button)
795 GtkSettings *settings;
798 settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
800 if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_SMALL_TOOLBAR,
802 button->priv->icon_size = MAX (width, height);
804 button->priv->icon_size = FALLBACK_ICON_SIZE;
806 update_icons (button);
809 /* Callback used when a GtkSettings value changes */
811 settings_notify_cb (GObject *object,
817 name = g_param_spec_get_name (pspec);
819 if (strcmp (name, "gtk-icon-theme-name") == 0
820 || strcmp (name, "gtk-icon-sizes") == 0)
821 change_icon_theme (user_data);
824 /* Installs a signal handler for GtkSettings so that we can monitor changes in
828 check_icon_theme (GtkFileChooserButton *button)
830 GtkSettings *settings;
832 if (button->priv->settings_signal_id)
835 if (gtk_widget_has_screen (GTK_WIDGET (button)))
837 settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
838 button->priv->settings_signal_id = g_signal_connect (settings, "notify",
839 G_CALLBACK (settings_notify_cb),
842 change_icon_theme (button);
847 gtk_file_chooser_button_style_set (GtkWidget *widget,
850 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set)
851 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set) (widget,
854 if (gtk_widget_has_screen (widget))
855 change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
859 gtk_file_chooser_button_screen_changed (GtkWidget *widget,
860 GdkScreen *old_screen)
862 GtkFileChooserButton *button;
864 button = GTK_FILE_CHOOSER_BUTTON (widget);
866 if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed)
867 (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed) (widget,
870 remove_settings_signal (button, old_screen);
871 check_icon_theme (button);
875 /* ************************************************************************** *
877 * ************************************************************************** */
880 * gtk_file_chooser_button_new:
881 * @title: the title of the browse dialog.
883 * Creates a new file-selecting button widget.
885 * Returns: a new button widget.
890 gtk_file_chooser_button_new (const gchar *title)
892 return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
898 * gtk_file_chooser_button_new_with_backend:
899 * @title: the title of the browse dialog.
900 * @backend: the name of the #GtkFileSystem backend to use.
902 * Creates a new file-selecting button widget using @backend.
904 * Returns: a new button widget.
909 gtk_file_chooser_button_new_with_backend (const gchar *title,
910 const gchar *backend)
912 return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
914 "file-system-backend", backend,
919 * gtk_file_chooser_button_new_with_dialog:
920 * @dialog: the #GtkDialog widget to use.
922 * Creates a #GtkFileChooserButton widget which uses @dialog as it's
923 * file-picking window. Note that @dialog must be a #GtkFileChooserDialog (or
926 * Returns: a new button widget.
931 gtk_file_chooser_button_new_with_dialog (GtkWidget *dialog)
933 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_DIALOG (dialog), NULL);
935 return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
941 * gtk_file_chooser_button_set_title:
942 * @button: the button widget to modify.
943 * @title: the new browse dialog title.
945 * Modifies the @title of the browse dialog used by @button.
950 gtk_file_chooser_button_set_title (GtkFileChooserButton *button,
953 g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
955 gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title);
956 g_object_notify (G_OBJECT (button), "title");
960 * gtk_file_chooser_button_get_title:
961 * @button: the button widget to examine.
963 * Retrieves the title of the browse dialog used by @button. The returned value
964 * should not be modified or freed.
966 * Returns: a pointer to the browse dialog's title.
970 G_CONST_RETURN gchar *
971 gtk_file_chooser_button_get_title (GtkFileChooserButton *button)
973 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), NULL);
975 return gtk_window_get_title (GTK_WINDOW (button->priv->dialog));
979 * gtk_file_chooser_button_set_active:
980 * @button: the button widget to modify.
981 * @is_active: whether or not the dialog is visible.
983 * Modifies whether or not the dialog attached to @button is visible or not.
988 gtk_file_chooser_button_set_active (GtkFileChooserButton *button,
991 g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
993 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button->priv->button), is_active);
997 * gtk_file_chooser_button_get_active:
998 * @button: the button widget to examine.
1000 * Retrieves whether or not the dialog attached to @button is visible.
1002 * Returns: a boolean whether the dialog is visible or not.
1007 gtk_file_chooser_button_get_active (GtkFileChooserButton *button)
1009 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), FALSE);
1011 return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button->priv->button));
1015 * gtk_file_chooser_button_get_width_chars:
1016 * @button: the button widget to examine.
1018 * Retrieves the width in characters of the @button widget's entry and/or label.
1020 * Returns: an integer width (in characters) that the button will use to size itself.
1025 gtk_file_chooser_button_get_width_chars (GtkFileChooserButton *button)
1027 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), -1);
1029 return gtk_entry_get_width_chars (GTK_ENTRY (button->priv->entry));
1033 * gtk_file_chooser_button_set_width_chars:
1034 * @button: the button widget to examine.
1035 * @n_chars: the new width, in chracters.
1037 * Sets the width (in characters) that @button will use to @n_chars.
1042 gtk_file_chooser_button_set_width_chars (GtkFileChooserButton *button,
1045 g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
1047 gtk_entry_set_width_chars (GTK_ENTRY (button->priv->entry), n_chars);
1048 gtk_label_set_width_chars (GTK_LABEL (button->priv->label), n_chars);
1049 g_object_notify (G_OBJECT (button), "width-chars");
1053 /* ******************* *
1054 * Utility Functions *
1055 * ******************* */
1057 /* Removes the settings signal handler. It's safe to call multiple times */
1059 remove_settings_signal (GtkFileChooserButton *button,
1062 if (button->priv->settings_signal_id)
1064 GtkSettings *settings;
1066 settings = gtk_settings_get_for_screen (screen);
1067 g_signal_handler_disconnect (settings,
1068 button->priv->settings_signal_id);
1069 button->priv->settings_signal_id = 0;
1073 static GtkIconTheme *
1074 get_icon_theme (GtkWidget *widget)
1076 if (gtk_widget_has_screen (widget))
1077 return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
1079 return gtk_icon_theme_get_default ();
1083 check_if_path_exists (GtkFileSystem *fs,
1084 const GtkFilePath *path)
1086 gboolean path_exists;
1087 GtkFilePath *parent_path;
1089 path_exists = FALSE;
1092 if (gtk_file_system_get_parent (fs, path, &parent_path, NULL))
1094 GtkFileFolder *folder;
1096 folder = gtk_file_system_get_folder (fs, parent_path, 0, NULL);
1101 info = gtk_file_folder_get_info (folder, path, NULL);
1105 gtk_file_info_free (info);
1108 g_object_unref (folder);
1111 gtk_file_path_free (parent_path);
1118 update_icons (GtkFileChooserButton *button)
1120 GtkFileChooserButtonPrivate *priv;
1124 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1126 paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
1134 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1136 switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
1138 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1140 GtkFileSystemVolume *volume;
1142 volume = gtk_file_system_get_volume_for_path (fs, path);
1145 GtkFilePath *base_path;
1147 base_path = gtk_file_system_volume_get_base_path (fs, volume);
1149 if (base_path && gtk_file_path_compare (base_path, path) == 0)
1150 pixbuf = gtk_file_system_volume_render_icon (fs, volume,
1151 GTK_WIDGET (button),
1156 gtk_file_path_free (base_path);
1158 gtk_file_system_volume_free (fs, volume);
1162 case GTK_FILE_CHOOSER_ACTION_OPEN:
1164 pixbuf = gtk_file_system_render_icon (fs, path, GTK_WIDGET (button),
1165 priv->icon_size, NULL);
1168 case GTK_FILE_CHOOSER_ACTION_SAVE:
1169 if (check_if_path_exists (fs, path))
1170 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1171 GTK_STOCK_DIALOG_WARNING,
1172 priv->icon_size, 0, NULL);
1174 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1176 priv->icon_size, 0, NULL);
1178 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1179 if (check_if_path_exists (fs, path))
1180 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1181 GTK_STOCK_DIALOG_WARNING,
1182 priv->icon_size, 0, NULL);
1184 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1186 priv->icon_size, 0, NULL);
1190 gtk_file_paths_free (paths);
1194 pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
1196 priv->icon_size, 0, NULL);
1198 gtk_image_set_from_pixbuf (GTK_IMAGE (priv->entry_image), pixbuf);
1199 gtk_image_set_from_pixbuf (GTK_IMAGE (priv->label_image), pixbuf);
1202 g_object_unref (pixbuf);
1207 update_label (GtkFileChooserButton *button)
1209 GtkFileChooserButtonPrivate *priv;
1213 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1214 paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (button->priv->dialog));
1221 GtkFileSystemVolume *volume;
1225 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1227 volume = gtk_file_system_get_volume_for_path (fs, path);
1230 GtkFilePath *base_path;
1232 base_path = gtk_file_system_volume_get_base_path (fs, volume);
1233 if (base_path && gtk_file_path_compare (base_path, path) == 0)
1234 label_text = gtk_file_system_volume_get_display_name (fs, volume);
1237 gtk_file_path_free (base_path);
1239 gtk_file_system_volume_free (fs, volume);
1245 if (gtk_file_system_path_is_local (fs, path))
1251 filename = gtk_file_system_path_to_filename (fs, path);
1256 home = g_get_home_dir ();
1258 /* Munging for psuedo-volumes and files in the user's home tree */
1261 if (strcmp (filename, home) == 0)
1263 label_text = g_strdup (_("Home"));
1267 tmp = g_build_filename (home, "Desktop", NULL);
1269 if (strcmp (filename, tmp) == 0)
1270 label_text = g_strdup (_("Desktop"));
1277 if (g_str_has_prefix (filename, home))
1279 label_text = g_strconcat ("~", filename + strlen (home), NULL);
1285 label_text = g_strdup (filename);
1294 uri = gtk_file_system_path_to_uri (fs, path);
1301 gtk_file_paths_free (paths);
1306 gtk_label_set_text (GTK_LABEL (priv->label), label_text);
1307 g_free (label_text);
1310 gtk_label_set_text (GTK_LABEL (priv->label), _(DEFAULT_FILENAME));
1314 update_entry (GtkFileChooserButton *button)
1316 GtkFileChooserButtonPrivate *priv;
1320 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1322 paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
1330 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1332 if (gtk_file_system_path_is_local (fs, path))
1334 filename = gtk_file_system_path_to_filename (fs, path);
1341 if (g_file_test (filename, G_FILE_TEST_IS_DIR))
1343 tmp = g_strconcat (filename, "/", NULL);
1348 home = g_get_home_dir ();
1350 if (home && g_str_has_prefix (filename, home))
1352 tmp = g_strconcat ("~", filename + strlen (home), NULL);
1359 filename = gtk_file_system_path_to_uri (fs, path);
1368 entry_text = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
1371 gtk_entry_set_text (GTK_ENTRY (priv->entry), entry_text);
1372 g_free (entry_text);
1375 gtk_entry_set_text (GTK_ENTRY (priv->entry), "");
1379 update_dialog (GtkFileChooserButton *button)
1381 GtkFileChooserButtonPrivate *priv;
1382 GtkFilePath *current_folder;
1384 GtkFilePath *folder_part, *full_path;
1387 GtkFilePath *base_path;
1389 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
1392 fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
1394 text = gtk_entry_get_text (GTK_ENTRY (priv->entry));
1396 base_path = gtk_file_path_new_dup ("/");
1397 gtk_file_system_parse (fs, base_path, text, &folder_part, &file_part, NULL);
1398 gtk_file_path_free (base_path);
1400 switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
1402 case GTK_FILE_CHOOSER_ACTION_OPEN:
1403 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
1406 GtkFileFolder *folder;
1409 folder = gtk_file_system_get_folder (fs, folder_part,
1410 GTK_FILE_INFO_IS_FOLDER, NULL);
1412 full_path = gtk_file_system_make_path (fs, folder_part, file_part, NULL);
1413 info = gtk_file_folder_get_info (folder, full_path, NULL);
1415 /* Entry contents don't exist. */
1417 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1419 /* Entry contents are a folder */
1420 else if (gtk_file_info_get_is_folder (info))
1421 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1423 /* Entry contents must be a file. */
1425 _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
1429 gtk_file_info_free (info);
1431 gtk_file_path_free (full_path);
1434 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1435 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
1438 full_path = gtk_file_system_make_path (fs, folder_part, file_part, NULL);
1440 /* Entry contents don't exist. */
1442 _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
1445 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1448 gtk_file_path_free (full_path);
1452 case GTK_FILE_CHOOSER_ACTION_SAVE:
1453 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1454 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
1457 current_folder = _gtk_file_chooser_get_current_folder_path (GTK_FILE_CHOOSER (priv->dialog));
1459 if (!current_folder ||
1460 gtk_file_path_compare (current_folder, folder_part) != 0)
1462 _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
1464 g_signal_emit_by_name (button, "current-folder-changed");
1468 gtk_file_path_free (current_folder);
1471 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (priv->dialog),
1473 g_signal_emit_by_name (button, "selection-changed");
1478 /* ************************ *
1479 * Child-Widget Callbacks *
1480 * ************************ */
1483 dialog_current_folder_changed_cb (GtkFileChooser *dialog,
1486 g_signal_emit_by_name (user_data, "current-folder-changed");
1490 dialog_file_activated_cb (GtkFileChooser *dialog,
1493 g_signal_emit_by_name (user_data, "file-activated");
1497 dialog_selection_changed_cb (GtkFileChooser *dialog,
1500 GtkFileChooserButtonPrivate *priv;
1502 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1504 g_signal_handler_block (priv->entry, priv->entry_changed_id);
1505 update_entry (user_data);
1506 g_signal_handler_unblock (priv->entry, priv->entry_changed_id);
1507 update_icons (user_data);
1508 update_label (user_data);
1512 dialog_selection_changed_proxy_cb (GtkFileChooser *dialog,
1515 GtkFileChooserButtonPrivate *priv;
1517 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1519 g_signal_emit_by_name (user_data, "selection-changed");
1523 dialog_update_preview_cb (GtkFileChooser *dialog,
1526 g_signal_emit_by_name (user_data, "update-preview");
1530 dialog_notify_cb (GObject *dialog,
1536 iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (dialog)),
1537 GTK_TYPE_FILE_CHOOSER);
1538 if (g_object_interface_find_property (iface, pspec->name))
1539 g_object_notify (user_data, pspec->name);
1543 dialog_delete_event_cb (GtkWidget *dialog,
1547 g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_DELETE_EVENT);
1553 dialog_response_cb (GtkFileChooser *dialog,
1557 GtkFileChooserButtonPrivate *priv;
1559 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1561 if (response == GTK_RESPONSE_ACCEPT)
1563 g_signal_handler_block (priv->entry, priv->entry_changed_id);
1564 update_entry (user_data);
1565 g_signal_handler_unblock (priv->entry, priv->entry_changed_id);
1566 update_label (user_data);
1567 update_icons (user_data);
1569 g_signal_emit_by_name (user_data, "current-folder-changed");
1570 g_signal_emit_by_name (user_data, "selection-changed");
1574 update_dialog (user_data);
1577 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE);
1582 button_toggled_cb (GtkToggleButton *real_button,
1585 GtkFileChooserButtonPrivate *priv;
1587 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1589 if (gtk_toggle_button_get_active (real_button))
1591 /* Setup the dialog parent to be chooser button's toplevel, and be modal
1593 if (!GTK_WIDGET_VISIBLE (priv->dialog))
1595 GtkWidget *toplevel;
1597 toplevel = gtk_widget_get_toplevel (user_data);
1599 if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_IS_WINDOW (toplevel))
1601 if (GTK_WINDOW (toplevel) != gtk_window_get_transient_for (GTK_WINDOW (priv->dialog)))
1602 gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), GTK_WINDOW (toplevel));
1604 gtk_window_set_modal (GTK_WINDOW (priv->dialog),
1605 gtk_window_get_modal (GTK_WINDOW (toplevel)));
1609 g_signal_handler_block (priv->dialog,
1610 priv->dialog_folder_changed_id);
1611 g_signal_handler_block (priv->dialog,
1612 priv->dialog_file_activated_id);
1613 g_signal_handler_block (priv->dialog,
1614 priv->dialog_selection_changed_proxy_id);
1615 gtk_widget_set_sensitive (priv->entry, FALSE);
1616 gtk_window_present (GTK_WINDOW (priv->dialog));
1620 g_signal_handler_unblock (priv->dialog,
1621 priv->dialog_folder_changed_id);
1622 g_signal_handler_unblock (priv->dialog,
1623 priv->dialog_file_activated_id);
1624 g_signal_handler_unblock (priv->dialog,
1625 priv->dialog_selection_changed_proxy_id);
1626 gtk_widget_set_sensitive (priv->entry, TRUE);
1627 gtk_widget_hide (priv->dialog);
1632 button_notify_active_cb (GObject *real_button,
1636 g_object_notify (user_data, "active");
1641 update_idler (gpointer user_data)
1643 GtkFileChooserButtonPrivate *priv;
1646 GDK_THREADS_ENTER ();
1648 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1650 if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (priv->entry),
1653 g_signal_handler_block (priv->dialog,
1654 priv->dialog_selection_changed_id);
1655 update_dialog (user_data);
1656 g_signal_handler_unblock (priv->dialog,
1657 priv->dialog_selection_changed_id);
1658 update_icons (user_data);
1659 update_label (user_data);
1660 priv->update_id = 0;
1666 GDK_THREADS_LEAVE ();
1672 entry_changed_cb (GtkEditable *editable,
1675 GtkFileChooserButtonPrivate *priv;
1677 priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1679 if (priv->update_id)
1680 g_source_remove (priv->update_id);
1682 priv->update_id = g_idle_add_full (G_PRIORITY_LOW, update_idler,
1686 /* Ensure the button height == entry height */
1688 entry_size_allocate_cb (GtkWidget *entry,
1689 GtkAllocation *allocation,
1692 gtk_widget_set_size_request (user_data, -1, allocation->height);