From b418bf3aa4428c0c2189d846d64cd77ca8205841 Mon Sep 17 00:00:00 2001 From: "James M. Cape" Date: Wed, 15 Dec 2004 14:58:39 +0000 Subject: [PATCH] Use a GtkComboBox in SELECT_FOLDER mode (#157726). 2004-12-15 James M. Cape * gtk/gtkfilechooserbutton.c (struct _GtkFileChooserButtonPrivate) (gtk_file_chooser_button_init) (gtk_file_chooser_button_file_chooser_iface_init) (gtk_file_chooser_button_add_shortcut_folder) (gtk_file_chooser_button_remove_shortcut_folder) (gtk_file_chooser_button_constructor) (gtk_file_chooser_button_set_property) (gtk_file_chooser_button_destroy) (gtk_file_chooser_button_finalize) (get_icon_theme) (get_display_name_for_path) (model_get_type_position) (model_free_row_data) (model_add_special) (model_add_other) (model_add_volumes) (model_add_bookmarks) (model_update_current_folder) (model_remove_rows) (filter_model_visible_func) (combo_box_row_separator_func) (name_cell_data_func) (update_combo_box) (fs_volumes_changed_cb) (fs_bookmarks_changed_cb) (combo_box_changed_cb) (change_icon_size): Use a GtkComboBox in SELECT_FOLDER mode (#157726). * gtk/gtkfilechooserbutton.c: * gtk/gtkfilechooserbutton.h (gtk_file_chooser_button_new) (gtk_file_chooser_button_new_with_backend): Add @action to constructors to match other GtkFileChooser impls. API CHANGE. * docs/tools/widgets.c (create_file_button): * tests/testfilechooserbutton.c (main): Update callers. * docs/reference/gtk/tmpl/gtkfilechooserbutton.sgml: Reflect API change. * tests/testfilechooserbutton.c: Add LGPL license. (main): Added GOption parser for --backend and --right-to-left cmd line args. (add_pwds_parent_as_shortcut_clicked_cb) (del_pwds_parent_as_shortcut_clicked_cb) (tests_button_clicked_cb): Add shortcut_folders test. (chooser_current_folder_changed_cb) (chooser_selection_changed_cb) (chooser_file_activated_cb) (chooser_update_preview_cb): Print URIS, not filenames. --- ChangeLog | 41 + ChangeLog.pre-2-10 | 41 + ChangeLog.pre-2-6 | 41 + ChangeLog.pre-2-8 | 41 + .../gtk/tmpl/gtkfilechooserbutton.sgml | 2 + docs/tools/widgets.c | 14 +- gtk/gtkfilechooserbutton.c | 1643 ++++++++++++++--- gtk/gtkfilechooserbutton.h | 4 +- tests/testfilechooserbutton.c | 145 +- 9 files changed, 1710 insertions(+), 262 deletions(-) diff --git a/ChangeLog b/ChangeLog index 46a6f4edc..091c8c58e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,44 @@ +2004-12-15 James M. Cape + + * gtk/gtkfilechooserbutton.c (struct _GtkFileChooserButtonPrivate) + (gtk_file_chooser_button_init) + (gtk_file_chooser_button_file_chooser_iface_init) + (gtk_file_chooser_button_add_shortcut_folder) + (gtk_file_chooser_button_remove_shortcut_folder) + (gtk_file_chooser_button_constructor) + (gtk_file_chooser_button_set_property) + (gtk_file_chooser_button_destroy) + (gtk_file_chooser_button_finalize) + (get_icon_theme) (get_display_name_for_path) (model_get_type_position) + (model_free_row_data) (model_add_special) (model_add_other) + (model_add_volumes) (model_add_bookmarks) + (model_update_current_folder) (model_remove_rows) + (filter_model_visible_func) (combo_box_row_separator_func) + (name_cell_data_func) (update_combo_box) (fs_volumes_changed_cb) + (fs_bookmarks_changed_cb) (combo_box_changed_cb) + (change_icon_size): Use a GtkComboBox in SELECT_FOLDER mode (#157726). + + * gtk/gtkfilechooserbutton.c: + * gtk/gtkfilechooserbutton.h (gtk_file_chooser_button_new) + (gtk_file_chooser_button_new_with_backend): Add @action to constructors + to match other GtkFileChooser impls. API CHANGE. + + * docs/tools/widgets.c (create_file_button): + * tests/testfilechooserbutton.c (main): Update callers. + + * docs/reference/gtk/tmpl/gtkfilechooserbutton.sgml: Reflect API + change. + + * tests/testfilechooserbutton.c: Add LGPL license. + (main): Added GOption parser for --backend and --right-to-left cmd line + args. + (add_pwds_parent_as_shortcut_clicked_cb) + (del_pwds_parent_as_shortcut_clicked_cb) (tests_button_clicked_cb): + Add shortcut_folders test. + (chooser_current_folder_changed_cb) (chooser_selection_changed_cb) + (chooser_file_activated_cb) (chooser_update_preview_cb): Print URIS, + not filenames. + 2004-12-15 Matthias Clasen * gtk/gtkfilechooserdefault.c (get_is_file_filtered): Don't diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 46a6f4edc..091c8c58e 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,44 @@ +2004-12-15 James M. Cape + + * gtk/gtkfilechooserbutton.c (struct _GtkFileChooserButtonPrivate) + (gtk_file_chooser_button_init) + (gtk_file_chooser_button_file_chooser_iface_init) + (gtk_file_chooser_button_add_shortcut_folder) + (gtk_file_chooser_button_remove_shortcut_folder) + (gtk_file_chooser_button_constructor) + (gtk_file_chooser_button_set_property) + (gtk_file_chooser_button_destroy) + (gtk_file_chooser_button_finalize) + (get_icon_theme) (get_display_name_for_path) (model_get_type_position) + (model_free_row_data) (model_add_special) (model_add_other) + (model_add_volumes) (model_add_bookmarks) + (model_update_current_folder) (model_remove_rows) + (filter_model_visible_func) (combo_box_row_separator_func) + (name_cell_data_func) (update_combo_box) (fs_volumes_changed_cb) + (fs_bookmarks_changed_cb) (combo_box_changed_cb) + (change_icon_size): Use a GtkComboBox in SELECT_FOLDER mode (#157726). + + * gtk/gtkfilechooserbutton.c: + * gtk/gtkfilechooserbutton.h (gtk_file_chooser_button_new) + (gtk_file_chooser_button_new_with_backend): Add @action to constructors + to match other GtkFileChooser impls. API CHANGE. + + * docs/tools/widgets.c (create_file_button): + * tests/testfilechooserbutton.c (main): Update callers. + + * docs/reference/gtk/tmpl/gtkfilechooserbutton.sgml: Reflect API + change. + + * tests/testfilechooserbutton.c: Add LGPL license. + (main): Added GOption parser for --backend and --right-to-left cmd line + args. + (add_pwds_parent_as_shortcut_clicked_cb) + (del_pwds_parent_as_shortcut_clicked_cb) (tests_button_clicked_cb): + Add shortcut_folders test. + (chooser_current_folder_changed_cb) (chooser_selection_changed_cb) + (chooser_file_activated_cb) (chooser_update_preview_cb): Print URIS, + not filenames. + 2004-12-15 Matthias Clasen * gtk/gtkfilechooserdefault.c (get_is_file_filtered): Don't diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index 46a6f4edc..091c8c58e 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,44 @@ +2004-12-15 James M. Cape + + * gtk/gtkfilechooserbutton.c (struct _GtkFileChooserButtonPrivate) + (gtk_file_chooser_button_init) + (gtk_file_chooser_button_file_chooser_iface_init) + (gtk_file_chooser_button_add_shortcut_folder) + (gtk_file_chooser_button_remove_shortcut_folder) + (gtk_file_chooser_button_constructor) + (gtk_file_chooser_button_set_property) + (gtk_file_chooser_button_destroy) + (gtk_file_chooser_button_finalize) + (get_icon_theme) (get_display_name_for_path) (model_get_type_position) + (model_free_row_data) (model_add_special) (model_add_other) + (model_add_volumes) (model_add_bookmarks) + (model_update_current_folder) (model_remove_rows) + (filter_model_visible_func) (combo_box_row_separator_func) + (name_cell_data_func) (update_combo_box) (fs_volumes_changed_cb) + (fs_bookmarks_changed_cb) (combo_box_changed_cb) + (change_icon_size): Use a GtkComboBox in SELECT_FOLDER mode (#157726). + + * gtk/gtkfilechooserbutton.c: + * gtk/gtkfilechooserbutton.h (gtk_file_chooser_button_new) + (gtk_file_chooser_button_new_with_backend): Add @action to constructors + to match other GtkFileChooser impls. API CHANGE. + + * docs/tools/widgets.c (create_file_button): + * tests/testfilechooserbutton.c (main): Update callers. + + * docs/reference/gtk/tmpl/gtkfilechooserbutton.sgml: Reflect API + change. + + * tests/testfilechooserbutton.c: Add LGPL license. + (main): Added GOption parser for --backend and --right-to-left cmd line + args. + (add_pwds_parent_as_shortcut_clicked_cb) + (del_pwds_parent_as_shortcut_clicked_cb) (tests_button_clicked_cb): + Add shortcut_folders test. + (chooser_current_folder_changed_cb) (chooser_selection_changed_cb) + (chooser_file_activated_cb) (chooser_update_preview_cb): Print URIS, + not filenames. + 2004-12-15 Matthias Clasen * gtk/gtkfilechooserdefault.c (get_is_file_filtered): Don't diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index 46a6f4edc..091c8c58e 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,44 @@ +2004-12-15 James M. Cape + + * gtk/gtkfilechooserbutton.c (struct _GtkFileChooserButtonPrivate) + (gtk_file_chooser_button_init) + (gtk_file_chooser_button_file_chooser_iface_init) + (gtk_file_chooser_button_add_shortcut_folder) + (gtk_file_chooser_button_remove_shortcut_folder) + (gtk_file_chooser_button_constructor) + (gtk_file_chooser_button_set_property) + (gtk_file_chooser_button_destroy) + (gtk_file_chooser_button_finalize) + (get_icon_theme) (get_display_name_for_path) (model_get_type_position) + (model_free_row_data) (model_add_special) (model_add_other) + (model_add_volumes) (model_add_bookmarks) + (model_update_current_folder) (model_remove_rows) + (filter_model_visible_func) (combo_box_row_separator_func) + (name_cell_data_func) (update_combo_box) (fs_volumes_changed_cb) + (fs_bookmarks_changed_cb) (combo_box_changed_cb) + (change_icon_size): Use a GtkComboBox in SELECT_FOLDER mode (#157726). + + * gtk/gtkfilechooserbutton.c: + * gtk/gtkfilechooserbutton.h (gtk_file_chooser_button_new) + (gtk_file_chooser_button_new_with_backend): Add @action to constructors + to match other GtkFileChooser impls. API CHANGE. + + * docs/tools/widgets.c (create_file_button): + * tests/testfilechooserbutton.c (main): Update callers. + + * docs/reference/gtk/tmpl/gtkfilechooserbutton.sgml: Reflect API + change. + + * tests/testfilechooserbutton.c: Add LGPL license. + (main): Added GOption parser for --backend and --right-to-left cmd line + args. + (add_pwds_parent_as_shortcut_clicked_cb) + (del_pwds_parent_as_shortcut_clicked_cb) (tests_button_clicked_cb): + Add shortcut_folders test. + (chooser_current_folder_changed_cb) (chooser_selection_changed_cb) + (chooser_file_activated_cb) (chooser_update_preview_cb): Print URIS, + not filenames. + 2004-12-15 Matthias Clasen * gtk/gtkfilechooserdefault.c (get_is_file_filtered): Don't diff --git a/docs/reference/gtk/tmpl/gtkfilechooserbutton.sgml b/docs/reference/gtk/tmpl/gtkfilechooserbutton.sgml index 7b1fa397c..a88eddf48 100644 --- a/docs/reference/gtk/tmpl/gtkfilechooserbutton.sgml +++ b/docs/reference/gtk/tmpl/gtkfilechooserbutton.sgml @@ -74,6 +74,7 @@ This should not be accessed directly. Use the accessor functions below. @title: +@action: @Returns: @@ -83,6 +84,7 @@ This should not be accessed directly. Use the accessor functions below. @title: +@action: @backend: @Returns: diff --git a/docs/tools/widgets.c b/docs/tools/widgets.c index 182f0b3ac..fbbd24601 100644 --- a/docs/tools/widgets.c +++ b/docs/tools/widgets.c @@ -444,13 +444,14 @@ create_file_button (void) vbox = gtk_vbox_new (FALSE, 12); vbox2 = gtk_vbox_new (FALSE, 3); align = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); - picker = gtk_file_chooser_button_new ("File Chooser Button"); + picker = gtk_file_chooser_button_new ("File Chooser Button", + GTK_FILE_CHOOSER_ACTION_OPEN); gtk_widget_set_size_request (picker, 150, -1); gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (picker), "/etc/yum.conf"); gtk_container_add (GTK_CONTAINER (align), picker); gtk_box_pack_start (GTK_BOX (vbox2), align, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox2), - gtk_label_new ("File Button (Open)"), + gtk_label_new ("File Button (Files)"), FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), @@ -461,15 +462,14 @@ create_file_button (void) vbox2 = gtk_vbox_new (FALSE, 3); align = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); - picker = gtk_file_chooser_button_new ("File Chooser Button"); - gtk_file_chooser_set_action (GTK_FILE_CHOOSER (picker), - GTK_FILE_CHOOSER_ACTION_SAVE); + picker = gtk_file_chooser_button_new ("File Chooser Button", + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); gtk_widget_set_size_request (picker, 150, -1); - gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (picker), "/etc/yum.conf"); + gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (picker), "/"); gtk_container_add (GTK_CONTAINER (align), picker); gtk_box_pack_start (GTK_BOX (vbox2), align, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox2), - gtk_label_new ("File Button (Save)"), + gtk_label_new ("File Button (Select Folder)"), FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), vbox2, TRUE, TRUE, 0); diff --git a/gtk/gtkfilechooserbutton.c b/gtk/gtkfilechooserbutton.c index 98ba56ef8..a4a64f7d5 100644 --- a/gtk/gtkfilechooserbutton.c +++ b/gtk/gtkfilechooserbutton.c @@ -33,12 +33,15 @@ #include "gtkalias.h" #include "gtkintl.h" #include "gtkbutton.h" +#include "gtkcombobox.h" #include "gtkdnd.h" #include "gtkicontheme.h" #include "gtkiconfactory.h" #include "gtkimage.h" #include "gtklabel.h" +#include "gtkliststore.h" #include "gtkstock.h" +#include "gtktreemodelfilter.h" #include "gtkvseparator.h" #include "gtkfilechooserdialog.h" #include "gtkfilechooserprivate.h" @@ -53,11 +56,12 @@ #define GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE(object) (GTK_FILE_CHOOSER_BUTTON ((object))->priv) -#define DEFAULT_FILENAME N_("(None)") -#define MIN_LABEL_WIDTH 100 -#define ENTRY_BUTTON_SPACING 0 -#define FALLBACK_ICON_SIZE 20 +#define HOME_DISPLAY_NAME N_("Home") +#define DESKTOP_DISPLAY_NAME N_("Desktop") +#define FALLBACK_DISPLAY_NAME N_("(None)") #define FALLBACK_ICON_NAME "stock_unknown" +#define FALLBACK_ICON_SIZE 20 + /* ********************** * * Private Enumerations * @@ -73,6 +77,33 @@ enum PROP_WIDTH_CHARS }; +/* TreeModel Columns */ +enum +{ + ICON_COLUMN, + DISPLAY_NAME_COLUMN, + TYPE_COLUMN, + DATA_COLUMN, + NUM_COLUMNS +}; + +/* TreeModel Row Types */ +typedef enum +{ + ROW_TYPE_SPECIAL, + ROW_TYPE_VOLUME, + ROW_TYPE_SHORTCUT, + ROW_TYPE_BOOKMARK_SEPARATOR, + ROW_TYPE_BOOKMARK, + ROW_TYPE_CURRENT_FOLDER_SEPARATOR, + ROW_TYPE_CURRENT_FOLDER, + ROW_TYPE_OTHER_SEPARATOR, + ROW_TYPE_OTHER, + + ROW_TYPE_INVALID = -1 +} +RowType; + /* ******************** * * Private Structures * @@ -84,16 +115,37 @@ struct _GtkFileChooserButtonPrivate GtkWidget *button; GtkWidget *image; GtkWidget *label; + GtkWidget *combo_box; + GtkCellRenderer *icon_cell; + GtkCellRenderer *name_cell; + + GtkTreeModel *model; + GtkTreeModel *filter_model; - GtkFilePath *old_path; gchar *backend; + GtkFileSystem *fs; + GtkFilePath *old_path; + + gulong combo_box_changed_id; gulong dialog_file_activated_id; gulong dialog_folder_changed_id; gulong dialog_selection_changed_id; + gulong fs_volumes_changed_id; + gulong fs_bookmarks_changed_id; + gint icon_size; + guint8 n_special; + guint8 n_volumes; + guint8 n_shortcuts; + guint8 n_bookmarks; + guint8 has_bookmark_separator : 1; + guint8 has_current_folder_separator : 1; + guint8 has_current_folder : 1; + guint8 has_other_separator : 1; + /* Used for hiding/showing the dialog when the button is hidden */ - guint8 active : 1; + guint8 active : 1; }; @@ -107,10 +159,20 @@ enum TEXT_URI_LIST }; + /* ********************* * * Function Prototypes * * ********************* */ +/* GtkFileChooserIface Functions */ +static void gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface); +static gboolean gtk_file_chooser_button_add_shortcut_folder (GtkFileChooser *chooser, + const GtkFilePath *path, + GError **error); +static gboolean gtk_file_chooser_button_remove_shortcut_folder (GtkFileChooser *chooser, + const GtkFilePath *path, + GError **error); + /* GObject Functions */ static GObject *gtk_file_chooser_button_constructor (GType type, guint n_params, @@ -123,6 +185,7 @@ static void gtk_file_chooser_button_get_property (GObject *ob guint param_id, GValue *value, GParamSpec *pspec); +static void gtk_file_chooser_button_finalize (GObject *object); /* GtkObject Functions */ static void gtk_file_chooser_button_destroy (GtkObject *object); @@ -146,30 +209,72 @@ static void gtk_file_chooser_button_style_set (GtkWidget *wi static void gtk_file_chooser_button_screen_changed (GtkWidget *widget, GdkScreen *old_screen); -/* Child Widget Callbacks */ -static void dialog_update_preview_cb (GtkFileChooser *dialog, - gpointer user_data); -static void dialog_selection_changed_cb (GtkFileChooser *dialog, - gpointer user_data); -static void dialog_file_activated_cb (GtkFileChooser *dialog, - gpointer user_data); -static void dialog_current_folder_changed_cb (GtkFileChooser *dialog, - gpointer user_data); -static void dialog_notify_cb (GObject *dialog, - GParamSpec *pspec, - gpointer user_data); -static gboolean dialog_delete_event_cb (GtkWidget *dialog, - GdkEvent *event, - gpointer user_data); -static void dialog_response_cb (GtkDialog *dialog, - gint response, - gpointer user_data); - -static void button_clicked_cb (GtkButton *real_button, - gpointer user_data); - /* Utility Functions */ -static void update_label_and_image (GtkFileChooserButton *button); +static GtkIconTheme *get_icon_theme (GtkWidget *widget); +static gchar *get_display_name_for_path (GtkFileSystem *fs, + const GtkFilePath *path); + +static gint model_get_type_position (GtkFileChooserButton *button, + RowType row_type); +static void model_free_row_data (GtkFileChooserButton *button, + GtkTreeIter *iter); +static inline void model_add_special (GtkFileChooserButton *button); +static inline void model_add_other (GtkFileChooserButton *button); +static void model_add_volumes (GtkFileChooserButton *button, + GSList *volumes); +static void model_add_bookmarks (GtkFileChooserButton *button, + GSList *bookmarks); +static void model_update_current_folder (GtkFileChooserButton *button, + const GtkFilePath *path); +static void model_remove_rows (GtkFileChooserButton *button, + gint pos, + gint n_rows); + +static gboolean filter_model_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data); + +static gboolean combo_box_row_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data); +static void name_cell_data_func (GtkCellLayout *layout, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data); +static void open_dialog (GtkFileChooserButton *button); +static void update_combo_box (GtkFileChooserButton *button); +static void update_label_and_image (GtkFileChooserButton *button); + +/* Child Object Callbacks */ +static void fs_volumes_changed_cb (GtkFileSystem *fs, + gpointer user_data); +static void fs_bookmarks_changed_cb (GtkFileSystem *fs, + gpointer user_data); + +static void combo_box_changed_cb (GtkComboBox *combo_box, + gpointer user_data); + +static void button_clicked_cb (GtkButton *real_button, + gpointer user_data); + +static void dialog_update_preview_cb (GtkFileChooser *dialog, + gpointer user_data); +static void dialog_selection_changed_cb (GtkFileChooser *dialog, + gpointer user_data); +static void dialog_file_activated_cb (GtkFileChooser *dialog, + gpointer user_data); +static void dialog_current_folder_changed_cb (GtkFileChooser *dialog, + gpointer user_data); +static void dialog_notify_cb (GObject *dialog, + GParamSpec *pspec, + gpointer user_data); +static gboolean dialog_delete_event_cb (GtkWidget *dialog, + GdkEvent *event, + gpointer user_data); +static void dialog_response_cb (GtkDialog *dialog, + gint response, + gpointer user_data); /* ******************* * @@ -177,7 +282,7 @@ static void update_label_and_image (GtkFileChooserButton * ******************* */ G_DEFINE_TYPE_WITH_CODE (GtkFileChooserButton, gtk_file_chooser_button, GTK_TYPE_HBOX, { \ - G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, _gtk_file_chooser_delegate_iface_init) \ + G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, gtk_file_chooser_button_file_chooser_iface_init) \ }); @@ -199,6 +304,7 @@ gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class) gobject_class->constructor = gtk_file_chooser_button_constructor; gobject_class->set_property = gtk_file_chooser_button_set_property; gobject_class->get_property = gtk_file_chooser_button_get_property; + gobject_class->finalize = gtk_file_chooser_button_finalize; gtkobject_class->destroy = gtk_file_chooser_button_destroy; @@ -259,7 +365,6 @@ gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class) g_type_class_add_private (class, sizeof (GtkFileChooserButtonPrivate)); } - static void gtk_file_chooser_button_init (GtkFileChooserButton *button) { @@ -275,6 +380,7 @@ gtk_file_chooser_button_init (GtkFileChooserButton *button) gtk_widget_push_composite_child (); + /* Button */ priv->button = gtk_button_new (); g_signal_connect (priv->button, "clicked", G_CALLBACK (button_clicked_cb), button); @@ -289,8 +395,8 @@ gtk_file_chooser_button_init (GtkFileChooserButton *button) gtk_box_pack_start (GTK_BOX (box), priv->image, FALSE, FALSE, 0); gtk_widget_show (priv->image); - priv->label = gtk_label_new (_(DEFAULT_FILENAME)); - gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_START); + priv->label = gtk_label_new (_(FALLBACK_DISPLAY_NAME)); + gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END); gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5); gtk_container_add (GTK_CONTAINER (box), priv->label); gtk_widget_show (priv->label); @@ -304,6 +410,36 @@ gtk_file_chooser_button_init (GtkFileChooserButton *button) gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0); gtk_widget_show (image); + /* Combo Box */ + /* Keep in sync with columns enum, line 88 */ + priv->model = + GTK_TREE_MODEL (gtk_list_store_new (NUM_COLUMNS, + GDK_TYPE_PIXBUF, /* Icon */ + G_TYPE_STRING, /* Display Name */ + G_TYPE_CHAR, /* Row Type */ + G_TYPE_POINTER /* Volume || Path */)); + + priv->combo_box = gtk_combo_box_new (); + priv->combo_box_changed_id = + g_signal_connect (priv->combo_box, "changed", + G_CALLBACK (combo_box_changed_cb), button); + gtk_container_add (GTK_CONTAINER (button), priv->combo_box); + + priv->icon_cell = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->combo_box), + priv->icon_cell, FALSE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->combo_box), + priv->icon_cell, "pixbuf", ICON_COLUMN); + + priv->name_cell = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->combo_box), + priv->name_cell, TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->combo_box), + priv->name_cell, "text", DISPLAY_NAME_COLUMN); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->combo_box), + priv->name_cell, name_cell_data_func, + NULL, NULL); + gtk_widget_pop_composite_child (); /* DnD */ @@ -319,6 +455,123 @@ gtk_file_chooser_button_init (GtkFileChooserButton *button) } +/* ******************************* * + * GtkFileChooserIface Functions * + * ******************************* */ +static void +gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface) +{ + _gtk_file_chooser_delegate_iface_init (iface); + + iface->add_shortcut_folder = gtk_file_chooser_button_add_shortcut_folder; + iface->remove_shortcut_folder = gtk_file_chooser_button_remove_shortcut_folder; +} + +static gboolean +gtk_file_chooser_button_add_shortcut_folder (GtkFileChooser *chooser, + const GtkFilePath *path, + GError **error) +{ + GtkFileChooser *delegate; + gboolean retval; + + delegate = g_object_get_qdata (G_OBJECT (chooser), + GTK_FILE_CHOOSER_DELEGATE_QUARK); + retval = _gtk_file_chooser_add_shortcut_folder (delegate, path, error); + + if (retval) + { + GtkFileChooserButtonPrivate *priv; + GtkTreeIter iter; + gint pos; + GdkPixbuf *pixbuf; + gchar *display_name; + + priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (chooser); + + pos = model_get_type_position (GTK_FILE_CHOOSER_BUTTON (chooser), + ROW_TYPE_SHORTCUT); + pos += priv->n_shortcuts; + + pixbuf = gtk_file_system_render_icon (priv->fs, path, + GTK_WIDGET (chooser), + priv->icon_size, NULL); + display_name = get_display_name_for_path (priv->fs, path); + + gtk_list_store_insert (GTK_LIST_STORE (priv->model), &iter, pos); + gtk_list_store_set (GTK_LIST_STORE (priv->model), &iter, + ICON_COLUMN, pixbuf, + DISPLAY_NAME_COLUMN, display_name, + TYPE_COLUMN, ROW_TYPE_SHORTCUT, + DATA_COLUMN, gtk_file_path_copy (path), + -1); + priv->n_shortcuts++; + + if (pixbuf) + g_object_unref (pixbuf); + g_free (display_name); + + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model)); + } + + return retval; +} + +static gboolean +gtk_file_chooser_button_remove_shortcut_folder (GtkFileChooser *chooser, + const GtkFilePath *path, + GError **error) +{ + GtkFileChooser *delegate; + gboolean retval; + + delegate = g_object_get_qdata (G_OBJECT (chooser), + GTK_FILE_CHOOSER_DELEGATE_QUARK); + + retval = _gtk_file_chooser_add_shortcut_folder (delegate, path, error); + + if (retval) + { + GtkFileChooserButtonPrivate *priv; + GtkTreeIter iter; + gint pos; + gchar type; + + priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (chooser); + + pos = model_get_type_position (GTK_FILE_CHOOSER_BUTTON (chooser), + ROW_TYPE_SHORTCUT); + g_assert (gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos)); + + do + { + gpointer data; + + gtk_tree_model_get (priv->model, &iter, + TYPE_COLUMN, &type, + DATA_COLUMN, &data, + -1); + + if (type == ROW_TYPE_SHORTCUT && + data && + gtk_file_path_compare (data, path) == 0) + { + model_free_row_data (GTK_FILE_CHOOSER_BUTTON (chooser), &iter); + gtk_list_store_remove (GTK_LIST_STORE (priv->model), &iter); + priv->n_shortcuts--; + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model)); + update_combo_box (GTK_FILE_CHOOSER_BUTTON (chooser)); + break; + } + } + while (type == ROW_TYPE_SHORTCUT && + gtk_tree_model_iter_next (priv->model, &iter)); + } + + return retval; +} + + /* ******************* * * GObject Functions * * ******************* */ @@ -330,6 +583,7 @@ gtk_file_chooser_button_constructor (GType type, { GObject *object; GtkFileChooserButtonPrivate *priv; + GSList *list; object = (*G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructor) (type, n_params, @@ -391,6 +645,41 @@ gtk_file_chooser_button_constructor (GType type, g_object_add_weak_pointer (G_OBJECT (priv->dialog), (gpointer *) (&priv->dialog)); + priv->fs = + g_object_ref (_gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog))); + + model_add_special (GTK_FILE_CHOOSER_BUTTON (object)); + + list = gtk_file_system_list_volumes (priv->fs); + model_add_volumes (GTK_FILE_CHOOSER_BUTTON (object), list); + g_slist_free (list); + + list = gtk_file_system_list_bookmarks (priv->fs); + model_add_bookmarks (GTK_FILE_CHOOSER_BUTTON (object), list); + gtk_file_paths_free (list); + + model_add_other (GTK_FILE_CHOOSER_BUTTON (object)); + + priv->filter_model = gtk_tree_model_filter_new (priv->model, NULL); + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter_model), + filter_model_visible_func, + object, NULL); + + gtk_combo_box_set_model (GTK_COMBO_BOX (priv->combo_box), priv->filter_model); + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (priv->combo_box), + combo_box_row_separator_func, + NULL, NULL); + + update_label_and_image (GTK_FILE_CHOOSER_BUTTON (object)); + update_combo_box (GTK_FILE_CHOOSER_BUTTON (object)); + + priv->fs_volumes_changed_id = + g_signal_connect (priv->fs, "volumes-changed", + G_CALLBACK (fs_volumes_changed_cb), object); + priv->fs_bookmarks_changed_id = + g_signal_connect (priv->fs, "bookmarks-changed", + G_CALLBACK (fs_bookmarks_changed_cb), object); + return object; } @@ -436,6 +725,22 @@ gtk_file_chooser_button_set_property (GObject *object, g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value); update_label_and_image (GTK_FILE_CHOOSER_BUTTON (object)); + update_combo_box (GTK_FILE_CHOOSER_BUTTON (object)); + + switch (g_value_get_enum (value)) + { + case GTK_FILE_CHOOSER_ACTION_OPEN: + gtk_widget_hide (priv->combo_box); + gtk_widget_show (priv->button); + break; + case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER: + gtk_widget_hide (priv->button); + gtk_widget_show (priv->combo_box); + break; + default: + g_assert_not_reached (); + break; + } break; case PROP_TITLE: @@ -501,13 +806,42 @@ gtk_file_chooser_button_get_property (GObject *object, } } +static void +gtk_file_chooser_button_finalize (GObject *object) +{ + GtkFileChooserButtonPrivate *priv; + GtkTreeIter iter; + + priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object); + + if (priv->old_path) + gtk_file_path_free (priv->old_path); + + g_assert (gtk_tree_model_get_iter_first (priv->model, &iter)); + + do + { + model_free_row_data (GTK_FILE_CHOOSER_BUTTON (object), &iter); + } + while (gtk_tree_model_iter_next (priv->model, &iter)); + + g_object_unref (priv->model); + g_object_unref (priv->filter_model); + + g_signal_handler_disconnect (priv->fs, priv->fs_volumes_changed_id); + g_signal_handler_disconnect (priv->fs, priv->fs_bookmarks_changed_id); + g_object_unref (priv->fs); + + if (G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->finalize != NULL) + (*G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->finalize) (object); +} /* ********************* * * GtkObject Functions * * ********************* */ static void -gtk_file_chooser_button_destroy (GtkObject * object) +gtk_file_chooser_button_destroy (GtkObject *object) { GtkFileChooserButtonPrivate *priv; @@ -516,9 +850,6 @@ gtk_file_chooser_button_destroy (GtkObject * object) if (priv->dialog != NULL) gtk_widget_destroy (priv->dialog); - if (priv->old_path) - gtk_file_path_free (priv->old_path); - if (GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy != NULL) (*GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy) (object); } @@ -538,7 +869,6 @@ gtk_file_chooser_button_drag_data_received (GtkWidget *widget, guint drag_time) { GtkFileChooserButtonPrivate *priv; - GtkFileSystem *fs; GtkFilePath *path; gchar *text; @@ -554,8 +884,6 @@ gtk_file_chooser_button_drag_data_received (GtkWidget *widget, priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget); - fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)); - switch (info) { case TEXT_URI_LIST: @@ -573,16 +901,16 @@ gtk_file_chooser_button_drag_data_received (GtkWidget *widget, selected = FALSE; for (i = 0; !selected && uris[i] != NULL; i++) { - path = gtk_file_system_uri_to_path (fs, uris[i]); + path = gtk_file_system_uri_to_path (priv->fs, uris[i]); base_path = NULL; if (path != NULL && - gtk_file_system_get_parent (fs, path, &base_path, NULL)) + gtk_file_system_get_parent (priv->fs, path, &base_path, NULL)) { GtkFileFolder *folder; GtkFileInfo *info; - folder = gtk_file_system_get_folder (fs, base_path, + folder = gtk_file_system_get_folder (priv->fs, base_path, GTK_FILE_INFO_IS_FOLDER, NULL); @@ -655,7 +983,7 @@ gtk_file_chooser_button_show (GtkWidget *widget) (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show) (widget); if (priv->active) - gtk_widget_show (priv->dialog); + open_dialog (GTK_FILE_CHOOSER_BUTTON (widget)); } static void @@ -687,28 +1015,95 @@ gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget, static void change_icon_theme (GtkFileChooserButton *button) { + GtkFileChooserButtonPrivate *priv; GtkSettings *settings; + GtkIconTheme *theme; + GtkTreeIter iter; gint width, height; + priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button); + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button))); if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_SMALL_TOOLBAR, &width, &height)) - button->priv->icon_size = MAX (width, height); + priv->icon_size = MAX (width, height); else - button->priv->icon_size = FALLBACK_ICON_SIZE; + priv->icon_size = FALLBACK_ICON_SIZE; update_label_and_image (button); + + g_assert (gtk_tree_model_get_iter_first (priv->model, &iter)); + + theme = get_icon_theme (GTK_WIDGET (button)); + + do + { + GdkPixbuf *pixbuf; + gchar type; + gpointer data; + + type = ROW_TYPE_INVALID; + gtk_tree_model_get (priv->model, &iter, + TYPE_COLUMN, &type, + DATA_COLUMN, &data, + -1); + + switch (type) + { + case ROW_TYPE_SPECIAL: + case ROW_TYPE_SHORTCUT: + case ROW_TYPE_BOOKMARK: + case ROW_TYPE_CURRENT_FOLDER: + if (data) + pixbuf = gtk_file_system_render_icon (priv->fs, data, + GTK_WIDGET (button), + priv->icon_size, NULL); + else + pixbuf = gtk_icon_theme_load_icon (theme, FALLBACK_ICON_NAME, + priv->icon_size, 0, NULL); + break; + case ROW_TYPE_VOLUME: + if (data) + pixbuf = gtk_file_system_volume_render_icon (priv->fs, data, + GTK_WIDGET (button), + priv->icon_size, + NULL); + else + pixbuf = gtk_icon_theme_load_icon (theme, FALLBACK_ICON_NAME, + priv->icon_size, 0, NULL); + break; + default: + continue; + break; + } + + if (pixbuf) + width = MAX (width, gdk_pixbuf_get_width (pixbuf)); + + gtk_list_store_set (GTK_LIST_STORE (priv->model), &iter, + ICON_COLUMN, pixbuf, + -1); + } + while (gtk_tree_model_iter_next (priv->model, &iter)); + + g_object_set (button->priv->icon_cell, + "width", width, + NULL); } static void gtk_file_chooser_button_style_set (GtkWidget *widget, GtkStyle *old_style) { + GtkFileChooserButtonPrivate *priv; + if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set) (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set) (widget, old_style); + priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget); + if (gtk_widget_has_screen (widget)) change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget)); } @@ -717,172 +1112,635 @@ static void gtk_file_chooser_button_screen_changed (GtkWidget *widget, GdkScreen *old_screen) { - GtkFileChooserButton *button; - - button = GTK_FILE_CHOOSER_BUTTON (widget); - if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed) (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed) (widget, old_screen); - change_icon_theme (button); + change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget)); } -/* ************************************************************************** * - * Public API * - * ************************************************************************** */ +/* ******************* * + * Utility Functions * + * ******************* */ -/** - * gtk_file_chooser_button_new: - * @title: the title of the browse dialog. - * - * Creates a new file-selecting button widget. - * - * Returns: a new button widget. - * - * Since: 2.6 - **/ -GtkWidget * -gtk_file_chooser_button_new (const gchar *title) +/* General */ +static GtkIconTheme * +get_icon_theme (GtkWidget *widget) { - return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, - "title", title, - NULL); -} + if (gtk_widget_has_screen (widget)) + return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); -/** - * gtk_file_chooser_button_new_with_backend: - * @title: the title of the browse dialog. - * @backend: the name of the #GtkFileSystem backend to use. - * - * Creates a new file-selecting button widget using @backend. - * - * Returns: a new button widget. - * - * Since: 2.6 - **/ -GtkWidget * -gtk_file_chooser_button_new_with_backend (const gchar *title, - const gchar *backend) -{ - return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, - "title", title, - "file-system-backend", backend, - NULL); + return gtk_icon_theme_get_default (); } -/** - * gtk_file_chooser_button_new_with_dialog: - * @dialog: the #GtkDialog widget to use. - * - * Creates a #GtkFileChooserButton widget which uses @dialog as it's - * file-picking window. Note that @dialog must be a #GtkFileChooserDialog (or - * subclass). - * - * Returns: a new button widget. - * - * Since: 2.6 - **/ -GtkWidget * -gtk_file_chooser_button_new_with_dialog (GtkWidget *dialog) +static gchar * +get_display_name_for_path (GtkFileSystem *fs, + const GtkFilePath *path) { - g_return_val_if_fail (GTK_IS_FILE_CHOOSER_DIALOG (dialog), NULL); + GtkFilePath *parent_path; + GtkFileFolder *folder; + gchar *retval; - return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, - "dialog", dialog, - NULL); -} + parent_path = NULL; + retval = NULL; -/** - * gtk_file_chooser_button_set_title: - * @button: the button widget to modify. - * @title: the new browse dialog title. - * - * Modifies the @title of the browse dialog used by @button. - * - * Since: 2.6 - **/ -void -gtk_file_chooser_button_set_title (GtkFileChooserButton *button, - const gchar *title) -{ - g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button)); + gtk_file_system_get_parent (fs, path, &parent_path, NULL); - gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title); - g_object_notify (G_OBJECT (button), "title"); -} + folder = gtk_file_system_get_folder (fs, parent_path ? parent_path : path, + GTK_FILE_INFO_DISPLAY_NAME, NULL); -/** - * gtk_file_chooser_button_get_title: - * @button: the button widget to examine. - * - * Retrieves the title of the browse dialog used by @button. The returned value - * should not be modified or freed. - * - * Returns: a pointer to the browse dialog's title. - * - * Since: 2.6 - **/ -G_CONST_RETURN gchar * -gtk_file_chooser_button_get_title (GtkFileChooserButton *button) -{ - g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), NULL); + if (folder) + { + GtkFileInfo *info; - return gtk_window_get_title (GTK_WINDOW (button->priv->dialog)); -} + info = gtk_file_folder_get_info (folder, path, NULL); + g_object_unref (folder); -/** - * gtk_file_chooser_button_get_width_chars: - * @button: the button widget to examine. - * - * Retrieves the width in characters of the @button widget's entry and/or label. - * - * Returns: an integer width (in characters) that the button will use to size itself. - * - * Since: 2.6 - **/ -gint -gtk_file_chooser_button_get_width_chars (GtkFileChooserButton *button) -{ - g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), -1); + if (info) + { + retval = g_strdup (gtk_file_info_get_display_name (info)); + gtk_file_info_free (info); + } + } - return gtk_label_get_width_chars (GTK_LABEL (button->priv->label)); + if (parent_path) + gtk_file_path_free (parent_path); + + if (!retval) + retval = g_strdup (_(FALLBACK_DISPLAY_NAME)); + + return retval; } -/** - * gtk_file_chooser_button_set_width_chars: - * @button: the button widget to examine. - * @n_chars: the new width, in chracters. - * - * Sets the width (in characters) that @button will use to @n_chars. - * - * Since: 2.6 - **/ -void -gtk_file_chooser_button_set_width_chars (GtkFileChooserButton *button, - gint n_chars) +/* Shortcuts Model */ +static gint +model_get_type_position (GtkFileChooserButton *button, + RowType row_type) { - g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button)); + gint retval = 0; - gtk_label_set_width_chars (GTK_LABEL (button->priv->label), n_chars); - g_object_notify (G_OBJECT (button), "width-chars"); -} + if (row_type == ROW_TYPE_SPECIAL) + return retval; + retval += button->priv->n_special; -/* ******************* * - * Utility Functions * - * ******************* */ + if (row_type == ROW_TYPE_VOLUME) + return retval; -static inline GtkIconTheme * -get_icon_theme (GtkWidget *widget) + retval += button->priv->n_volumes; + + if (row_type == ROW_TYPE_SHORTCUT) + return retval; + + retval += button->priv->n_shortcuts; + + if (row_type == ROW_TYPE_BOOKMARK_SEPARATOR) + return retval; + + retval += button->priv->has_bookmark_separator; + + if (row_type == ROW_TYPE_BOOKMARK) + return retval; + + retval += button->priv->n_bookmarks; + + if (row_type == ROW_TYPE_CURRENT_FOLDER_SEPARATOR) + return retval; + + retval += button->priv->has_current_folder_separator; + + if (row_type == ROW_TYPE_CURRENT_FOLDER) + return retval; + + retval += button->priv->has_current_folder; + + if (row_type == ROW_TYPE_OTHER_SEPARATOR) + return retval; + + retval += button->priv->has_other_separator; + + if (row_type == ROW_TYPE_OTHER) + return retval; + + g_assert_not_reached (); + return -1; +} + +static void +model_free_row_data (GtkFileChooserButton *button, + GtkTreeIter *iter) { - if (gtk_widget_has_screen (widget)) - return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); + gchar type; + gpointer data; - return gtk_icon_theme_get_default (); + gtk_tree_model_get (button->priv->model, iter, + TYPE_COLUMN, &type, + DATA_COLUMN, &data, + -1); + + switch (type) + { + case ROW_TYPE_SPECIAL: + case ROW_TYPE_SHORTCUT: + case ROW_TYPE_BOOKMARK: + case ROW_TYPE_CURRENT_FOLDER: + gtk_file_path_free (data); + break; + case ROW_TYPE_VOLUME: + gtk_file_system_volume_free (button->priv->fs, data); + break; + default: + break; + } +} + +static inline void +model_add_special (GtkFileChooserButton *button) +{ + const gchar *homedir; + + homedir = g_get_home_dir (); + + if (homedir) + { + GtkListStore *store; + GtkTreeIter iter; + GtkFilePath *path; + GdkPixbuf *pixbuf; + gchar *desktopdir; + gint pos; + + store = GTK_LIST_STORE (button->priv->model); + + pos = model_get_type_position (button, ROW_TYPE_SPECIAL); + + path = gtk_file_system_filename_to_path (button->priv->fs, homedir); + pixbuf = gtk_file_system_render_icon (button->priv->fs, path, + GTK_WIDGET (button), + button->priv->icon_size, NULL); + gtk_list_store_insert (store, &iter, pos); + gtk_list_store_set (store, &iter, + ICON_COLUMN, pixbuf, + DISPLAY_NAME_COLUMN, _(HOME_DISPLAY_NAME), + TYPE_COLUMN, ROW_TYPE_SPECIAL, + DATA_COLUMN, path, + -1); + + g_object_unref (pixbuf); + button->priv->n_special++; + + desktopdir = g_build_filename (homedir, DESKTOP_DISPLAY_NAME, NULL); + pos++; + + path = gtk_file_system_filename_to_path (button->priv->fs, desktopdir); + g_free (desktopdir); + pixbuf = gtk_file_system_render_icon (button->priv->fs, path, + GTK_WIDGET (button), + button->priv->icon_size, NULL); + gtk_list_store_insert (store, &iter, pos); + gtk_list_store_set (store, &iter, + TYPE_COLUMN, ROW_TYPE_SPECIAL, + ICON_COLUMN, pixbuf, + DISPLAY_NAME_COLUMN, _(DESKTOP_DISPLAY_NAME), + DATA_COLUMN, path, + -1); + + g_object_unref (pixbuf); + button->priv->n_special++; + } +} + +static void +model_add_volumes (GtkFileChooserButton *button, + GSList *volumes) +{ + GtkListStore *store; + gint pos; + + if (!volumes) + return; + + store = GTK_LIST_STORE (button->priv->model); + pos = model_get_type_position (button, ROW_TYPE_VOLUME); + + do + { + GtkTreeIter iter; + GdkPixbuf *pixbuf; + gchar *display_name; + + pixbuf = gtk_file_system_volume_render_icon (button->priv->fs, + volumes->data, + GTK_WIDGET (button), + button->priv->icon_size, + NULL); + display_name = gtk_file_system_volume_get_display_name (button->priv->fs, + volumes->data); + + gtk_list_store_insert (store, &iter, pos); + gtk_list_store_set (store, &iter, + ICON_COLUMN, pixbuf, + DISPLAY_NAME_COLUMN, display_name, + TYPE_COLUMN, ROW_TYPE_VOLUME, + DATA_COLUMN, volumes->data, + -1); + + g_object_unref (pixbuf); + g_free (display_name); + + button->priv->n_volumes++; + pos++; + volumes = volumes->next; + } + while (volumes); +} + +static void +model_add_bookmarks (GtkFileChooserButton *button, + GSList *bookmarks) +{ + GtkListStore *store; + GtkTreeIter iter; + gint pos; + + if (!bookmarks) + return; + + store = GTK_LIST_STORE (button->priv->model); + pos = model_get_type_position (button, ROW_TYPE_BOOKMARK_SEPARATOR); + + if (!button->priv->has_bookmark_separator) + { + gtk_list_store_insert (store, &iter, pos); + gtk_list_store_set (store, &iter, + ICON_COLUMN, NULL, + DISPLAY_NAME_COLUMN, NULL, + TYPE_COLUMN, ROW_TYPE_BOOKMARK_SEPARATOR, + DATA_COLUMN, NULL, + -1); + button->priv->has_bookmark_separator = TRUE; + } + + do + { + GdkPixbuf *pixbuf; + gchar *display_name; + + pos++; + pixbuf = gtk_file_system_render_icon (button->priv->fs, bookmarks->data, + GTK_WIDGET (button), + button->priv->icon_size, NULL); + display_name = get_display_name_for_path (button->priv->fs, + bookmarks->data); + + gtk_list_store_insert (store, &iter, pos); + gtk_list_store_set (store, &iter, + ICON_COLUMN, pixbuf, + DISPLAY_NAME_COLUMN, display_name, + TYPE_COLUMN, ROW_TYPE_BOOKMARK, + DATA_COLUMN, gtk_file_path_copy (bookmarks->data), + -1); + if (pixbuf) + g_object_unref (pixbuf); + g_free (display_name); + + button->priv->n_bookmarks++; + bookmarks = bookmarks->next; + } + while (bookmarks); +} + +static void +model_update_current_folder (GtkFileChooserButton *button, + const GtkFilePath *path) +{ + GtkListStore *store; + GtkTreeIter iter; + gint pos; + GdkPixbuf *pixbuf; + gchar *display_name; + + if (!path) + return; + + store = GTK_LIST_STORE (button->priv->model); + + if (!button->priv->has_current_folder_separator) + { + pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER_SEPARATOR); + gtk_list_store_insert (store, &iter, pos); + gtk_list_store_set (store, &iter, + ICON_COLUMN, NULL, + DISPLAY_NAME_COLUMN, NULL, + TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER_SEPARATOR, + DATA_COLUMN, NULL, + -1); + button->priv->has_current_folder_separator = TRUE; + } + + pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER); + if (!button->priv->has_current_folder) + { + gtk_list_store_insert (store, &iter, pos); + button->priv->has_current_folder = TRUE; + } + else + gtk_tree_model_iter_nth_child (button->priv->model, &iter, NULL, pos); + + pixbuf = gtk_file_system_render_icon (button->priv->fs, path, + GTK_WIDGET (button), + button->priv->icon_size, NULL); + display_name = get_display_name_for_path (button->priv->fs, path); + gtk_list_store_set (store, &iter, + ICON_COLUMN, pixbuf, + DISPLAY_NAME_COLUMN, display_name, + TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER, + DATA_COLUMN, gtk_file_path_copy (path), + -1); + if (pixbuf) + g_object_unref (pixbuf); + g_free (display_name); +} + +static inline void +model_add_other (GtkFileChooserButton *button) +{ + GtkListStore *store; + GtkTreeIter iter; + gint pos; + + store = GTK_LIST_STORE (button->priv->model); + pos = model_get_type_position (button, ROW_TYPE_OTHER_SEPARATOR); + + gtk_list_store_insert (store, &iter, pos); + gtk_list_store_set (store, &iter, + ICON_COLUMN, NULL, + DISPLAY_NAME_COLUMN, NULL, + TYPE_COLUMN, ROW_TYPE_OTHER_SEPARATOR, + DATA_COLUMN, NULL, + -1); + button->priv->has_other_separator = TRUE; + pos++; + + gtk_list_store_insert (store, &iter, pos); + gtk_list_store_set (store, &iter, + ICON_COLUMN, NULL, + DISPLAY_NAME_COLUMN, _("Other..."), + TYPE_COLUMN, ROW_TYPE_OTHER, + DATA_COLUMN, NULL, + -1); +} + +static void +model_remove_rows (GtkFileChooserButton *button, + gint pos, + gint n_rows) +{ + GtkListStore *store; + + if (!n_rows) + return; + + store = GTK_LIST_STORE (button->priv->model); + + do + { + GtkTreeIter iter; + + if (!gtk_tree_model_iter_nth_child (button->priv->model, &iter, NULL, pos)) + g_assert_not_reached (); + + model_free_row_data (button, &iter); + gtk_list_store_remove (store, &iter); + n_rows--; + } + while (n_rows); +} + +/* Filter Model */ +static inline gboolean +test_if_path_is_visible (GtkFileSystem *fs, + const GtkFilePath *path, + gboolean local_only) +{ + GtkFilePath *parent_path; + GtkFileFolder *folder; + GtkFileInfo *info; + + if (!path) + return FALSE; + + if (local_only && !gtk_file_system_path_is_local (fs, path)) + return FALSE; + + parent_path = NULL; + gtk_file_system_get_parent (fs, path, &parent_path, NULL); + + folder = gtk_file_system_get_folder (fs, parent_path ? parent_path : path, + GTK_FILE_INFO_IS_FOLDER, NULL); + gtk_file_path_free (parent_path); + + if (folder) + { + info = gtk_file_folder_get_info (folder, path, NULL); + g_object_unref (folder); + } + else + info = NULL; + + if (!info) + return FALSE; + else if (!gtk_file_info_get_is_folder (info)) + { + gtk_file_info_free (info); + return FALSE; + } + + gtk_file_info_free (info); + + return TRUE; +} + +static gboolean +filter_model_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + GtkFileChooserButtonPrivate *priv; + gchar type; + gpointer data; + gboolean local_only, retval; + + priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data); + type = ROW_TYPE_INVALID; + data = NULL; + local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (priv->dialog)); + + gtk_tree_model_get (model, iter, + TYPE_COLUMN, &type, + DATA_COLUMN, &data, + -1); + + switch (type) + { + case ROW_TYPE_CURRENT_FOLDER: + retval = TRUE; + break; + case ROW_TYPE_SPECIAL: + case ROW_TYPE_SHORTCUT: + case ROW_TYPE_BOOKMARK: + retval = test_if_path_is_visible (priv->fs, data, local_only); + break; + case ROW_TYPE_VOLUME: + { + GtkFilePath *base_path; + + base_path = gtk_file_system_volume_get_base_path (priv->fs, data); + if (base_path) + { + retval = (!local_only || + gtk_file_system_path_is_local (priv->fs, base_path)); + gtk_file_path_free (base_path); + } + else + retval = FALSE; + } + break; + default: + retval = TRUE; + break; + } + + return retval; +} + +/* Combo Box */ +static void +name_cell_data_func (GtkCellLayout *layout, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + gchar type; + + type = 0; + gtk_tree_model_get (model, iter, + TYPE_COLUMN, &type, + -1); + + if (type == ROW_TYPE_CURRENT_FOLDER) + g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + else + g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_NONE, NULL); +} + +static gboolean +combo_box_row_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + gchar type = ROW_TYPE_INVALID; + + gtk_tree_model_get (model, iter, TYPE_COLUMN, &type, -1); + + return (type == ROW_TYPE_BOOKMARK_SEPARATOR || + type == ROW_TYPE_CURRENT_FOLDER_SEPARATOR || + type == ROW_TYPE_OTHER_SEPARATOR); +} + +static void +update_combo_box (GtkFileChooserButton *button) +{ + GtkFileChooserButtonPrivate *priv; + GSList *paths; + GtkTreeIter iter; + gboolean row_found; + + priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button); + + g_assert (gtk_tree_model_get_iter_first (priv->filter_model, &iter)); + + _gtk_file_chooser_get_current_folder_path (GTK_FILE_CHOOSER (priv->dialog)); + paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog)); + + row_found = FALSE; + + do + { + gchar type; + gpointer data; + + type = ROW_TYPE_INVALID; + data = NULL; + + gtk_tree_model_get (priv->filter_model, &iter, + TYPE_COLUMN, &type, + DATA_COLUMN, &data, + -1); + + switch (type) + { + case ROW_TYPE_SPECIAL: + case ROW_TYPE_SHORTCUT: + case ROW_TYPE_BOOKMARK: + case ROW_TYPE_CURRENT_FOLDER: + row_found = (paths && + paths->data && + gtk_file_path_compare (data, paths->data) == 0); + break; + case ROW_TYPE_VOLUME: + { + GtkFilePath *base_path; + + base_path = gtk_file_system_volume_get_base_path (priv->fs, data); + row_found = (paths && + paths->data && + gtk_file_path_compare (base_path, paths->data) == 0); + gtk_file_path_free (base_path); + } + break; + default: + row_found = FALSE; + break; + } + + if (row_found) + { + g_signal_handler_block (priv->combo_box, priv->combo_box_changed_id); + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_box), + &iter); + g_signal_handler_unblock (priv->combo_box, + priv->combo_box_changed_id); + } + } + while (!row_found && gtk_tree_model_iter_next (priv->filter_model, &iter)); + + /* If it hasn't been found already, update & select the current-folder row. */ + if (!row_found && paths && paths->data) + { + GtkTreeIter filter_iter; + gint pos; + + model_update_current_folder (button, paths->data); + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model)); + + pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER); + g_assert (gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos)); + + gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter_model), + &filter_iter, &iter); + + g_signal_handler_block (priv->combo_box, priv->combo_box_changed_id); + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_box), &filter_iter); + g_signal_handler_unblock (priv->combo_box, priv->combo_box_changed_id); + } + + gtk_file_paths_free (paths); } +/* Button */ static void update_label_and_image (GtkFileChooserButton *button) { @@ -898,25 +1756,23 @@ update_label_and_image (GtkFileChooserButton *button) if (paths) { - GtkFileSystem *fs; GtkFilePath *path, *parent_path; GtkFileSystemVolume *volume; GtkFileFolder *folder; path = paths->data; - fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)); - - volume = gtk_file_system_get_volume_for_path (fs, path); + volume = gtk_file_system_get_volume_for_path (priv->fs, path); if (volume) { GtkFilePath *base_path; - base_path = gtk_file_system_volume_get_base_path (fs, volume); + base_path = gtk_file_system_volume_get_base_path (priv->fs, volume); if (base_path && gtk_file_path_compare (base_path, path) == 0) { - label_text = gtk_file_system_volume_get_display_name (fs, volume); - pixbuf = gtk_file_system_volume_render_icon (fs, volume, + label_text = gtk_file_system_volume_get_display_name (priv->fs, + volume); + pixbuf = gtk_file_system_volume_render_icon (priv->fs, volume, GTK_WIDGET (button), priv->icon_size, NULL); @@ -925,20 +1781,22 @@ update_label_and_image (GtkFileChooserButton *button) if (base_path) gtk_file_path_free (base_path); - gtk_file_system_volume_free (fs, volume); + gtk_file_system_volume_free (priv->fs, volume); if (label_text) goto out; } if (!pixbuf) - pixbuf = gtk_file_system_render_icon (fs, path, GTK_WIDGET (button), + pixbuf = gtk_file_system_render_icon (priv->fs, path, + GTK_WIDGET (button), priv->icon_size, NULL); parent_path = NULL; - gtk_file_system_get_parent (fs, path, &parent_path, NULL); + gtk_file_system_get_parent (priv->fs, path, &parent_path, NULL); - folder = gtk_file_system_get_folder (fs, parent_path ? parent_path : path, + folder = gtk_file_system_get_folder (priv->fs, + parent_path ? parent_path : path, GTK_FILE_INFO_DISPLAY_NAME, NULL); if (folder) { @@ -964,7 +1822,7 @@ update_label_and_image (GtkFileChooserButton *button) g_free (label_text); } else - gtk_label_set_text (GTK_LABEL (priv->label), _(DEFAULT_FILENAME)); + gtk_label_set_text (GTK_LABEL (priv->label), _(FALLBACK_DISPLAY_NAME)); if (!pixbuf) pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)), @@ -976,10 +1834,189 @@ update_label_and_image (GtkFileChooserButton *button) g_object_unref (pixbuf); } + /* ************************ * - * Child-Widget Callbacks * + * Child Object Callbacks * * ************************ */ +/* File System */ +static void +fs_volumes_changed_cb (GtkFileSystem *fs, + gpointer user_data) +{ + GtkFileChooserButtonPrivate *priv; + GSList *volumes; + + priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data); + + model_remove_rows (user_data, + model_get_type_position (user_data, ROW_TYPE_VOLUME), + priv->n_volumes); + + priv->n_volumes = 0; + + volumes = gtk_file_system_list_volumes (fs); + model_add_volumes (user_data, volumes); + g_slist_free (volumes); + + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model)); + + update_label_and_image (user_data); + update_combo_box (user_data); +} + +static void +fs_bookmarks_changed_cb (GtkFileSystem *fs, + gpointer user_data) +{ + GtkFileChooserButtonPrivate *priv; + GSList *bookmarks; + + priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data); + + bookmarks = gtk_file_system_list_bookmarks (fs); + if (!bookmarks) + { + model_remove_rows (user_data, + model_get_type_position (user_data, + ROW_TYPE_BOOKMARK_SEPARATOR), + (priv->n_bookmarks + priv->has_bookmark_separator)); + priv->has_bookmark_separator = FALSE; + } + else + model_remove_rows (user_data, + model_get_type_position (user_data, ROW_TYPE_BOOKMARK), + priv->n_bookmarks); + + priv->n_bookmarks = 0; + model_add_bookmarks (user_data, bookmarks); + gtk_file_paths_free (bookmarks); + + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model)); + + update_label_and_image (user_data); + update_combo_box (user_data); +} + +/* Dialog */ +static void +open_dialog (GtkFileChooserButton *button) +{ + GtkFileChooserButtonPrivate *priv; + + priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button); + + /* Setup the dialog parent to be chooser button's toplevel, and be modal + as needed. */ + if (!GTK_WIDGET_VISIBLE (priv->dialog)) + { + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button)); + + if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_IS_WINDOW (toplevel)) + { + if (GTK_WINDOW (toplevel) != gtk_window_get_transient_for (GTK_WINDOW (priv->dialog))) + gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), + GTK_WINDOW (toplevel)); + + gtk_window_set_modal (GTK_WINDOW (priv->dialog), + gtk_window_get_modal (GTK_WINDOW (toplevel))); + } + } + + if (!priv->active) + { + GSList *paths; + + g_signal_handler_block (priv->dialog, + priv->dialog_folder_changed_id); + g_signal_handler_block (priv->dialog, + priv->dialog_file_activated_id); + g_signal_handler_block (priv->dialog, + priv->dialog_selection_changed_id); + paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog)); + if (paths) + { + if (paths->data) + priv->old_path = gtk_file_path_copy (paths->data); + + gtk_file_paths_free (paths); + } + + priv->active = TRUE; + } + + gtk_widget_set_sensitive (priv->combo_box, FALSE); + gtk_window_present (GTK_WINDOW (priv->dialog)); +} + +/* Combo Box */ +static void +combo_box_changed_cb (GtkComboBox *combo_box, + gpointer user_data) +{ + GtkTreeIter iter; + + if (gtk_combo_box_get_active_iter (combo_box, &iter)) + { + GtkFileChooserButtonPrivate *priv; + gchar type; + gpointer data; + + priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data); + + type = ROW_TYPE_INVALID; + data = NULL; + + gtk_tree_model_get (priv->filter_model, &iter, + TYPE_COLUMN, &type, + DATA_COLUMN, &data, + -1); + + switch (type) + { + case ROW_TYPE_SPECIAL: + case ROW_TYPE_SHORTCUT: + case ROW_TYPE_BOOKMARK: + case ROW_TYPE_CURRENT_FOLDER: + gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog)); + if (data) + _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog), + data, NULL); + break; + case ROW_TYPE_VOLUME: + { + GtkFilePath *base_path; + + gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog)); + base_path = gtk_file_system_volume_get_base_path (priv->fs, data); + if (base_path) + { + _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog), + base_path, NULL); + gtk_file_path_free (base_path); + } + } + break; + case ROW_TYPE_OTHER: + open_dialog (user_data); + break; + default: + break; + } + } +} + +/* Button */ +static void +button_clicked_cb (GtkButton *real_button, + gpointer user_data) +{ + open_dialog (user_data); +} + +/* Dialog */ static void dialog_current_folder_changed_cb (GtkFileChooser *dialog, gpointer user_data) @@ -999,6 +2036,7 @@ dialog_selection_changed_cb (GtkFileChooser *dialog, gpointer user_data) { update_label_and_image (user_data); + update_combo_box (user_data); g_signal_emit_by_name (user_data, "selection-changed"); } @@ -1020,6 +2058,41 @@ dialog_notify_cb (GObject *dialog, GTK_TYPE_FILE_CHOOSER); if (g_object_interface_find_property (iface, pspec->name)) g_object_notify (user_data, pspec->name); + + if (g_ascii_strcasecmp (pspec->name, "local-only") == 0 || + g_ascii_strcasecmp (pspec->name, "local_only") == 0) + { + GtkFileChooserButtonPrivate *priv; + + priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data); + + if (priv->has_current_folder) + { + GtkTreeIter iter; + gint pos; + gpointer data; + + pos = model_get_type_position (user_data, + ROW_TYPE_CURRENT_FOLDER); + g_assert (gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos)); + + data = NULL; + gtk_tree_model_get (priv->model, &iter, DATA_COLUMN, &data, -1); + + /* If the path isn't local but we're in local-only mode now, remove + * the custom-folder row */ + if (data && + (!gtk_file_system_path_is_local (priv->fs, data) && + gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (priv->dialog)))) + { + pos--; + model_remove_rows (user_data, pos, 2); + } + } + + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model)); + update_combo_box (user_data); + } } static gboolean @@ -1071,6 +2144,7 @@ dialog_response_cb (GtkDialog *dialog, } update_label_and_image (user_data); + update_combo_box (user_data); g_signal_handler_unblock (priv->dialog, priv->dialog_folder_changed_id); @@ -1079,58 +2153,159 @@ dialog_response_cb (GtkDialog *dialog, g_signal_handler_unblock (priv->dialog, priv->dialog_selection_changed_id); priv->active = FALSE; + gtk_widget_set_sensitive (priv->combo_box, TRUE); gtk_widget_hide (priv->dialog); } -static void -button_clicked_cb (GtkButton *real_button, - gpointer user_data) +/* ************************************************************************** * + * Public API * + * ************************************************************************** */ + +/** + * gtk_file_chooser_button_new: + * @title: the title of the browse dialog. + * @action: the open mode for the widget. + * + * Creates a new file-selecting button widget. + * + * Returns: a new button widget. + * + * Since: 2.6 + **/ +GtkWidget * +gtk_file_chooser_button_new (const gchar *title, + GtkFileChooserAction action) { - GtkFileChooserButtonPrivate *priv; + g_return_val_if_fail (action == GTK_FILE_CHOOSER_ACTION_OPEN || + action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, NULL); - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data); + return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, + "action", action, + "title", title, + NULL); +} - if (!priv->active) - { - GSList *paths; +/** + * gtk_file_chooser_button_new_with_backend: + * @title: the title of the browse dialog. + * @action: the open mode for the widget. + * @backend: the name of the #GtkFileSystem backend to use. + * + * Creates a new file-selecting button widget using @backend. + * + * Returns: a new button widget. + * + * Since: 2.6 + **/ +GtkWidget * +gtk_file_chooser_button_new_with_backend (const gchar *title, + GtkFileChooserAction action, + const gchar *backend) +{ + g_return_val_if_fail (action == GTK_FILE_CHOOSER_ACTION_OPEN || + action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, NULL); - /* Setup the dialog parent to be chooser button's toplevel, and be modal - as needed. */ - if (!GTK_WIDGET_VISIBLE (priv->dialog)) - { - GtkWidget *toplevel; + return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, + "title", title, + "action", action, + "file-system-backend", backend, + NULL); +} - toplevel = gtk_widget_get_toplevel (user_data); +/** + * gtk_file_chooser_button_new_with_dialog: + * @dialog: the #GtkDialog widget to use. + * + * Creates a #GtkFileChooserButton widget which uses @dialog as it's + * file-picking window. Note that @dialog must be a #GtkFileChooserDialog (or + * subclass). + * + * Returns: a new button widget. + * + * Since: 2.6 + **/ +GtkWidget * +gtk_file_chooser_button_new_with_dialog (GtkWidget *dialog) +{ + g_return_val_if_fail (GTK_IS_FILE_CHOOSER_DIALOG (dialog), NULL); - if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_IS_WINDOW (toplevel)) - { - if (GTK_WINDOW (toplevel) != gtk_window_get_transient_for (GTK_WINDOW (priv->dialog))) - gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), - GTK_WINDOW (toplevel)); - - gtk_window_set_modal (GTK_WINDOW (priv->dialog), - gtk_window_get_modal (GTK_WINDOW (toplevel))); - } - } + return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, + "dialog", dialog, + NULL); +} - g_signal_handler_block (priv->dialog, - priv->dialog_folder_changed_id); - g_signal_handler_block (priv->dialog, - priv->dialog_file_activated_id); - g_signal_handler_block (priv->dialog, - priv->dialog_selection_changed_id); - paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog)); - if (paths) - { - if (paths->data) - priv->old_path = gtk_file_path_copy (paths->data); +/** + * gtk_file_chooser_button_set_title: + * @button: the button widget to modify. + * @title: the new browse dialog title. + * + * Modifies the @title of the browse dialog used by @button. + * + * Since: 2.6 + **/ +void +gtk_file_chooser_button_set_title (GtkFileChooserButton *button, + const gchar *title) +{ + g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button)); - gtk_file_paths_free (paths); - } + gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title); + g_object_notify (G_OBJECT (button), "title"); +} - priv->active = TRUE; - } +/** + * gtk_file_chooser_button_get_title: + * @button: the button widget to examine. + * + * Retrieves the title of the browse dialog used by @button. The returned value + * should not be modified or freed. + * + * Returns: a pointer to the browse dialog's title. + * + * Since: 2.6 + **/ +G_CONST_RETURN gchar * +gtk_file_chooser_button_get_title (GtkFileChooserButton *button) +{ + g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), NULL); - gtk_window_present (GTK_WINDOW (priv->dialog)); + return gtk_window_get_title (GTK_WINDOW (button->priv->dialog)); +} + +/** + * gtk_file_chooser_button_get_width_chars: + * @button: the button widget to examine. + * + * Retrieves the width in characters of the @button widget's entry and/or label. + * + * Returns: an integer width (in characters) that the button will use to size itself. + * + * Since: 2.6 + **/ +gint +gtk_file_chooser_button_get_width_chars (GtkFileChooserButton *button) +{ + g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), -1); + + return gtk_label_get_width_chars (GTK_LABEL (button->priv->label)); +} + +/** + * gtk_file_chooser_button_set_width_chars: + * @button: the button widget to examine. + * @n_chars: the new width, in chracters. + * + * Sets the width (in characters) that @button will use to @n_chars. + * + * Since: 2.6 + **/ +void +gtk_file_chooser_button_set_width_chars (GtkFileChooserButton *button, + gint n_chars) +{ + g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button)); + + gtk_label_set_width_chars (GTK_LABEL (button->priv->label), n_chars); + g_object_notify (G_OBJECT (button), "width-chars"); } diff --git a/gtk/gtkfilechooserbutton.h b/gtk/gtkfilechooserbutton.h index ba5649076..1bc9d1ba6 100644 --- a/gtk/gtkfilechooserbutton.h +++ b/gtk/gtkfilechooserbutton.h @@ -70,8 +70,10 @@ struct _GtkFileChooserButtonClass GType gtk_file_chooser_button_get_type (void) G_GNUC_CONST; -GtkWidget * gtk_file_chooser_button_new (const gchar *title); +GtkWidget * gtk_file_chooser_button_new (const gchar *title, + GtkFileChooserAction action); GtkWidget * gtk_file_chooser_button_new_with_backend (const gchar *title, + GtkFileChooserAction action, const gchar *backend); GtkWidget * gtk_file_chooser_button_new_with_dialog (GtkWidget *dialog); G_CONST_RETURN gchar *gtk_file_chooser_button_get_title (GtkFileChooserButton *button); diff --git a/tests/testfilechooserbutton.c b/tests/testfilechooserbutton.c index e4774e5ee..00215302d 100644 --- a/tests/testfilechooserbutton.c +++ b/tests/testfilechooserbutton.c @@ -1,3 +1,30 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 2 -*- */ + +/* GTK+: gtkfilechooserbutton.c + * + * Copyright (c) 2004 James M. Cape + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#ifdef HAVE_UNISTD_H +#include +#endif #include #include @@ -9,6 +36,16 @@ #include "prop-editor.h" +static gchar *backend = "gtk+"; +static gboolean rtl = FALSE; +static GOptionEntry entries[] = { + { "backend", 'b', 0, G_OPTION_ARG_STRING, &backend, "The filesystem backend to use.", "gtk+" }, + { "right-to-left", 'r', 0, G_OPTION_ARG_NONE, &rtl, "Force right-to-left layout.", NULL }, + { NULL } +}; + +static gchar *gtk_src_dir = NULL; + static void win_style_set_cb (GtkWidget *win) @@ -67,7 +104,43 @@ print_selected_path_clicked_cb (GtkWidget *button, } static void -tests_button_clicked_cb (GtkWidget *button, +add_pwds_parent_as_shortcut_clicked_cb (GtkWidget *button, + gpointer user_data) +{ + GError *err = NULL; + + if (!gtk_file_chooser_add_shortcut_folder (user_data, gtk_src_dir, &err)) + { + g_message ("Couldn't add `%s' as shortcut folder: %s", gtk_src_dir, + err->message); + g_error_free (err); + } + else + { + g_message ("Added `%s' as shortcut folder.", gtk_src_dir); + } +} + +static void +del_pwds_parent_as_shortcut_clicked_cb (GtkWidget *button, + gpointer user_data) +{ + GError *err = NULL; + + if (!gtk_file_chooser_remove_shortcut_folder (user_data, gtk_src_dir, &err)) + { + g_message ("Couldn't remove `%s' as shortcut folder: %s", gtk_src_dir, + err->message); + g_error_free (err); + } + else + { + g_message ("Removed `%s' as shortcut folder.", gtk_src_dir); + } +} + +static void +tests_button_clicked_cb (GtkButton *real_button, gpointer user_data) { GtkWidget *tests; @@ -76,24 +149,36 @@ tests_button_clicked_cb (GtkWidget *button, if (tests == NULL) { - GtkWidget *box, *button1; + GtkWidget *box, *button; tests = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (tests), "Tests - TestFileChooserButton"); gtk_container_set_border_width (GTK_CONTAINER (tests), 12); gtk_window_set_transient_for (GTK_WINDOW (tests), - GTK_WINDOW (gtk_widget_get_toplevel (button))); + GTK_WINDOW (gtk_widget_get_toplevel (user_data))); box = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (tests), box); gtk_widget_show (box); - button1 = gtk_button_new_with_label ("Print Selected Path"); - g_signal_connect (button1, "clicked", + button = gtk_button_new_with_label ("Print Selected Path"); + g_signal_connect (button, "clicked", G_CALLBACK (print_selected_path_clicked_cb), user_data); - gtk_box_pack_start (GTK_BOX (box), button1, FALSE, FALSE, 0); - gtk_widget_show (button1); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("Add $PWD's Parent as Shortcut"); + g_signal_connect (button, "clicked", + G_CALLBACK (add_pwds_parent_as_shortcut_clicked_cb), user_data); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("Remove $PWD's Parent as Shortcut"); + g_signal_connect (button, "clicked", + G_CALLBACK (del_pwds_parent_as_shortcut_clicked_cb), user_data); + gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0); + gtk_widget_show (button); g_signal_connect (tests, "delete-event", G_CALLBACK (delete_event_cb), NULL); g_object_set_data (user_data, "tests-dialog", tests); @@ -108,8 +193,8 @@ chooser_current_folder_changed_cb (GtkFileChooser *chooser, { gchar *folder, *filename; - folder = gtk_file_chooser_get_current_folder (chooser); - filename = gtk_file_chooser_get_filename (chooser); + folder = gtk_file_chooser_get_current_folder_uri (chooser); + filename = gtk_file_chooser_get_uri (chooser); g_message ("%s::current-folder-changed\n\tFolder: `%s'\n\tFilename: `%s'\nDone.\n", G_OBJECT_TYPE_NAME (chooser), folder, filename); g_free (folder); @@ -122,7 +207,7 @@ chooser_selection_changed_cb (GtkFileChooser *chooser, { gchar *filename; - filename = gtk_file_chooser_get_filename (chooser); + filename = gtk_file_chooser_get_uri (chooser); g_message ("%s::selection-changed\n\tSelection:`%s'\nDone.\n", G_OBJECT_TYPE_NAME (chooser), filename); g_free (filename); @@ -134,8 +219,8 @@ chooser_file_activated_cb (GtkFileChooser *chooser, { gchar *folder, *filename; - folder = gtk_file_chooser_get_current_folder (chooser); - filename = gtk_file_chooser_get_filename (chooser); + folder = gtk_file_chooser_get_current_folder_uri (chooser); + filename = gtk_file_chooser_get_uri (chooser); g_message ("%s::file-activated\n\tFolder: `%s'\n\tFilename: `%s'\nDone.\n", G_OBJECT_TYPE_NAME (chooser), folder, filename); g_free (folder); @@ -148,7 +233,7 @@ chooser_update_preview_cb (GtkFileChooser *chooser, { gchar *filename; - filename = gtk_file_chooser_get_preview_filename (chooser); + filename = gtk_file_chooser_get_preview_uri (chooser); g_message ("%s::update-preview\n\tPreview Filename: `%s'\nDone.\n", G_OBJECT_TYPE_NAME (chooser), filename); g_free (filename); @@ -162,12 +247,24 @@ main (int argc, GtkWidget *win, *vbox, *frame, *alignment, *group_box; GtkWidget *hbox, *label, *chooser, *button; GtkSizeGroup *label_group; + GOptionContext *context; + gchar cwd[2048]; + + context = g_option_context_new ("- test GtkFileChooserButton widget"); + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); + g_option_context_add_group (context, gtk_get_option_group (TRUE)); + g_option_context_parse (context, &argc, &argv, NULL); + g_option_context_free (context); + gtk_init (&argc, &argv); - /* to test rtl layout, set RTL=1 in the environment */ - if (g_getenv ("RTL")) + /* to test rtl layout, use "--right-to-left" */ + if (rtl) gtk_widget_set_default_direction (GTK_TEXT_DIR_RTL); + getcwd (cwd, sizeof (cwd)); + gtk_src_dir = g_path_get_dirname (cwd); + win = gtk_dialog_new_with_buttons ("TestFileChooserButton", NULL, GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_QUIT, GTK_RESPONSE_CLOSE, NULL); g_signal_connect (win, "style-set", G_CALLBACK (win_style_set_cb), NULL); @@ -190,7 +287,7 @@ main (int argc, group_box = gtk_vbox_new (FALSE, 6); gtk_container_add (GTK_CONTAINER (alignment), group_box); - /* open mode */ + /* OPEN */ hbox = gtk_hbox_new (FALSE, 12); gtk_box_pack_start (GTK_BOX (group_box), hbox, FALSE, FALSE, 0); @@ -199,7 +296,11 @@ main (int argc, gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); - chooser = gtk_file_chooser_button_new ("Select A File - testfilechooserbutton"); + chooser = gtk_file_chooser_button_new_with_backend ("Select A File - testfilechooserbutton", + GTK_FILE_CHOOSER_ACTION_OPEN, + backend); + gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (chooser), gtk_src_dir, NULL); + gtk_file_chooser_remove_shortcut_folder (GTK_FILE_CHOOSER (chooser), gtk_src_dir, NULL); gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser); g_signal_connect (chooser, "current-folder-changed", G_CALLBACK (chooser_current_folder_changed_cb), NULL); @@ -216,7 +317,7 @@ main (int argc, g_signal_connect (button, "clicked", G_CALLBACK (tests_button_clicked_cb), chooser); gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); - /* select folder mode */ + /* SELECT_FOLDER */ hbox = gtk_hbox_new (FALSE, 12); gtk_box_pack_start (GTK_BOX (group_box), hbox, FALSE, FALSE, 0); @@ -225,8 +326,12 @@ main (int argc, gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); - chooser = gtk_file_chooser_button_new ("Select A File - testfilechooserbutton"); - gtk_file_chooser_set_action (GTK_FILE_CHOOSER (chooser), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); + chooser = gtk_file_chooser_button_new_with_backend ("Select A Folder - testfilechooserbutton", + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + backend); + gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (chooser), gtk_src_dir, NULL); + gtk_file_chooser_remove_shortcut_folder (GTK_FILE_CHOOSER (chooser), gtk_src_dir, NULL); + gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (chooser), gtk_src_dir, NULL); gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser); g_signal_connect (chooser, "current-folder-changed", G_CALLBACK (chooser_current_folder_changed_cb), NULL); -- 2.43.2