X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkfilechooserbutton.c;h=dfeba8d83ca5c5022fe916b6ed236349c18bb784;hb=10862a344aa8fb44e6045343ddf65eb0d14cc1b2;hp=8105c82efe5f5483aea7804bc6f391a0e4bbb578;hpb=384b1c488601e5efab5f65dbe3bc53d38737cf22;p=~andy%2Fgtk diff --git a/gtk/gtkfilechooserbutton.c b/gtk/gtkfilechooserbutton.c index 8105c82ef..dfeba8d83 100644 --- a/gtk/gtkfilechooserbutton.c +++ b/gtk/gtkfilechooserbutton.c @@ -30,39 +30,47 @@ #include -#include "gtkalias.h" #include "gtkintl.h" +#include "gtkbutton.h" +#include "gtkcelllayout.h" +#include "gtkcellrenderertext.h" +#include "gtkcellrendererpixbuf.h" +#include "gtkcombobox.h" #include "gtkdnd.h" -#include "gtkentry.h" -#include "gtkhbox.h" #include "gtkicontheme.h" #include "gtkiconfactory.h" #include "gtkimage.h" #include "gtklabel.h" +#include "gtkliststore.h" #include "gtkstock.h" -#include "gtktogglebutton.h" +#include "gtktreemodelfilter.h" #include "gtkvseparator.h" #include "gtkfilechooserdialog.h" -#include "gtkfilechooserentry.h" #include "gtkfilechooserprivate.h" #include "gtkfilechooserutils.h" +#include "gtkmarshalers.h" #include "gtkfilechooserbutton.h" +#ifdef G_OS_WIN32 +#include "gtkfilesystemwin32.h" +#endif + +#include "gtkprivate.h" +#include "gtkalias.h" /* **************** * * Private Macros * * **************** */ -#define GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE(object) (GTK_FILE_CHOOSER_BUTTON ((object))->priv) +#define GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_FILE_CHOOSER_BUTTON, GtkFileChooserButtonPrivate)) -#define DEFAULT_FILENAME N_("(None)") -#define MIN_LABEL_WIDTH 100 -#define ENTRY_BUTTON_SPACING 0 -#define FALLBACK_ICON_SIZE 20 +#define DEFAULT_TITLE N_("Select A File") +#define DESKTOP_DISPLAY_NAME N_("Desktop") +#define FALLBACK_DISPLAY_NAME N_("(None)") #define FALLBACK_ICON_NAME "stock_unknown" -#define NEW_FILE_ICON_NAME "stock_new" -#define NEW_DIR_ICON_NAME "stock_new-dir" +#define FALLBACK_ICON_SIZE 16 + /* ********************** * * Private Enumerations * @@ -74,11 +82,47 @@ enum PROP_0, PROP_DIALOG, + PROP_FOCUS_ON_CLICK, PROP_TITLE, - PROP_ACTIVE, PROP_WIDTH_CHARS }; +/* Signals */ +enum +{ + FILE_SET, + LAST_SIGNAL +}; + +/* TreeModel Columns */ +enum +{ + ICON_COLUMN, + DISPLAY_NAME_COLUMN, + TYPE_COLUMN, + DATA_COLUMN, + IS_FOLDER_COLUMN, + HANDLE_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 * @@ -87,24 +131,52 @@ enum struct _GtkFileChooserButtonPrivate { GtkWidget *dialog; - GtkWidget *accept_button; - GtkWidget *entry_box; - GtkWidget *entry_image; - GtkWidget *entry; - GtkWidget *label_box; - GtkWidget *label_image; - GtkWidget *label; GtkWidget *button; + GtkWidget *image; + GtkWidget *label; + GtkWidget *combo_box; + GtkCellRenderer *icon_cell; + GtkCellRenderer *name_cell; + + GtkTreeModel *model; + GtkTreeModel *filter_model; gchar *backend; - gulong entry_changed_id; + 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 dialog_selection_changed_proxy_id; - gulong settings_signal_id; - guint update_id; + gulong fs_volumes_changed_id; + gulong fs_bookmarks_changed_id; + + GtkFileSystemHandle *dnd_select_folder_handle; + GtkFileSystemHandle *update_button_handle; + GSList *change_icon_theme_handles; + 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; + + /* Used to remember whether a title has been set yet, so we can use the default if it has not been set. */ + guint8 has_title : 1; + + /* Used to track whether we need to set a default current folder on ::map() */ + guint8 folder_has_been_set : 1; + + guint8 focus_on_click : 1; }; @@ -118,10 +190,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, @@ -134,6 +216,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); @@ -144,12 +227,13 @@ static void gtk_file_chooser_button_drag_data_received (GtkWidget *wi gint x, gint y, GtkSelectionData *data, - guint info, + guint type, guint drag_time); static void gtk_file_chooser_button_show_all (GtkWidget *widget); static void gtk_file_chooser_button_hide_all (GtkWidget *widget); static void gtk_file_chooser_button_show (GtkWidget *widget); static void gtk_file_chooser_button_hide (GtkWidget *widget); +static void gtk_file_chooser_button_map (GtkWidget *widget); static gboolean gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget, gboolean group_cycling); static void gtk_file_chooser_button_style_set (GtkWidget *widget, @@ -157,56 +241,83 @@ 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_selection_changed_proxy_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 (GtkFileChooser *dialog, - gint response, - gpointer user_data); - -static void button_toggled_cb (GtkToggleButton *real_button, - gpointer user_data); -static void button_notify_active_cb (GObject *real_button, - GParamSpec *pspec, - gpointer user_data); - -static void entry_size_allocate_cb (GtkWidget *entry, - GtkAllocation *allocation, - gpointer user_data); -static void entry_changed_cb (GtkEditable *editable, - gpointer user_data); - /* Utility Functions */ -static void remove_settings_signal (GtkFileChooserButton *button, - GdkScreen *screen); - -static void update_dialog (GtkFileChooserButton *button); -static void update_entry (GtkFileChooserButton *button); -static void update_label (GtkFileChooserButton *button); -static void update_icons (GtkFileChooserButton *button); - +static GtkIconTheme *get_icon_theme (GtkWidget *widget); +static void set_info_for_path_at_iter (GtkFileChooserButton *fs, + const GtkFilePath *path, + GtkTreeIter *iter); + +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); + +static guint file_chooser_button_signals[LAST_SIGNAL] = { 0 }; /* ******************* * * GType Declaration * * ******************* */ 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) \ +}) /* ***************** * @@ -227,6 +338,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; @@ -235,9 +347,30 @@ gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class) widget_class->hide_all = gtk_file_chooser_button_hide_all; widget_class->show = gtk_file_chooser_button_show; widget_class->hide = gtk_file_chooser_button_hide; + widget_class->map = gtk_file_chooser_button_map; widget_class->style_set = gtk_file_chooser_button_style_set; widget_class->screen_changed = gtk_file_chooser_button_screen_changed; widget_class->mnemonic_activate = gtk_file_chooser_button_mnemonic_activate; + + /** + * GtkFileChooserButtons::file-set: + * @widget: the object which received the signal. + * + * The ::file-set signal is emitted when the user selects a file. + * + * Note that this signal is only emitted when the user + * changes the file. + * + * Since: 2.12 + */ + file_chooser_button_signals[FILE_SET] = + g_signal_new (I_("file-set"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkFileChooserButtonClass, file_set), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); /** * GtkFileChooserButton:dialog: @@ -250,10 +383,26 @@ gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class) g_param_spec_object ("dialog", P_("Dialog"), P_("The file chooser dialog to use."), - GTK_TYPE_FILE_CHOOSER_DIALOG, - (G_PARAM_WRITABLE | + GTK_TYPE_FILE_CHOOSER, + (GTK_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY))); + /** + * GtkFileChooserButton:focus-on-click: + * + * Whether the #GtkFileChooserButton button grabs focus when it is clicked + * with the mouse. + * + * Since: 2.10 + */ + g_object_class_install_property (gobject_class, + PROP_FOCUS_ON_CLICK, + g_param_spec_boolean ("focus-on-click", + P_("Focus on click"), + P_("Whether the button grabs focus when it is clicked with the mouse"), + TRUE, + GTK_PARAM_READWRITE)); + /** * GtkFileChooserButton:title: * @@ -265,23 +414,8 @@ gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class) g_param_spec_string ("title", P_("Title"), P_("The title of the file chooser dialog."), - _("Select a File"), - G_PARAM_READWRITE)); - - /** - * GtkFileChooserButton:active: - * - * %TRUE, if the #GtkFileChooserDialog associated with the button has been - * made visible. This can also be set by the application, though it is - * rarely useful to do so. - * - * Since: 2.6 - */ - g_object_class_install_property (gobject_class, PROP_ACTIVE, - g_param_spec_boolean ("active", - P_("Active"), - P_("Whether the browse dialog is visible or not."), - FALSE, G_PARAM_READWRITE)); + _(DEFAULT_TITLE), + GTK_PARAM_READWRITE)); /** * GtkFileChooserButton:width-chars: @@ -295,14 +429,13 @@ gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class) P_("Width In Characters"), P_("The desired width of the button widget, in characters."), -1, G_MAXINT, -1, - G_PARAM_READWRITE)); + GTK_PARAM_READWRITE)); _gtk_file_chooser_install_properties (gobject_class); g_type_class_add_private (class, sizeof (GtkFileChooserButtonPrivate)); } - static void gtk_file_chooser_button_init (GtkFileChooserButton *button) { @@ -310,70 +443,78 @@ gtk_file_chooser_button_init (GtkFileChooserButton *button) GtkWidget *box, *image, *sep; GtkTargetList *target_list; - gtk_box_set_spacing (GTK_BOX (button), ENTRY_BUTTON_SPACING); - - priv = G_TYPE_INSTANCE_GET_PRIVATE (button, GTK_TYPE_FILE_CHOOSER_BUTTON, - GtkFileChooserButtonPrivate); - button->priv = priv; + priv = button->priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button); priv->icon_size = FALLBACK_ICON_SIZE; + priv->focus_on_click = TRUE; gtk_widget_push_composite_child (); - priv->entry_box = gtk_hbox_new (FALSE, 4); - gtk_container_add (GTK_CONTAINER (button), priv->entry_box); - - priv->entry_image = gtk_image_new (); - gtk_box_pack_start (GTK_BOX (priv->entry_box), priv->entry_image, - FALSE, FALSE, 0); - gtk_widget_show (priv->entry_image); - - priv->entry = _gtk_file_chooser_entry_new (FALSE); - gtk_container_add (GTK_CONTAINER (priv->entry_box), priv->entry); - gtk_widget_show (priv->entry); - - priv->button = gtk_toggle_button_new (); - g_signal_connect (priv->button, "toggled", - G_CALLBACK (button_toggled_cb), button); - g_signal_connect (priv->button, "notify::active", - G_CALLBACK (button_notify_active_cb), button); - g_signal_connect (priv->entry, "size-allocate", - G_CALLBACK (entry_size_allocate_cb), priv->button); - gtk_box_pack_start (GTK_BOX (button), priv->button, TRUE, TRUE, 0); + /* Button */ + priv->button = gtk_button_new (); + g_signal_connect (priv->button, "clicked", G_CALLBACK (button_clicked_cb), + button); + gtk_container_add (GTK_CONTAINER (button), priv->button); gtk_widget_show (priv->button); box = gtk_hbox_new (FALSE, 4); gtk_container_add (GTK_CONTAINER (priv->button), box); gtk_widget_show (box); - - priv->label_box = gtk_hbox_new (FALSE, 6); - gtk_container_add (GTK_CONTAINER (box), priv->label_box); - gtk_widget_show (priv->label_box); - priv->label_image = gtk_image_new (); - gtk_box_pack_start (GTK_BOX (priv->label_box), priv->label_image, - FALSE, FALSE, 0); - gtk_widget_show (priv->label_image); + priv->image = gtk_image_new (); + 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 (priv->label_box), priv->label); + gtk_container_add (GTK_CONTAINER (box), priv->label); gtk_widget_show (priv->label); sep = gtk_vseparator_new (); - gtk_box_pack_end (GTK_BOX (priv->label_box), sep, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box), sep, FALSE, FALSE, 0); gtk_widget_show (sep); image = gtk_image_new_from_stock (GTK_STOCK_OPEN, - GTK_ICON_SIZE_SMALL_TOOLBAR); - gtk_box_pack_end (GTK_BOX (box), image, FALSE, FALSE, 0); + GTK_ICON_SIZE_MENU); + 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 */, + G_TYPE_BOOLEAN /* Is Folder? */, + G_TYPE_POINTER /* handle */)); + + 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 */ - gtk_drag_dest_unset (priv->entry); gtk_drag_dest_set (GTK_WIDGET (button), (GTK_DEST_DEFAULT_ALL), NULL, 0, @@ -386,6 +527,110 @@ 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) + { + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser); + GtkFileChooserButtonPrivate *priv = button->priv; + GtkTreeIter iter; + gint pos; + + pos = model_get_type_position (button, ROW_TYPE_SHORTCUT); + pos += priv->n_shortcuts; + + gtk_list_store_insert (GTK_LIST_STORE (priv->model), &iter, pos); + gtk_list_store_set (GTK_LIST_STORE (priv->model), &iter, + ICON_COLUMN, NULL, + DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME), + TYPE_COLUMN, ROW_TYPE_SHORTCUT, + DATA_COLUMN, gtk_file_path_copy (path), + IS_FOLDER_COLUMN, FALSE, + -1); + set_info_for_path_at_iter (button, path, &iter); + priv->n_shortcuts++; + + 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_remove_shortcut_folder (delegate, path, error); + + if (retval) + { + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (chooser); + GtkFileChooserButtonPrivate *priv = button->priv; + GtkTreeIter iter; + gint pos; + gchar type; + + pos = model_get_type_position (button, ROW_TYPE_SHORTCUT); + 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 * * ******************* */ @@ -396,43 +641,60 @@ gtk_file_chooser_button_constructor (GType type, GObjectConstructParam *params) { GObject *object; + GtkFileChooserButton *button; GtkFileChooserButtonPrivate *priv; - GtkFilePath *path; + GSList *list; + char *current_folder; object = (*G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructor) (type, n_params, params); - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object); + button = GTK_FILE_CHOOSER_BUTTON (object); + priv = button->priv; if (!priv->dialog) { if (priv->backend) priv->dialog = gtk_file_chooser_dialog_new_with_backend (NULL, NULL, GTK_FILE_CHOOSER_ACTION_OPEN, - priv->backend, NULL); + priv->backend, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, + GTK_RESPONSE_ACCEPT, + NULL); else priv->dialog = gtk_file_chooser_dialog_new (NULL, NULL, GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, + GTK_RESPONSE_ACCEPT, NULL); - gtk_dialog_add_button (GTK_DIALOG (priv->dialog), - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); - priv->accept_button = gtk_dialog_add_button (GTK_DIALOG (priv->dialog), - GTK_STOCK_OPEN, - GTK_RESPONSE_ACCEPT); gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog), GTK_RESPONSE_ACCEPT); - gtk_dialog_set_alternative_button_order (GTK_DIALOG (priv->dialog), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_CANCEL, -1); } + /* Set the default title if necessary. We must wait until the dialog has been created to do this. */ + if (!priv->has_title) + gtk_file_chooser_button_set_title (button, _(DEFAULT_TITLE)); + + current_folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (priv->dialog)); + if (current_folder != NULL) + { + priv->folder_has_been_set = TRUE; + g_free (current_folder); + } + g_free (priv->backend); priv->backend = NULL; - g_signal_connect (priv->dialog, "delete-event", + g_signal_connect (priv->dialog, "delete_event", G_CALLBACK (dialog_delete_event_cb), object); g_signal_connect (priv->dialog, "response", G_CALLBACK (dialog_response_cb), object); @@ -449,9 +711,6 @@ gtk_file_chooser_button_constructor (GType type, priv->dialog_selection_changed_id = g_signal_connect (priv->dialog, "selection-changed", G_CALLBACK (dialog_selection_changed_cb), object); - priv->dialog_selection_changed_proxy_id = - g_signal_connect (priv->dialog, "selection-changed", - G_CALLBACK (dialog_selection_changed_proxy_cb), object); g_signal_connect (priv->dialog, "update-preview", G_CALLBACK (dialog_update_preview_cb), object); g_signal_connect (priv->dialog, "notify", @@ -459,16 +718,44 @@ gtk_file_chooser_button_constructor (GType type, g_object_add_weak_pointer (G_OBJECT (priv->dialog), (gpointer *) (&priv->dialog)); - _gtk_file_chooser_entry_set_file_system (GTK_FILE_CHOOSER_ENTRY (priv->entry), - _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog))); - path = gtk_file_path_new_steal ("/"); - _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->entry), - path); - priv->entry_changed_id = g_signal_connect (priv->entry, "changed", - G_CALLBACK (entry_changed_cb), - object); + priv->fs = + g_object_ref (_gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog))); + + model_add_special (button); + + list = gtk_file_system_list_volumes (priv->fs); + model_add_volumes (button, list); + g_slist_free (list); + + list = gtk_file_system_list_bookmarks (priv->fs); + model_add_bookmarks (button, list); + gtk_file_paths_free (list); + + model_add_other (button); + + 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); - update_label (GTK_FILE_CHOOSER_BUTTON (object)); + 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); + + /* set up the action for a user-provided dialog, this also updates + * the label, image and combobox + */ + g_object_set (object, + "action", gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)), + NULL); + + 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; } @@ -479,9 +766,8 @@ gtk_file_chooser_button_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - GtkFileChooserButtonPrivate *priv; - - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object); + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object); + GtkFileChooserButtonPrivate *priv = button->priv; switch (param_id) { @@ -489,15 +775,13 @@ gtk_file_chooser_button_set_property (GObject *object, /* Construct-only */ priv->dialog = g_value_get_object (value); break; - case PROP_ACTIVE: - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), - g_value_get_boolean (value)); + case PROP_FOCUS_ON_CLICK: + gtk_file_chooser_button_set_focus_on_click (button, g_value_get_boolean (value)); break; case PROP_WIDTH_CHARS: gtk_file_chooser_button_set_width_chars (GTK_FILE_CHOOSER_BUTTON (object), g_value_get_int (value)); break; - case GTK_FILE_CHOOSER_PROP_ACTION: switch (g_value_get_enum (value)) { @@ -509,56 +793,52 @@ gtk_file_chooser_button_set_property (GObject *object, eclass = g_type_class_peek (GTK_TYPE_FILE_CHOOSER_ACTION); eval = g_enum_get_value (eclass, g_value_get_enum (value)); - g_warning ("%s: Choosers of type `%s` do not support `%s'.", + g_warning ("%s: Choosers of type `%s' do not support `%s'.", G_STRFUNC, G_OBJECT_TYPE_NAME (object), eval->value_name); - g_value_set_enum (value, GTK_FILE_CHOOSER_ACTION_OPEN); + g_value_set_enum ((GValue *) value, GTK_FILE_CHOOSER_ACTION_OPEN); } break; } - + g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value); - _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (priv->entry), - (GtkFileChooserAction) g_value_get_enum (value)); - update_icons (GTK_FILE_CHOOSER_BUTTON (object)); + 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_SELECT_FOLDER: - gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog)); - /* Fall through to set the widget states */ case GTK_FILE_CHOOSER_ACTION_OPEN: - gtk_widget_hide (priv->entry_box); - gtk_widget_show (priv->label_box); - gtk_box_set_child_packing (GTK_BOX (object), priv->button, - TRUE, TRUE, 0, GTK_PACK_START); - gtk_button_set_label (GTK_BUTTON (priv->accept_button), - GTK_STOCK_OPEN); + gtk_widget_hide (priv->combo_box); + gtk_widget_show (priv->button); break; - - case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER: - gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog)); - /* Fall through to set the widget states */ - case GTK_FILE_CHOOSER_ACTION_SAVE: - gtk_widget_show (priv->entry_box); - gtk_widget_hide (priv->label_box); - gtk_box_set_child_packing (GTK_BOX (object), priv->button, - FALSE, FALSE, 0, GTK_PACK_START); - gtk_button_set_label (GTK_BUTTON (priv->accept_button), - GTK_STOCK_SAVE); + 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: + /* Remember that a title has been set, so we do no try to set it to the default in _init(). */ + priv->has_title = TRUE; + /* Intentionally fall through instead of breaking here, to actually set the property. */ case GTK_FILE_CHOOSER_PROP_FILTER: - case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY: case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET: case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE: case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL: case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET: case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN: + case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION: + g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value); + break; + + case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY: g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value); + fs_volumes_changed_cb (priv->fs, button); + fs_bookmarks_changed_cb (priv->fs, button); break; case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND: @@ -582,19 +862,18 @@ gtk_file_chooser_button_get_property (GObject *object, GValue *value, GParamSpec *pspec) { - GtkFileChooserButtonPrivate *priv; - - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object); + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object); + GtkFileChooserButtonPrivate *priv = button->priv; switch (param_id) { - case PROP_ACTIVE: - g_value_set_boolean (value, - gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button))); - break; case PROP_WIDTH_CHARS: g_value_set_int (value, - gtk_entry_get_width_chars (GTK_ENTRY (priv->entry))); + gtk_label_get_width_chars (GTK_LABEL (priv->label))); + break; + case PROP_FOCUS_ON_CLICK: + g_value_set_boolean (value, + gtk_file_chooser_button_get_focus_on_click (button)); break; case PROP_TITLE: @@ -608,6 +887,7 @@ gtk_file_chooser_button_get_property (GObject *object, case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET: case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE: case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN: + case GTK_FILE_CHOOSER_PROP_DO_OVERWRITE_CONFIRMATION: g_object_get_property (G_OBJECT (priv->dialog), pspec->name, value); break; @@ -617,26 +897,85 @@ gtk_file_chooser_button_get_property (GObject *object, } } +static void +gtk_file_chooser_button_finalize (GObject *object) +{ + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object); + GtkFileChooserButtonPrivate *priv = button->priv; + + if (priv->old_path) + gtk_file_path_free (priv->old_path); + + 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; + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object); + GtkFileChooserButtonPrivate *priv = button->priv; + GtkTreeIter iter; + GSList *l; - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object); + if (priv->dialog != NULL) + { + gtk_widget_destroy (priv->dialog); + priv->dialog = NULL; + } - if (priv->update_id) - g_source_remove (priv->update_id); + if (priv->model && gtk_tree_model_get_iter_first (priv->model, &iter)) do + { + model_free_row_data (button, &iter); + } + while (gtk_tree_model_iter_next (priv->model, &iter)); - if (priv->dialog != NULL) - gtk_widget_destroy (priv->dialog); - - remove_settings_signal (GTK_FILE_CHOOSER_BUTTON (object), - gtk_widget_get_screen (GTK_WIDGET (object))); + if (priv->dnd_select_folder_handle) + { + gtk_file_system_cancel_operation (priv->dnd_select_folder_handle); + priv->dnd_select_folder_handle = NULL; + } + + if (priv->update_button_handle) + { + gtk_file_system_cancel_operation (priv->update_button_handle); + priv->update_button_handle = NULL; + } + + if (priv->change_icon_theme_handles) + { + for (l = priv->change_icon_theme_handles; l; l = l->next) + { + GtkFileSystemHandle *handle = GTK_FILE_SYSTEM_HANDLE (l->data); + gtk_file_system_cancel_operation (handle); + } + g_slist_free (priv->change_icon_theme_handles); + priv->change_icon_theme_handles = NULL; + } + + if (priv->model) + { + g_object_unref (priv->model); + priv->model = NULL; + } + + if (priv->filter_model) + { + g_object_unref (priv->filter_model); + priv->filter_model = NULL; + } + + if (priv->fs) + { + 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); + priv->fs = NULL; + } if (GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy != NULL) (*GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy) (object); @@ -647,100 +986,141 @@ gtk_file_chooser_button_destroy (GtkObject * object) * GtkWidget Functions * * ********************* */ +struct DndSelectFolderData +{ + GtkFileChooserButton *button; + GtkFileChooserAction action; + GtkFilePath *path; + gchar **uris; + guint i; + gboolean selected; +}; + +static void +dnd_select_folder_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = handle->cancelled; + struct DndSelectFolderData *data = user_data; + + if (handle != data->button->priv->dnd_select_folder_handle) + { + g_object_unref (data->button); + gtk_file_path_free (data->path); + g_strfreev (data->uris); + g_free (data); + + g_object_unref (handle); + return; + } + + data->button->priv->dnd_select_folder_handle = NULL; + + if (!cancelled && !error && info != NULL) + { + data->selected = + (((data->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER && + gtk_file_info_get_is_folder (info)) || + (data->action == GTK_FILE_CHOOSER_ACTION_OPEN && + !gtk_file_info_get_is_folder (info))) && + _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (data->button->priv->dialog), + data->path, NULL)); + } + else + data->selected = FALSE; + + if (data->selected || data->uris[++data->i] == NULL) + { + g_object_unref (data->button); + gtk_file_path_free (data->path); + g_strfreev (data->uris); + g_free (data); + + g_object_unref (handle); + return; + } + + if (data->path) + gtk_file_path_free (data->path); + + data->path = gtk_file_system_uri_to_path (handle->file_system, + data->uris[data->i]); + + data->button->priv->dnd_select_folder_handle = + gtk_file_system_get_info (handle->file_system, data->path, + GTK_FILE_INFO_IS_FOLDER, + dnd_select_folder_get_info_cb, user_data); + + g_object_unref (handle); +} + static void gtk_file_chooser_button_drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, - guint info, + guint type, guint drag_time) { - GtkFileChooserButtonPrivate *priv; + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget); + GtkFileChooserButtonPrivate *priv = button->priv; + GtkFilePath *path; gchar *text; if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received != NULL) (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received) (widget, context, x, y, - data, info, + data, type, drag_time); if (widget == NULL || context == NULL || data == NULL || data->length < 0) return; - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget); - - switch (info) + switch (type) { case TEXT_URI_LIST: { gchar **uris; - guint i; - gboolean selected; + struct DndSelectFolderData *info; uris = gtk_selection_data_get_uris (data); if (uris == NULL) break; - selected = FALSE; - for (i = 0; !selected && uris[i] != NULL; i++) - { - GtkFileSystem *fs; - GtkFilePath *path, *base_path; - - fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)); - path = gtk_file_system_uri_to_path (fs, uris[i]); + info = g_new0 (struct DndSelectFolderData, 1); + info->button = g_object_ref (button); + info->i = 0; + info->uris = uris; + info->selected = FALSE; + g_object_get (priv->dialog, "action", &info->action, NULL); - base_path = NULL; - if (path != NULL && - gtk_file_system_get_parent (fs, path, &base_path, NULL)) - { - GtkFileFolder *folder; - GtkFileInfo *info; - - folder = gtk_file_system_get_folder (fs, base_path, - GTK_FILE_INFO_IS_FOLDER, - NULL); - - info = gtk_file_folder_get_info (folder, path, NULL); - - if (info != NULL) - { - GtkFileChooserAction action; - - g_object_get (priv->dialog, "action", &action, NULL); - - selected = - ((((action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER || - action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) && - gtk_file_info_get_is_folder (info)) || - ((action == GTK_FILE_CHOOSER_ACTION_OPEN || - action == GTK_FILE_CHOOSER_ACTION_SAVE) && - !gtk_file_info_get_is_folder (info))) && - _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog), - path, NULL)); - - gtk_file_info_free (info); - } - else - selected = FALSE; - - gtk_file_path_free (base_path); - } + info->path = gtk_file_system_uri_to_path (priv->fs, + info->uris[info->i]); - gtk_file_path_free (path); - } + if (priv->dnd_select_folder_handle) + gtk_file_system_cancel_operation (priv->dnd_select_folder_handle); - g_strfreev (uris); + priv->dnd_select_folder_handle = + gtk_file_system_get_info (priv->fs, info->path, + GTK_FILE_INFO_IS_FOLDER, + dnd_select_folder_get_info_cb, info); } break; case TEXT_PLAIN: - text = gtk_selection_data_get_text (data); - gtk_entry_set_text (GTK_ENTRY (priv->entry), text); - g_free (text); + text = (char*) gtk_selection_data_get_text (data); + path = gtk_file_path_new_steal (text); + _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog), path, + NULL); + gtk_file_path_free (path); + break; + + default: break; } @@ -762,23 +1142,21 @@ gtk_file_chooser_button_hide_all (GtkWidget *widget) static void gtk_file_chooser_button_show (GtkWidget *widget) { - GtkFileChooserButtonPrivate *priv; - - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget); + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget); + GtkFileChooserButtonPrivate *priv = button->priv; if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show) (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show) (widget); - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button))) - gtk_widget_show (priv->dialog); + if (priv->active) + open_dialog (GTK_FILE_CHOOSER_BUTTON (widget)); } static void gtk_file_chooser_button_hide (GtkWidget *widget) { - GtkFileChooserButtonPrivate *priv; - - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget); + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget); + GtkFileChooserButtonPrivate *priv = button->priv; gtk_widget_hide (priv->dialog); @@ -786,23 +1164,44 @@ gtk_file_chooser_button_hide (GtkWidget *widget) (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide) (widget); } +static void +gtk_file_chooser_button_map (GtkWidget *widget) +{ + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget); + GtkFileChooserButtonPrivate *priv = button->priv; + + if (!priv->folder_has_been_set) + { + char *current_working_dir; + + current_working_dir = g_get_current_dir (); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget), current_working_dir); + g_free (current_working_dir); + + priv->folder_has_been_set = TRUE; + } + + if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->map) + (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->map) (widget); +} + static gboolean gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget, gboolean group_cycling) { - GtkFileChooserButtonPrivate *priv; - - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget); + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (widget); + GtkFileChooserButtonPrivate *priv = button->priv; switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog))) { case GTK_FILE_CHOOSER_ACTION_OPEN: - case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER: gtk_widget_grab_focus (priv->button); break; - case GTK_FILE_CHOOSER_ACTION_SAVE: - case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER: - gtk_widget_grab_focus (priv->entry); + case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER: + return gtk_widget_mnemonic_activate (priv->combo_box, group_cycling); + break; + default: + g_assert_not_reached (); break; } @@ -810,64 +1209,188 @@ gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget, } /* Changes the icons wherever it is needed */ +struct ChangeIconThemeData +{ + GtkFileChooserButton *button; + GtkTreeRowReference *row_ref; +}; + static void -change_icon_theme (GtkFileChooserButton *button) +change_icon_theme_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) { - GtkSettings *settings; - gint width, height; + gboolean cancelled = handle->cancelled; + GdkPixbuf *pixbuf; + struct ChangeIconThemeData *data = user_data; - settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button))); + if (!g_slist_find (data->button->priv->change_icon_theme_handles, handle)) + goto out; - if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_SMALL_TOOLBAR, - &width, &height)) - button->priv->icon_size = MAX (width, height); - else - button->priv->icon_size = FALLBACK_ICON_SIZE; + data->button->priv->change_icon_theme_handles = + g_slist_remove (data->button->priv->change_icon_theme_handles, handle); - update_icons (button); -} + if (cancelled || error) + goto out; -/* Callback used when a GtkSettings value changes */ -static void -settings_notify_cb (GObject *object, - GParamSpec *pspec, - gpointer user_data) -{ - const char *name; + pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (data->button), + data->button->priv->icon_size, NULL); + + if (pixbuf) + { + gint width = 0; + GtkTreeIter iter; + GtkTreePath *path; + + width = MAX (width, gdk_pixbuf_get_width (pixbuf)); + + path = gtk_tree_row_reference_get_path (data->row_ref); + if (path) + { + gtk_tree_model_get_iter (data->button->priv->model, &iter, path); + gtk_tree_path_free (path); + + gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter, + ICON_COLUMN, pixbuf, + -1); + + g_object_set (data->button->priv->icon_cell, + "width", width, + NULL); + } + g_object_unref (pixbuf); + } - name = g_param_spec_get_name (pspec); +out: + g_object_unref (data->button); + gtk_tree_row_reference_free (data->row_ref); + g_free (data); - if (strcmp (name, "gtk-icon-theme-name") == 0 - || strcmp (name, "gtk-icon-sizes") == 0) - change_icon_theme (user_data); + g_object_unref (handle); } -/* Installs a signal handler for GtkSettings so that we can monitor changes in - * the icon theme. - */ static void -check_icon_theme (GtkFileChooserButton *button) +change_icon_theme (GtkFileChooserButton *button) { + GtkFileChooserButtonPrivate *priv = button->priv; GtkSettings *settings; + GtkIconTheme *theme; + GtkTreeIter iter; + GSList *l; + gint width = 0, height = 0; - if (button->priv->settings_signal_id) - return; - - if (gtk_widget_has_screen (GTK_WIDGET (button))) + for (l = button->priv->change_icon_theme_handles; l; l = l->next) { - settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button))); - button->priv->settings_signal_id = g_signal_connect (settings, "notify", - G_CALLBACK (settings_notify_cb), - button); - - change_icon_theme (button); + GtkFileSystemHandle *handle = GTK_FILE_SYSTEM_HANDLE (l->data); + gtk_file_system_cancel_operation (handle); } -} + g_slist_free (button->priv->change_icon_theme_handles); + button->priv->change_icon_theme_handles = NULL; -static void -gtk_file_chooser_button_style_set (GtkWidget *widget, - GtkStyle *old_style) -{ + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button))); + + if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU, + &width, &height)) + priv->icon_size = MAX (width, height); + else + priv->icon_size = FALLBACK_ICON_SIZE; + + update_label_and_image (button); + + 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) + { + if (gtk_file_system_path_is_local (priv->fs, (GtkFilePath *)data)) + { + GtkTreePath *path; + GtkFileSystemHandle *handle; + struct ChangeIconThemeData *info; + + info = g_new0 (struct ChangeIconThemeData, 1); + info->button = g_object_ref (button); + path = gtk_tree_model_get_path (priv->model, &iter); + info->row_ref = gtk_tree_row_reference_new (priv->model, path); + gtk_tree_path_free (path); + + handle = + gtk_file_system_get_info (priv->fs, data, GTK_FILE_INFO_ICON, + change_icon_theme_get_info_cb, + info); + button->priv->change_icon_theme_handles = + g_slist_append (button->priv->change_icon_theme_handles, handle); + pixbuf = NULL; + } + else + /* Don't call get_info for remote paths to avoid latency and + * auth dialogs. + * If we switch to a better bookmarks file format (XBEL), we + * should use mime info to get a better icon. + */ + pixbuf = gtk_icon_theme_load_icon (theme, "gnome-fs-regular", + priv->icon_size, 0, 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); + + if (pixbuf) + g_object_unref (pixbuf); + } + 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) +{ 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); @@ -880,447 +1403,1004 @@ 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); - remove_settings_signal (button, old_screen); - check_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)); + + return gtk_icon_theme_get_default (); } -/** - * 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) + +struct SetDisplayNameData { - return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, - "title", title, - "file-system-backend", backend, - NULL); -} + GtkFileChooserButton *button; + char *label; + GtkTreeRowReference *row_ref; +}; -/** - * 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 void +set_info_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer callback_data) { - g_return_val_if_fail (GTK_IS_FILE_CHOOSER_DIALOG (dialog), NULL); + gboolean cancelled = handle->cancelled; + GdkPixbuf *pixbuf; + GtkTreePath *path; + GtkTreeIter iter; + GtkFileSystemHandle *model_handle; + struct SetDisplayNameData *data = callback_data; + + if (!data->button->priv->model) + /* button got destroyed */ + goto out; + + path = gtk_tree_row_reference_get_path (data->row_ref); + if (!path) + /* Handle doesn't exist anymore in the model */ + goto out; + + gtk_tree_model_get_iter (data->button->priv->model, &iter, path); + gtk_tree_path_free (path); + + /* Validate the handle */ + gtk_tree_model_get (data->button->priv->model, &iter, + HANDLE_COLUMN, &model_handle, + -1); + if (handle != model_handle) + goto out; + + gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter, + HANDLE_COLUMN, NULL, + -1); + + if (cancelled || error) + /* There was an error, leave the fallback name in there */ + goto out; + + pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (data->button), + data->button->priv->icon_size, NULL); + + if (!data->label) + data->label = g_strdup (gtk_file_info_get_display_name (info)); + + gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter, + ICON_COLUMN, pixbuf, + DISPLAY_NAME_COLUMN, data->label, + IS_FOLDER_COLUMN, gtk_file_info_get_is_folder (info), + -1); - return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, - "dialog", dialog, - NULL); -} + if (pixbuf) + g_object_unref (pixbuf); -/** - * 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)); +out: + g_object_unref (data->button); + g_free (data->label); + gtk_tree_row_reference_free (data->row_ref); + g_free (data); - gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title); - g_object_notify (G_OBJECT (button), "title"); + g_object_unref (handle); } -/** - * 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) +static void +set_info_for_path_at_iter (GtkFileChooserButton *button, + const GtkFilePath *path, + GtkTreeIter *iter) { - g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), NULL); + struct SetDisplayNameData *data; + GtkTreePath *tree_path; + GtkFileSystemHandle *handle; - return gtk_window_get_title (GTK_WINDOW (button->priv->dialog)); -} + data = g_new0 (struct SetDisplayNameData, 1); + data->button = g_object_ref (button); + data->label = gtk_file_system_get_bookmark_label (button->priv->fs, path); -/** - * gtk_file_chooser_button_set_active: - * @button: the button widget to modify. - * @is_active: whether or not the dialog is visible. - * - * Modifies whether or not the dialog attached to @button is visible or not. - * - * Since: 2.6 - **/ -void -gtk_file_chooser_button_set_active (GtkFileChooserButton *button, - gboolean is_active) -{ - g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button)); + tree_path = gtk_tree_model_get_path (button->priv->model, iter); + data->row_ref = gtk_tree_row_reference_new (button->priv->model, tree_path); + gtk_tree_path_free (tree_path); + + handle = gtk_file_system_get_info (button->priv->fs, path, + GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_FOLDER | GTK_FILE_INFO_ICON, + set_info_get_info_cb, data); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button->priv->button), is_active); + gtk_list_store_set (GTK_LIST_STORE (button->priv->model), iter, + HANDLE_COLUMN, handle, + -1); } -/** - * gtk_file_chooser_button_get_active: - * @button: the button widget to examine. - * - * Retrieves whether or not the dialog attached to @button is visible. - * - * Returns: a boolean whether the dialog is visible or not. - * - * Since: 2.6 - **/ -gboolean -gtk_file_chooser_button_get_active (GtkFileChooserButton *button) +/* Shortcuts Model */ +static gint +model_get_type_position (GtkFileChooserButton *button, + RowType row_type) { - g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), FALSE); + gint retval = 0; + + if (row_type == ROW_TYPE_SPECIAL) + return retval; + + retval += button->priv->n_special; + + if (row_type == ROW_TYPE_VOLUME) + return retval; - return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button->priv->button)); + 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; } -/** - * 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) +static void +model_free_row_data (GtkFileChooserButton *button, + GtkTreeIter *iter) { - g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), -1); + gchar type; + gpointer data; + GtkFileSystemHandle *handle; - return gtk_entry_get_width_chars (GTK_ENTRY (button->priv->entry)); + gtk_tree_model_get (button->priv->model, iter, + TYPE_COLUMN, &type, + DATA_COLUMN, &data, + HANDLE_COLUMN, &handle, + -1); + + if (handle) + gtk_file_system_cancel_operation (handle); + + 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; + } } -/** - * 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) +static void +model_add_special_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) { - g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button)); + gboolean cancelled = handle->cancelled; + GtkTreeIter iter; + GtkTreePath *path; + GdkPixbuf *pixbuf; + GtkFileSystemHandle *model_handle; + struct ChangeIconThemeData *data = user_data; - gtk_entry_set_width_chars (GTK_ENTRY (button->priv->entry), n_chars); - gtk_label_set_width_chars (GTK_LABEL (button->priv->label), n_chars); - g_object_notify (G_OBJECT (button), "width-chars"); -} + if (!data->button->priv->model) + /* button got destroyed */ + goto out; + path = gtk_tree_row_reference_get_path (data->row_ref); + if (!path) + /* Handle doesn't exist anymore in the model */ + goto out; -/* ******************* * - * Utility Functions * - * ******************* */ + gtk_tree_model_get_iter (data->button->priv->model, &iter, path); + gtk_tree_path_free (path); -/* Removes the settings signal handler. It's safe to call multiple times */ -static void -remove_settings_signal (GtkFileChooserButton *button, - GdkScreen *screen) + gtk_tree_model_get (data->button->priv->model, &iter, + HANDLE_COLUMN, &model_handle, + -1); + if (handle != model_handle) + goto out; + + gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter, + HANDLE_COLUMN, NULL, + -1); + + if (cancelled || error) + goto out; + + pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (data->button), + data->button->priv->icon_size, NULL); + + if (pixbuf) + { + gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter, + ICON_COLUMN, pixbuf, + -1); + g_object_unref (pixbuf); + } + + gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter, + DISPLAY_NAME_COLUMN, gtk_file_info_get_display_name (info), + -1); + +out: + g_object_unref (data->button); + gtk_tree_row_reference_free (data->row_ref); + g_free (data); + + g_object_unref (handle); +} + +static inline void +model_add_special (GtkFileChooserButton *button) { - if (button->priv->settings_signal_id) + const gchar *homedir; + gchar *desktopdir = NULL; + GtkListStore *store; + GtkTreeIter iter; + GtkFilePath *path; + gint pos; + + store = GTK_LIST_STORE (button->priv->model); + pos = model_get_type_position (button, ROW_TYPE_SPECIAL); + + homedir = g_get_home_dir (); + + if (homedir) { - GtkSettings *settings; + GtkTreePath *tree_path; + GtkFileSystemHandle *handle; + struct ChangeIconThemeData *info; + + path = gtk_file_system_filename_to_path (button->priv->fs, homedir); + gtk_list_store_insert (store, &iter, pos); + pos++; + + info = g_new0 (struct ChangeIconThemeData, 1); + info->button = g_object_ref (button); + tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store), + tree_path); + gtk_tree_path_free (tree_path); + + handle = gtk_file_system_get_info (button->priv->fs, path, + GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_ICON, + model_add_special_get_info_cb, info); + + gtk_list_store_set (store, &iter, + ICON_COLUMN, NULL, + DISPLAY_NAME_COLUMN, NULL, + TYPE_COLUMN, ROW_TYPE_SPECIAL, + DATA_COLUMN, path, + IS_FOLDER_COLUMN, TRUE, + HANDLE_COLUMN, handle, + -1); + + button->priv->n_special++; + +#ifndef G_OS_WIN32 + desktopdir = g_build_filename (homedir, DESKTOP_DISPLAY_NAME, NULL); +#endif + } + +#ifdef G_OS_WIN32 + desktopdir = _gtk_file_system_win32_get_desktop (); +#endif - settings = gtk_settings_get_for_screen (screen); - g_signal_handler_disconnect (settings, - button->priv->settings_signal_id); - button->priv->settings_signal_id = 0; + if (desktopdir) + { + GtkTreePath *tree_path; + GtkFileSystemHandle *handle; + struct ChangeIconThemeData *info; + + path = gtk_file_system_filename_to_path (button->priv->fs, desktopdir); + g_free (desktopdir); + gtk_list_store_insert (store, &iter, pos); + pos++; + + info = g_new0 (struct ChangeIconThemeData, 1); + info->button = g_object_ref (button); + tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store), + tree_path); + gtk_tree_path_free (tree_path); + + handle = gtk_file_system_get_info (button->priv->fs, path, + GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_ICON, + model_add_special_get_info_cb, info); + + gtk_list_store_set (store, &iter, + TYPE_COLUMN, ROW_TYPE_SPECIAL, + ICON_COLUMN, NULL, + DISPLAY_NAME_COLUMN, _(DESKTOP_DISPLAY_NAME), + DATA_COLUMN, path, + IS_FOLDER_COLUMN, TRUE, + HANDLE_COLUMN, handle, + -1); + + button->priv->n_special++; } } -static GtkIconTheme * -get_icon_theme (GtkWidget *widget) +static void +model_add_volumes (GtkFileChooserButton *button, + GSList *volumes) { - if (gtk_widget_has_screen (widget)) - return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); + GtkListStore *store; + gint pos; + gboolean local_only; + GtkFileSystem *file_system; + GSList *l; + + if (!volumes) + return; - return gtk_icon_theme_get_default (); + store = GTK_LIST_STORE (button->priv->model); + pos = model_get_type_position (button, ROW_TYPE_VOLUME); + local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (button->priv->dialog)); + file_system = button->priv->fs; + + for (l = volumes; l; l = l->next) + { + GtkFileSystemVolume *volume; + GtkTreeIter iter; + GdkPixbuf *pixbuf; + gchar *display_name; + + volume = l->data; + + if (local_only) + { + if (gtk_file_system_volume_get_is_mounted (file_system, volume)) + { + GtkFilePath *base_path; + + base_path = gtk_file_system_volume_get_base_path (file_system, volume); + if (base_path != NULL) + { + gboolean is_local = gtk_file_system_path_is_local (file_system, base_path); + gtk_file_path_free (base_path); + + if (!is_local) + { + gtk_file_system_volume_free (file_system, volume); + continue; + } + } + } + } + + pixbuf = gtk_file_system_volume_render_icon (file_system, + volume, + GTK_WIDGET (button), + button->priv->icon_size, + NULL); + display_name = gtk_file_system_volume_get_display_name (file_system, volume); + + 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, volume, + IS_FOLDER_COLUMN, TRUE, + -1); + + if (pixbuf) + g_object_unref (pixbuf); + g_free (display_name); + + button->priv->n_volumes++; + pos++; + } } -static gboolean -check_if_path_exists (GtkFileSystem *fs, - const GtkFilePath *path) +extern gchar * _gtk_file_chooser_label_for_uri (const gchar *uri); + +static void +model_add_bookmarks (GtkFileChooserButton *button, + GSList *bookmarks) { - gboolean path_exists; - GtkFilePath *parent_path; + GtkListStore *store; + GtkTreeIter iter; + gint pos; + gboolean local_only; + GSList *l; - path_exists = FALSE; - parent_path = NULL; + if (!bookmarks) + return; + + store = GTK_LIST_STORE (button->priv->model); + pos = model_get_type_position (button, ROW_TYPE_BOOKMARK); + local_only = gtk_file_chooser_get_local_only (GTK_FILE_CHOOSER (button->priv->dialog)); - if (gtk_file_system_get_parent (fs, path, &parent_path, NULL)) + for (l = bookmarks; l; l = l->next) { - GtkFileFolder *folder; - - folder = gtk_file_system_get_folder (fs, parent_path, 0, NULL); - if (folder) + GtkFilePath *path; + + path = l->data; + + if (gtk_file_system_path_is_local (button->priv->fs, path)) { - GtkFileInfo *info; - - info = gtk_file_folder_get_info (folder, path, NULL); - if (info) + gtk_list_store_insert (store, &iter, pos); + gtk_list_store_set (store, &iter, + ICON_COLUMN, NULL, + DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME), + TYPE_COLUMN, ROW_TYPE_BOOKMARK, + DATA_COLUMN, gtk_file_path_copy (path), + IS_FOLDER_COLUMN, FALSE, + -1); + set_info_for_path_at_iter (button, path, &iter); + } + else + { + gchar *label; + GtkIconTheme *icon_theme; + GdkPixbuf *pixbuf; + + if (local_only) + continue; + + /* Don't call get_info for remote paths to avoid latency and + * auth dialogs. + * If we switch to a better bookmarks file format (XBEL), we + * should use mime info to get a better icon. + */ + label = gtk_file_system_get_bookmark_label (button->priv->fs, path); + if (!label) { - path_exists = TRUE; - gtk_file_info_free (info); + gchar *uri; + + uri = gtk_file_system_path_to_uri (button->priv->fs, path); + label = _gtk_file_chooser_label_for_uri (uri); + g_free (uri); } - g_object_unref (folder); + icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button))); + pixbuf = gtk_icon_theme_load_icon (icon_theme, "gnome-fs-directory", + button->priv->icon_size, 0, NULL); + + gtk_list_store_insert (store, &iter, pos); + gtk_list_store_set (store, &iter, + ICON_COLUMN, pixbuf, + DISPLAY_NAME_COLUMN, label, + TYPE_COLUMN, ROW_TYPE_BOOKMARK, + DATA_COLUMN, gtk_file_path_copy (path), + IS_FOLDER_COLUMN, TRUE, + -1); + + g_free (label); + g_object_unref (pixbuf); } - - gtk_file_path_free (parent_path); + + button->priv->n_bookmarks++; + pos++; } - return path_exists; + if (button->priv->n_bookmarks > 0 && + !button->priv->has_bookmark_separator) + { + pos = model_get_type_position (button, ROW_TYPE_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, + IS_FOLDER_COLUMN, FALSE, + -1); + button->priv->has_bookmark_separator = TRUE; + } } static void -update_icons (GtkFileChooserButton *button) +model_update_current_folder (GtkFileChooserButton *button, + const GtkFilePath *path) { - GtkFileChooserButtonPrivate *priv; - GdkPixbuf *pixbuf; - GSList *paths; + GtkListStore *store; + GtkTreeIter iter; + gint pos; - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button); - pixbuf = NULL; - paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog)); + if (!path) + return; + + store = GTK_LIST_STORE (button->priv->model); - if (paths) + if (!button->priv->has_current_folder_separator) { - GtkFilePath *path; - GtkFileSystem *fs; + 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, + IS_FOLDER_COLUMN, FALSE, + -1); + button->priv->has_current_folder_separator = TRUE; + } - path = paths->data; - fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)); + 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); + model_free_row_data (button, &iter); + } - switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog))) + if (gtk_file_system_path_is_local (button->priv->fs, path)) + { + gtk_list_store_set (store, &iter, + ICON_COLUMN, NULL, + DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME), + TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER, + DATA_COLUMN, gtk_file_path_copy (path), + IS_FOLDER_COLUMN, FALSE, + -1); + set_info_for_path_at_iter (button, path, &iter); + } + else + { + gchar *label; + GtkIconTheme *icon_theme; + GdkPixbuf *pixbuf; + + /* Don't call get_info for remote paths to avoid latency and + * auth dialogs. + * If we switch to a better bookmarks file format (XBEL), we + * should use mime info to get a better icon. + */ + label = gtk_file_system_get_bookmark_label (button->priv->fs, path); + if (!label) { - case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER: - { - GtkFileSystemVolume *volume; + gchar *uri; + + uri = gtk_file_system_path_to_uri (button->priv->fs, path); + label = _gtk_file_chooser_label_for_uri (uri); + g_free (uri); + } + + icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button))); + pixbuf = gtk_icon_theme_load_icon (icon_theme, "gnome-fs-directory", + button->priv->icon_size, 0, NULL); + + gtk_list_store_set (store, &iter, + ICON_COLUMN, pixbuf, + DISPLAY_NAME_COLUMN, label, + TYPE_COLUMN, ROW_TYPE_CURRENT_FOLDER, + DATA_COLUMN, gtk_file_path_copy (path), + IS_FOLDER_COLUMN, TRUE, + -1); - volume = gtk_file_system_get_volume_for_path (fs, path); - if (volume) + g_free (label); + g_object_unref (pixbuf); + } +} + +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, + IS_FOLDER_COLUMN, FALSE, + -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, + IS_FOLDER_COLUMN, FALSE, + -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, + gboolean is_folder) +{ + if (!path) + return FALSE; + + if (local_only && !gtk_file_system_path_is_local (fs, path)) + return FALSE; + + if (!is_folder) + return FALSE; + + return TRUE; +} + +static gboolean +filter_model_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data); + GtkFileChooserButtonPrivate *priv = button->priv; + gchar type; + gpointer data; + gboolean local_only, retval, is_folder; + + 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, + IS_FOLDER_COLUMN, &is_folder, + -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, is_folder); + break; + case ROW_TYPE_VOLUME: + { + retval = TRUE; + if (local_only) + { + if (gtk_file_system_volume_get_is_mounted (priv->fs, data)) { GtkFilePath *base_path; - - base_path = gtk_file_system_volume_get_base_path (fs, volume); - if (base_path && gtk_file_path_compare (base_path, path) == 0) - pixbuf = gtk_file_system_volume_render_icon (fs, volume, - GTK_WIDGET (button), - priv->icon_size, - NULL); - + base_path = gtk_file_system_volume_get_base_path (priv->fs, data); if (base_path) - gtk_file_path_free (base_path); + { + gboolean is_local = gtk_file_system_path_is_local (priv->fs, base_path); + + gtk_file_path_free (base_path); - gtk_file_system_volume_free (fs, volume); + if (!is_local) + retval = FALSE; + } + else + retval = FALSE; } } - - case GTK_FILE_CHOOSER_ACTION_OPEN: - if (!pixbuf) - pixbuf = gtk_file_system_render_icon (fs, path, GTK_WIDGET (button), - priv->icon_size, NULL); - break; + } + break; + default: + retval = TRUE; + break; + } - case GTK_FILE_CHOOSER_ACTION_SAVE: - if (check_if_path_exists (fs, path)) - pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)), - GTK_STOCK_DIALOG_WARNING, - priv->icon_size, 0, NULL); - else - pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)), - NEW_FILE_ICON_NAME, - priv->icon_size, 0, NULL); + 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 = button->priv; + GSList *paths; + GtkTreeIter iter; + gboolean row_found; + + gtk_tree_model_get_iter_first (priv->filter_model, &iter); + + 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 GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER: - if (check_if_path_exists (fs, path)) - pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)), - GTK_STOCK_DIALOG_WARNING, - priv->icon_size, 0, NULL); - else - pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)), - NEW_DIR_ICON_NAME, - priv->icon_size, 0, NULL); + case ROW_TYPE_VOLUME: + { + GtkFilePath *base_path; + + base_path = gtk_file_system_volume_get_base_path (priv->fs, data); + if (base_path) + { + 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)); - gtk_file_paths_free (paths); + pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER); + 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_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer data) +{ + gboolean cancelled = handle->cancelled; + GdkPixbuf *pixbuf; + GtkFileChooserButton *button = data; + GtkFileChooserButtonPrivate *priv = button->priv; + + if (handle != priv->update_button_handle) + goto out; + + priv->update_button_handle = NULL; + + if (cancelled || error) + goto out; + + gtk_label_set_text (GTK_LABEL (priv->label), gtk_file_info_get_display_name (info)); + + pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (priv->image), + priv->icon_size, NULL); if (!pixbuf) - pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)), + pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (priv->image)), FALLBACK_ICON_NAME, priv->icon_size, 0, NULL); - gtk_image_set_from_pixbuf (GTK_IMAGE (priv->entry_image), pixbuf); - gtk_image_set_from_pixbuf (GTK_IMAGE (priv->label_image), pixbuf); - + gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf); if (pixbuf) g_object_unref (pixbuf); -} +out: + g_object_unref (button); + g_object_unref (handle); +} static void -update_label (GtkFileChooserButton *button) +update_label_and_image (GtkFileChooserButton *button) { - GtkFileChooserButtonPrivate *priv; + GtkFileChooserButtonPrivate *priv = button->priv; + GdkPixbuf *pixbuf; gchar *label_text; GSList *paths; - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button); - paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (button->priv->dialog)); + paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog)); label_text = NULL; + pixbuf = NULL; - if (paths) + if (paths && paths->data) { - GtkFileSystem *fs; GtkFilePath *path; - GtkFileSystemVolume *volume; + GtkFileSystemVolume *volume = NULL; 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); + { + 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); + } 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 (gtk_file_system_path_is_local (fs, path)) - { - const gchar *home; - gchar *tmp; - gchar *filename; - - filename = gtk_file_system_path_to_filename (fs, path); - - if (!filename) - goto out; - home = g_get_home_dir (); - - /* Munging for psuedo-volumes and files in the user's home tree */ - if (home) - { - if (strcmp (filename, home) == 0) - { - label_text = g_strdup (_("Home")); - goto localout; - } - - tmp = g_build_filename (home, "Desktop", NULL); - - if (strcmp (filename, tmp) == 0) - label_text = g_strdup (_("Desktop")); - - g_free (tmp); - - if (label_text) - goto out; - - if (g_str_has_prefix (filename, home)) - { - label_text = g_strconcat ("~", filename + strlen (home), NULL); - goto localout; - } - } - - if (!label_text) - label_text = g_strdup (filename); - - localout: - g_free (filename); + if (priv->update_button_handle) + { + gtk_file_system_cancel_operation (priv->update_button_handle); + priv->update_button_handle = NULL; + } + + if (gtk_file_system_path_is_local (priv->fs, path)) + { + priv->update_button_handle = + gtk_file_system_get_info (priv->fs, path, + GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_ICON, + update_label_get_info_cb, + g_object_ref (button)); } else { - gchar *uri; - - uri = gtk_file_system_path_to_uri (fs, path); - - if (uri) - label_text = uri; + GdkPixbuf *pixbuf; + + label_text = gtk_file_system_get_bookmark_label (button->priv->fs, path); + + pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (priv->image)), + "gnome-fs-regular", + priv->icon_size, 0, NULL); + + gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf); + + if (pixbuf) + g_object_unref (pixbuf); } - - out: - gtk_file_paths_free (paths); } +out: + gtk_file_paths_free (paths); if (label_text) { @@ -1328,192 +2408,189 @@ update_label (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)); } -static void -update_entry (GtkFileChooserButton *button) -{ - GtkFileChooserButtonPrivate *priv; - GSList *paths; - gchar *filename; - - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button); - paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog)); +/* ************************ * + * Child Object Callbacks * + * ************************ */ - if (paths) - { - GtkFileSystem *fs; - GtkFilePath *path; - - path = paths->data; - fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)); - - if (gtk_file_system_path_is_local (fs, path)) - { - filename = gtk_file_system_path_to_filename (fs, path); +/* File System */ +static void +fs_volumes_changed_cb (GtkFileSystem *fs, + gpointer user_data) +{ + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data); + GtkFileChooserButtonPrivate *priv = button->priv; + GSList *volumes; - if (filename) - { - const gchar *home; - gchar *tmp; + model_remove_rows (user_data, + model_get_type_position (user_data, ROW_TYPE_VOLUME), + priv->n_volumes); - if (g_file_test (filename, G_FILE_TEST_IS_DIR)) - { - tmp = g_strconcat (filename, "/", NULL); - g_free (filename); - filename = tmp; - } + priv->n_volumes = 0; - home = g_get_home_dir (); + volumes = gtk_file_system_list_volumes (fs); + model_add_volumes (user_data, volumes); + g_slist_free (volumes); - if (home && g_str_has_prefix (filename, home)) - { - tmp = g_strconcat ("~", filename + strlen (home), NULL); - g_free (filename); - filename = tmp; - } - } - } - else - filename = gtk_file_system_path_to_uri (fs, path); - } - else - filename = NULL; + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model)); - if (filename) - { - gchar *entry_text; - - entry_text = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL); - g_free (filename); + update_label_and_image (user_data); + update_combo_box (user_data); +} - gtk_entry_set_text (GTK_ENTRY (priv->entry), entry_text); - g_free (entry_text); - } - else - gtk_entry_set_text (GTK_ENTRY (priv->entry), ""); +static void +fs_bookmarks_changed_cb (GtkFileSystem *fs, + gpointer user_data) +{ + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data); + GtkFileChooserButtonPrivate *priv = button->priv; + GSList *bookmarks; + + bookmarks = gtk_file_system_list_bookmarks (fs); + 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; + 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 -update_dialog (GtkFileChooserButton *button) +open_dialog (GtkFileChooserButton *button) { - GtkFileChooserButtonPrivate *priv; - GtkFilePath *current_folder; - GtkFileSystem *fs; - GtkFilePath *folder_part, *full_path; - gchar *file_part; - const gchar *text; - GtkFilePath *base_path; + GtkFileChooserButtonPrivate *priv = button->priv; - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button); - file_part = NULL; - folder_part = NULL; - fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)); + /* Setup the dialog parent to be chooser button's toplevel, and be modal + as needed. */ + if (!GTK_WIDGET_VISIBLE (priv->dialog)) + { + GtkWidget *toplevel; - text = gtk_entry_get_text (GTK_ENTRY (priv->entry)); + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button)); - base_path = gtk_file_path_new_dup ("/"); - gtk_file_system_parse (fs, base_path, text, &folder_part, &file_part, NULL); - gtk_file_path_free (base_path); + 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))); + } + } - switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog))) + if (!priv->active) { - case GTK_FILE_CHOOSER_ACTION_OPEN: - gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog)); - if (folder_part) + 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) { - GtkFileFolder *folder; - GtkFileInfo *info; + if (paths->data) + priv->old_path = gtk_file_path_copy (paths->data); - folder = gtk_file_system_get_folder (fs, folder_part, - GTK_FILE_INFO_IS_FOLDER, NULL); + gtk_file_paths_free (paths); + } - full_path = gtk_file_system_make_path (fs, folder_part, file_part, NULL); - info = gtk_file_folder_get_info (folder, full_path, NULL); + priv->active = TRUE; + } - /* Entry contents don't exist. */ - if (info == NULL) - _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog), - folder_part, NULL); - /* Entry contents are a folder */ - else if (gtk_file_info_get_is_folder (info)) - _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog), - full_path, NULL); - /* Entry contents must be a file. */ - else - _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog), - full_path, NULL); + 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 (info) - gtk_file_info_free (info); + if (gtk_combo_box_get_active_iter (combo_box, &iter)) + { + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data); + GtkFileChooserButtonPrivate *priv = button->priv; + gchar type; + gpointer data; - gtk_file_path_free (full_path); - } - break; - case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER: - gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog)); - if (folder_part) - { - /* Entry contents don't exist. */ - if (file_part && file_part[0] != '\0') - { - full_path = gtk_file_system_make_path (fs, folder_part, file_part, - NULL); - if (full_path) - { - _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog), - full_path, NULL); - gtk_file_path_free (full_path); - } - else - _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog), - folder_part, NULL); - } - else - { - _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog), - folder_part, NULL); - } - } - break; + type = ROW_TYPE_INVALID; + data = NULL; - case GTK_FILE_CHOOSER_ACTION_SAVE: - case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER: - gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog)); - if (folder_part) - { - current_folder = _gtk_file_chooser_get_current_folder_path (GTK_FILE_CHOOSER (priv->dialog)); + gtk_tree_model_get (priv->filter_model, &iter, + TYPE_COLUMN, &type, + DATA_COLUMN, &data, + -1); - if (!current_folder || - gtk_file_path_compare (current_folder, folder_part) != 0) - { - _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog), - folder_part, NULL); - g_signal_emit_by_name (button, "current-folder-changed"); - } + 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; - if (current_folder) - gtk_file_path_free (current_folder); + 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; } - - gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (priv->dialog), - file_part); - g_signal_emit_by_name (button, "selection-changed"); - break; } } -/* ************************ * - * Child-Widget Callbacks * - * ************************ */ +/* 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) { - g_signal_emit_by_name (user_data, "current-folder-changed"); + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data); + GtkFileChooserButtonPrivate *priv = button->priv; + + priv->folder_has_been_set = TRUE; + + g_signal_emit_by_name (button, "current-folder-changed"); } static void @@ -1527,25 +2604,8 @@ static void dialog_selection_changed_cb (GtkFileChooser *dialog, gpointer user_data) { - GtkFileChooserButtonPrivate *priv; - - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data); - - g_signal_handler_block (priv->entry, priv->entry_changed_id); - update_entry (user_data); - g_signal_handler_unblock (priv->entry, priv->entry_changed_id); - update_icons (user_data); - update_label (user_data); -} - -static void -dialog_selection_changed_proxy_cb (GtkFileChooser *dialog, - gpointer user_data) -{ - GtkFileChooserButtonPrivate *priv; - - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data); - + update_label_and_image (user_data); + update_combo_box (user_data); g_signal_emit_by_name (user_data, "selection-changed"); } @@ -1567,6 +2627,39 @@ 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) + { + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data); + GtkFileChooserButtonPrivate *priv = button->priv; + + if (priv->has_current_folder) + { + GtkTreeIter iter; + gint pos; + gpointer data; + + pos = model_get_type_position (user_data, + ROW_TYPE_CURRENT_FOLDER); + 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 @@ -1580,147 +2673,271 @@ dialog_delete_event_cb (GtkWidget *dialog, } static void -dialog_response_cb (GtkFileChooser *dialog, - gint response, - gpointer user_data) +dialog_response_cb (GtkDialog *dialog, + gint response, + gpointer user_data) { - GtkFileChooserButtonPrivate *priv; - - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data); + GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data); + GtkFileChooserButtonPrivate *priv = button->priv; if (response == GTK_RESPONSE_ACCEPT) { - g_signal_handler_block (priv->entry, priv->entry_changed_id); - update_entry (user_data); - g_signal_handler_unblock (priv->entry, priv->entry_changed_id); - update_label (user_data); - update_icons (user_data); - g_signal_emit_by_name (user_data, "current-folder-changed"); g_signal_emit_by_name (user_data, "selection-changed"); } - else - { - g_signal_handler_block (priv->dialog, priv->dialog_selection_changed_id); - update_dialog (user_data); - g_signal_handler_unblock (priv->dialog, priv->dialog_selection_changed_id); - } - - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE); -} - - -static void -button_toggled_cb (GtkToggleButton *real_button, - gpointer user_data) -{ - GtkFileChooserButtonPrivate *priv; - - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data); - - if (gtk_toggle_button_get_active (real_button)) + else if (priv->old_path) { - /* Setup the dialog parent to be chooser button's toplevel, and be modal - as needed. */ - if (!GTK_WIDGET_VISIBLE (priv->dialog)) + switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog))) { - GtkWidget *toplevel; - - toplevel = gtk_widget_get_toplevel (user_data); - - 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))); - } + case GTK_FILE_CHOOSER_ACTION_OPEN: + _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (dialog), priv->old_path, + NULL); + break; + case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER: + _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (dialog), + priv->old_path, NULL); + break; + default: + g_assert_not_reached (); + break; } - - 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_proxy_id); - gtk_widget_set_sensitive (priv->entry, FALSE); - gtk_window_present (GTK_WINDOW (priv->dialog)); } else + gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (dialog)); + + if (priv->old_path) + { + gtk_file_path_free (priv->old_path); + priv->old_path = NULL; + } + + update_label_and_image (user_data); + update_combo_box (user_data); + + if (priv->active) { g_signal_handler_unblock (priv->dialog, priv->dialog_folder_changed_id); g_signal_handler_unblock (priv->dialog, priv->dialog_file_activated_id); g_signal_handler_unblock (priv->dialog, - priv->dialog_selection_changed_proxy_id); - gtk_widget_set_sensitive (priv->entry, TRUE); - gtk_widget_hide (priv->dialog); + priv->dialog_selection_changed_id); + priv->active = FALSE; } + + gtk_widget_set_sensitive (priv->combo_box, TRUE); + gtk_widget_hide (priv->dialog); + + g_signal_emit_by_name (user_data, "file-set"); } -static void -button_notify_active_cb (GObject *real_button, - GParamSpec *pspec, - 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) +{ + g_return_val_if_fail (action == GTK_FILE_CHOOSER_ACTION_OPEN || + action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, NULL); + + return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, + "action", action, + "title", (title ? title : _(DEFAULT_TITLE)), + NULL); +} + +/** + * 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_object_notify (user_data, "active"); + g_return_val_if_fail (action == GTK_FILE_CHOOSER_ACTION_OPEN || + action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, NULL); + + return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, + "action", action, + "title", (title ? title : _(DEFAULT_TITLE)), + "file-system-backend", backend, + NULL); } +/** + * gtk_file_chooser_button_new_with_dialog: + * @dialog: the widget to use as dialog + * + * Creates a #GtkFileChooserButton widget which uses @dialog as it's + * file-picking window. Note that @dialog must be a #GtkDialog (or + * subclass) which implements the #GtkFileChooser interface and must + * not have %GTK_DIALOG_DESTROY_WITH_PARENT set. + * + * 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) && GTK_IS_DIALOG (dialog), NULL); + + return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, + "dialog", dialog, + NULL); +} -static gboolean -update_idler (gpointer user_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) { - GtkFileChooserButtonPrivate *priv; - gboolean retval; - gint start, end; + g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button)); - GDK_THREADS_ENTER (); + gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title); + g_object_notify (G_OBJECT (button), "title"); +} - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data); +/** + * 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 (!gtk_editable_get_selection_bounds (GTK_EDITABLE (priv->entry), - &start, &end)) - { - g_signal_handler_block (priv->dialog, - priv->dialog_selection_changed_id); - update_dialog (user_data); - g_signal_handler_unblock (priv->dialog, - priv->dialog_selection_changed_id); - update_icons (user_data); - update_label (user_data); - priv->update_id = 0; - retval = FALSE; - } - else - retval = TRUE; - - GDK_THREADS_LEAVE (); + return gtk_window_get_title (GTK_WINDOW (button->priv->dialog)); +} - return retval; +/** + * 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)); } -static void -entry_changed_cb (GtkEditable *editable, - gpointer user_data) +/** + * gtk_file_chooser_button_set_width_chars: + * @button: the button widget to examine. + * @n_chars: the new width, in characters. + * + * 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"); +} + +/** + * gtk_file_chooser_button_set_focus_on_click: + * @button: a #GtkFileChooserButton + * @focus_on_click: whether the button grabs focus when clicked with the mouse + * + * Sets whether the button will grab focus when it is clicked with the mouse. + * Making mouse clicks not grab focus is useful in places like toolbars where + * you don't want the keyboard focus removed from the main area of the + * application. + * + * Since: 2.10 + **/ +void +gtk_file_chooser_button_set_focus_on_click (GtkFileChooserButton *button, + gboolean focus_on_click) { GtkFileChooserButtonPrivate *priv; - priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data); + g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button)); + + priv = button->priv; - if (priv->update_id) - g_source_remove (priv->update_id); + focus_on_click = focus_on_click != FALSE; - priv->update_id = g_idle_add_full (G_PRIORITY_LOW, update_idler, - user_data, NULL); + if (priv->focus_on_click != focus_on_click) + { + priv->focus_on_click = focus_on_click; + gtk_button_set_focus_on_click (GTK_BUTTON (priv->button), focus_on_click); + gtk_combo_box_set_focus_on_click (GTK_COMBO_BOX (priv->combo_box), focus_on_click); + + g_object_notify (G_OBJECT (button), "focus-on-click"); + } } -/* Ensure the button height == entry height */ -static void -entry_size_allocate_cb (GtkWidget *entry, - GtkAllocation *allocation, - gpointer user_data) +/** + * gtk_file_chooser_button_get_focus_on_click: + * @button: a #GtkFileChooserButton + * + * Returns whether the button grabs focus when it is clicked with the mouse. + * See gtk_file_chooser_button_set_focus_on_click(). + * + * Return value: %TRUE if the button grabs focus when it is clicked with + * the mouse. + * + * Since: 2.10 + **/ +gboolean +gtk_file_chooser_button_get_focus_on_click (GtkFileChooserButton *button) { - gtk_widget_set_size_request (user_data, -1, allocation->height); + g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), FALSE); + + return button->priv->focus_on_click; } + +#define __GTK_FILE_CHOOSER_BUTTON_C__ +#include "gtkaliasdef.c"