From 2c97a8f6e8f260695515dabcb5185d0603dcd4f3 Mon Sep 17 00:00:00 2001 From: Kristian Rietveld Date: Mon, 1 May 2006 21:41:12 +0000 Subject: Merge of the GTK+ asynchronous file chooser branch. Please see the 2006-05-01 Kristian Rietveld Merge of the GTK+ asynchronous file chooser branch. Please see the kris-asynch-branch for more detailed ChangeLog entries. * configure.in: increase binary version to 2.9.0. * gtk.symbols: * gtkfilechooser.c: * gtkfilechooserbutton.c: * gtkfilechooserdefault.c: * gtkfilechooserdialog.c: * gtkfilechooserembed.c: * gtkfilechooserembed.h: * gtkfilechooserentry.c: * gtkfilechooserentry.h: * gtkfilechooserprivate.h: * gtkfilesystem.c: * gtkfilesystem.h: * gtkfilesystemmodel.c: * gtkfilesystemmodel.h: * gtkfilesystemunix.c: * gtkpathbar.c: * gtkpathbar.h: Merge from kris-async-branch. --- ChangeLog | 26 + ChangeLog.pre-2-10 | 26 + configure.in | 2 +- gtk/gtk.symbols | 7 +- gtk/gtkfilechooser.c | 3 + gtk/gtkfilechooserbutton.c | 703 ++++++++++++------ gtk/gtkfilechooserdefault.c | 1726 +++++++++++++++++++++++++++++++------------ gtk/gtkfilechooserdialog.c | 43 +- gtk/gtkfilechooserembed.c | 18 + gtk/gtkfilechooserembed.h | 1 + gtk/gtkfilechooserentry.c | 134 +++- gtk/gtkfilechooserentry.h | 2 + gtk/gtkfilechooserprivate.h | 16 +- gtk/gtkfilesystem.c | 306 ++++++-- gtk/gtkfilesystem.h | 145 +++- gtk/gtkfilesystemmodel.c | 610 ++++++++++----- gtk/gtkfilesystemmodel.h | 2 +- gtk/gtkfilesystemunix.c | 923 ++++++++++++++++------- gtk/gtkpathbar.c | 387 +++++++--- gtk/gtkpathbar.h | 2 + 20 files changed, 3700 insertions(+), 1382 deletions(-) diff --git a/ChangeLog b/ChangeLog index eabb68bae..d449ffc51 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +2006-05-01 Kristian Rietveld + + Merge of the GTK+ asynchronous file chooser branch. Please see + the kris-asynch-branch for more detailed ChangeLog entries. + + * configure.in: increase binary version to 2.9.0. + + * gtk.symbols: + * gtkfilechooser.c: + * gtkfilechooserbutton.c: + * gtkfilechooserdefault.c: + * gtkfilechooserdialog.c: + * gtkfilechooserembed.c: + * gtkfilechooserembed.h: + * gtkfilechooserentry.c: + * gtkfilechooserentry.h: + * gtkfilechooserprivate.h: + * gtkfilesystem.c: + * gtkfilesystem.h: + * gtkfilesystemmodel.c: + * gtkfilesystemmodel.h: + * gtkfilesystemunix.c: + * gtkpathbar.c: + * gtkpathbar.h: + Merge from kris-async-branch. + 2006-05-01 Matthias Clasen * NEWS: Updates diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index eabb68bae..d449ffc51 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,29 @@ +2006-05-01 Kristian Rietveld + + Merge of the GTK+ asynchronous file chooser branch. Please see + the kris-asynch-branch for more detailed ChangeLog entries. + + * configure.in: increase binary version to 2.9.0. + + * gtk.symbols: + * gtkfilechooser.c: + * gtkfilechooserbutton.c: + * gtkfilechooserdefault.c: + * gtkfilechooserdialog.c: + * gtkfilechooserembed.c: + * gtkfilechooserembed.h: + * gtkfilechooserentry.c: + * gtkfilechooserentry.h: + * gtkfilechooserprivate.h: + * gtkfilesystem.c: + * gtkfilesystem.h: + * gtkfilesystemmodel.c: + * gtkfilesystemmodel.h: + * gtkfilesystemunix.c: + * gtkpathbar.c: + * gtkpathbar.h: + Merge from kris-async-branch. + 2006-05-01 Matthias Clasen * NEWS: Updates diff --git a/configure.in b/configure.in index 857bf9d53..29f2d84fe 100644 --- a/configure.in +++ b/configure.in @@ -28,7 +28,7 @@ m4_define([gtk_api_version], [2.0]) # for GTK+. # #GTK_BINARY_VERSION=$GTK_MAJOR_VERSION.$GTK_MINOR_VERSION.$LT_CURRENT -m4_define([gtk_binary_version], [2.4.0]) +m4_define([gtk_binary_version], [2.9.0]) # required versions of other packages m4_define([glib_required_version], [2.10.1]) diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols index e4fba9775..b55a6f9a3 100644 --- a/gtk/gtk.symbols +++ b/gtk/gtk.symbols @@ -1392,6 +1392,7 @@ gtk_file_info_copy gtk_file_info_free gtk_file_info_get_display_key gtk_file_info_get_display_name +gtk_file_info_get_icon_name gtk_file_info_get_is_folder gtk_file_info_get_is_hidden gtk_file_info_get_mime_type @@ -1399,7 +1400,9 @@ gtk_file_info_get_modification_time gtk_file_info_get_size gtk_file_info_get_type G_GNUC_CONST gtk_file_info_new +gtk_file_info_render_icon gtk_file_info_set_display_name +gtk_file_info_set_icon_name gtk_file_info_set_is_folder gtk_file_info_set_is_hidden gtk_file_info_set_mime_type @@ -1409,9 +1412,11 @@ gtk_file_path_get_type G_GNUC_CONST gtk_file_paths_copy gtk_file_paths_free gtk_file_paths_sort +gtk_file_system_cancel_operation gtk_file_system_create_folder gtk_file_system_error_quark gtk_file_system_filename_to_path +gtk_file_system_get_info gtk_file_system_get_folder gtk_file_system_get_parent gtk_file_system_get_type G_GNUC_CONST @@ -1425,11 +1430,11 @@ gtk_file_system_path_is_local gtk_file_system_path_to_filename gtk_file_system_path_to_uri gtk_file_system_remove_bookmark -gtk_file_system_render_icon gtk_file_system_uri_to_path gtk_file_system_volume_free gtk_file_system_volume_get_base_path gtk_file_system_volume_get_display_name +gtk_file_system_volume_get_icon_name gtk_file_system_volume_get_is_mounted gtk_file_system_volume_mount gtk_file_system_volume_render_icon diff --git a/gtk/gtkfilechooser.c b/gtk/gtkfilechooser.c index 3f9b943f8..55689952b 100644 --- a/gtk/gtkfilechooser.c +++ b/gtk/gtkfilechooser.c @@ -717,6 +717,9 @@ gtk_file_chooser_get_current_folder (GtkFileChooser *chooser) file_system = _gtk_file_chooser_get_file_system (chooser); path = _gtk_file_chooser_get_current_folder_path (chooser); + if (!path) + return NULL; + filename = gtk_file_system_path_to_filename (file_system, path); gtk_file_path_free (path); diff --git a/gtk/gtkfilechooserbutton.c b/gtk/gtkfilechooserbutton.c index 390ded1b1..a3b202d0c 100644 --- a/gtk/gtkfilechooserbutton.c +++ b/gtk/gtkfilechooserbutton.c @@ -93,6 +93,8 @@ enum DISPLAY_NAME_COLUMN, TYPE_COLUMN, DATA_COLUMN, + IS_FOLDER_COLUMN, + HANDLE_COLUMN, NUM_COLUMNS }; @@ -142,6 +144,10 @@ struct _GtkFileChooserButtonPrivate 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; @@ -229,8 +235,9 @@ static void gtk_file_chooser_button_screen_changed (GtkWidget *wi /* Utility Functions */ static GtkIconTheme *get_icon_theme (GtkWidget *widget); -static gchar *get_display_name_for_path (GtkFileSystem *fs, - const GtkFilePath *path); +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); @@ -451,7 +458,9 @@ gtk_file_chooser_button_init (GtkFileChooserButton *button) GDK_TYPE_PIXBUF, /* Icon */ G_TYPE_STRING, /* Display Name */ G_TYPE_CHAR, /* Row Type */ - G_TYPE_POINTER /* Volume || Path */)); + G_TYPE_POINTER /* Volume || Path */, + G_TYPE_BOOLEAN /* Is Folder? */, + G_TYPE_OBJECT /* handle */)); priv->combo_box = gtk_combo_box_new (); priv->combo_box_changed_id = @@ -519,30 +528,21 @@ gtk_file_chooser_button_add_shortcut_folder (GtkFileChooser *chooser, GtkFileChooserButtonPrivate *priv = button->priv; GtkTreeIter iter; gint pos; - GdkPixbuf *pixbuf; - gchar *display_name; pos = model_get_type_position (button, ROW_TYPE_SHORTCUT); pos += priv->n_shortcuts; - pixbuf = gtk_file_system_render_icon (priv->fs, path, - GTK_WIDGET (chooser), - priv->icon_size, NULL); - display_name = get_display_name_for_path (priv->fs, path); - gtk_list_store_insert (GTK_LIST_STORE (priv->model), &iter, pos); gtk_list_store_set (GTK_LIST_STORE (priv->model), &iter, - ICON_COLUMN, pixbuf, - DISPLAY_NAME_COLUMN, display_name, + 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++; - if (pixbuf) - g_object_unref (pixbuf); - g_free (display_name); - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model)); } @@ -868,26 +868,10 @@ gtk_file_chooser_button_finalize (GObject *object) { GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object); GtkFileChooserButtonPrivate *priv = button->priv; - GtkTreeIter iter; if (priv->old_path) gtk_file_path_free (priv->old_path); - 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)); - - g_object_unref (priv->model); - g_object_unref (priv->filter_model); - - g_signal_handler_disconnect (priv->fs, priv->fs_volumes_changed_id); - g_signal_handler_disconnect (priv->fs, priv->fs_bookmarks_changed_id); - g_object_unref (priv->fs); - if (G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->finalize != NULL) (*G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->finalize) (object); } @@ -901,9 +885,65 @@ gtk_file_chooser_button_destroy (GtkObject *object) { GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (object); GtkFileChooserButtonPrivate *priv = button->priv; + GtkTreeIter iter; + GSList *l; if (priv->dialog != NULL) - gtk_widget_destroy (priv->dialog); + { + gtk_widget_destroy (priv->dialog); + priv->dialog = NULL; + } + + 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->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); @@ -914,6 +954,76 @@ 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, @@ -943,59 +1053,30 @@ gtk_file_chooser_button_drag_data_received (GtkWidget *widget, case TEXT_URI_LIST: { gchar **uris; - GtkFilePath *base_path; - 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++) - { - path = gtk_file_system_uri_to_path (priv->fs, uris[i]); - - base_path = NULL; - if (path != NULL && - gtk_file_system_get_parent (priv->fs, path, &base_path, NULL)) - { - GtkFileFolder *folder; - GtkFileInfo *info; - - folder = gtk_file_system_get_folder (priv->fs, base_path, - GTK_FILE_INFO_IS_FOLDER, - NULL); - - info = gtk_file_folder_get_info (folder, path, NULL); - - if (info != NULL) - { - GtkFileChooserAction action; + 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); - g_object_get (priv->dialog, "action", &action, NULL); - - selected = - (((action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER && - gtk_file_info_get_is_folder (info)) || - (action == GTK_FILE_CHOOSER_ACTION_OPEN && - !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; @@ -1096,6 +1177,64 @@ 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_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = handle->cancelled; + GdkPixbuf *pixbuf; + struct ChangeIconThemeData *data = user_data; + + if (!g_slist_find (data->button->priv->change_icon_theme_handles, handle)) + goto out; + + data->button->priv->change_icon_theme_handles = + g_slist_remove (data->button->priv->change_icon_theme_handles, handle); + + if (cancelled || error) + goto out; + + 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); + 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_unref (pixbuf); + + g_object_set (data->button->priv->icon_cell, + "width", width, + NULL); + } + +out: + g_object_unref (data->button); + gtk_tree_row_reference_free (data->row_ref); + g_free (data); + + g_object_unref (handle); +} + static void change_icon_theme (GtkFileChooserButton *button) { @@ -1103,7 +1242,16 @@ change_icon_theme (GtkFileChooserButton *button) GtkSettings *settings; GtkIconTheme *theme; GtkTreeIter iter; - gint width, height; + GSList *l; + gint width = 0, height = 0; + + for (l = button->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 (button->priv->change_icon_theme_handles); + button->priv->change_icon_theme_handles = NULL; settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button))); @@ -1138,9 +1286,25 @@ change_icon_theme (GtkFileChooserButton *button) case ROW_TYPE_BOOKMARK: case ROW_TYPE_CURRENT_FOLDER: if (data) - pixbuf = gtk_file_system_render_icon (priv->fs, data, - GTK_WIDGET (button), - priv->icon_size, NULL); + { + 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 pixbuf = gtk_icon_theme_load_icon (theme, FALLBACK_ICON_NAME, priv->icon_size, 0, NULL); @@ -1215,43 +1379,92 @@ get_icon_theme (GtkWidget *widget) return gtk_icon_theme_get_default (); } -static gchar * -get_display_name_for_path (GtkFileSystem *fs, - const GtkFilePath *path) + +struct SetDisplayNameData { - GtkFilePath *parent_path; - GtkFileFolder *folder; - gchar *retval; + GtkFileChooserButton *button; + GtkTreeRowReference *row_ref; +}; - parent_path = NULL; - retval = NULL; +static void +set_info_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer callback_data) +{ + gboolean cancelled = handle->cancelled; + GdkPixbuf *pixbuf; + GtkTreePath *path; + GtkTreeIter iter; + GtkFileSystemHandle *model_handle; + struct SetDisplayNameData *data = callback_data; - gtk_file_system_get_parent (fs, path, &parent_path, NULL); + path = gtk_tree_row_reference_get_path (data->row_ref); + if (!path) + /* Handle doesn't exist anymore in the model */ + goto out; - folder = gtk_file_system_get_folder (fs, parent_path ? parent_path : path, - GTK_FILE_INFO_DISPLAY_NAME, NULL); + gtk_tree_model_get_iter (data->button->priv->model, &iter, path); + gtk_tree_path_free (path); - if (folder) - { - GtkFileInfo *info; + /* 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); - info = gtk_file_folder_get_info (folder, path, NULL); - g_object_unref (folder); + if (cancelled || error) + /* There was an error, leave the fallback name in there */ + goto out; - if (info) - { - retval = g_strdup (gtk_file_info_get_display_name (info)); - gtk_file_info_free (info); - } - } + pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (data->button), + data->button->priv->icon_size, NULL); - if (parent_path) - gtk_file_path_free (parent_path); + gtk_list_store_set (GTK_LIST_STORE (data->button->priv->model), &iter, + ICON_COLUMN, pixbuf, + DISPLAY_NAME_COLUMN, gtk_file_info_get_display_name (info), + IS_FOLDER_COLUMN, gtk_file_info_get_is_folder (info), + -1); + + if (pixbuf) + g_object_unref (pixbuf); - if (!retval) - retval = g_strdup (_(FALLBACK_DISPLAY_NAME)); +out: + g_object_unref (data->button); + gtk_tree_row_reference_free (data->row_ref); + g_free (data); - return retval; + g_object_unref (handle); +} + +static void +set_info_for_path_at_iter (GtkFileChooserButton *button, + const GtkFilePath *path, + GtkTreeIter *iter) +{ + struct SetDisplayNameData *data; + GtkTreePath *tree_path; + GtkFileSystemHandle *handle; + + data = g_new0 (struct SetDisplayNameData, 1); + data->button = g_object_ref (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_list_store_set (GTK_LIST_STORE (button->priv->model), iter, + HANDLE_COLUMN, handle, + -1); } /* Shortcuts Model */ @@ -1314,12 +1527,17 @@ model_free_row_data (GtkFileChooserButton *button, { gchar type; gpointer data; + GtkFileSystemHandle *handle; 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: @@ -1336,16 +1554,70 @@ model_free_row_data (GtkFileChooserButton *button, } } +static void +model_add_special_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = handle->cancelled; + GtkTreeIter iter; + GtkTreePath *path; + GdkPixbuf *pixbuf; + GtkFileSystemHandle *model_handle; + struct ChangeIconThemeData *data = user_data; + + 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); + + 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: + gtk_tree_row_reference_free (data->row_ref); + g_free (data); + + g_object_unref (handle); +} + static inline void model_add_special (GtkFileChooserButton *button) { const gchar *homedir; - const gchar *display_name; gchar *desktopdir = NULL; GtkListStore *store; GtkTreeIter iter; GtkFilePath *path; - GdkPixbuf *pixbuf; gint pos; store = GTK_LIST_STORE (button->priv->model); @@ -1355,23 +1627,34 @@ model_add_special (GtkFileChooserButton *button) if (homedir) { + GtkTreePath *tree_path; + GtkFileSystemHandle *handle; + struct ChangeIconThemeData *info; + path = gtk_file_system_filename_to_path (button->priv->fs, homedir); - display_name = get_display_name_for_path (button->priv->fs, path); - pixbuf = gtk_file_system_render_icon (button->priv->fs, path, - GTK_WIDGET (button), - button->priv->icon_size, NULL); gtk_list_store_insert (store, &iter, pos); 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_ICON, + model_add_special_get_info_cb, info); + gtk_list_store_set (store, &iter, - ICON_COLUMN, pixbuf, - DISPLAY_NAME_COLUMN, display_name, + ICON_COLUMN, NULL, + DISPLAY_NAME_COLUMN, NULL, TYPE_COLUMN, ROW_TYPE_SPECIAL, DATA_COLUMN, path, + IS_FOLDER_COLUMN, TRUE, + HANDLE_COLUMN, handle, -1); - if (pixbuf) - g_object_unref (pixbuf); - g_free (display_name); button->priv->n_special++; #ifndef G_OS_WIN32 @@ -1385,22 +1668,34 @@ model_add_special (GtkFileChooserButton *button) if (desktopdir) { + GtkTreePath *tree_path; + GtkFileSystemHandle *handle; + struct ChangeIconThemeData *info; + path = gtk_file_system_filename_to_path (button->priv->fs, desktopdir); g_free (desktopdir); - pixbuf = gtk_file_system_render_icon (button->priv->fs, path, - GTK_WIDGET (button), - button->priv->icon_size, NULL); gtk_list_store_insert (store, &iter, pos); 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_ICON, + model_add_special_get_info_cb, info); + gtk_list_store_set (store, &iter, TYPE_COLUMN, ROW_TYPE_SPECIAL, - ICON_COLUMN, pixbuf, + ICON_COLUMN, NULL, DISPLAY_NAME_COLUMN, _(DESKTOP_DISPLAY_NAME), DATA_COLUMN, path, + IS_FOLDER_COLUMN, TRUE, -1); - if (pixbuf) - g_object_unref (pixbuf); button->priv->n_special++; } } @@ -1438,6 +1733,7 @@ model_add_volumes (GtkFileChooserButton *button, DISPLAY_NAME_COLUMN, display_name, TYPE_COLUMN, ROW_TYPE_VOLUME, DATA_COLUMN, volumes->data, + IS_FOLDER_COLUMN, TRUE, -1); if (pixbuf) @@ -1473,32 +1769,24 @@ model_add_bookmarks (GtkFileChooserButton *button, DISPLAY_NAME_COLUMN, NULL, TYPE_COLUMN, ROW_TYPE_BOOKMARK_SEPARATOR, DATA_COLUMN, NULL, + IS_FOLDER_COLUMN, FALSE, -1); button->priv->has_bookmark_separator = TRUE; } do { - GdkPixbuf *pixbuf; - gchar *display_name; - pos++; - pixbuf = gtk_file_system_render_icon (button->priv->fs, bookmarks->data, - GTK_WIDGET (button), - button->priv->icon_size, NULL); - display_name = get_display_name_for_path (button->priv->fs, - bookmarks->data); gtk_list_store_insert (store, &iter, pos); gtk_list_store_set (store, &iter, - ICON_COLUMN, pixbuf, - DISPLAY_NAME_COLUMN, display_name, + ICON_COLUMN, NULL, + DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME), TYPE_COLUMN, ROW_TYPE_BOOKMARK, DATA_COLUMN, gtk_file_path_copy (bookmarks->data), + IS_FOLDER_COLUMN, FALSE, -1); - if (pixbuf) - g_object_unref (pixbuf); - g_free (display_name); + set_info_for_path_at_iter (button, bookmarks->data, &iter); button->priv->n_bookmarks++; bookmarks = bookmarks->next; @@ -1513,8 +1801,6 @@ model_update_current_folder (GtkFileChooserButton *button, GtkListStore *store; GtkTreeIter iter; gint pos; - GdkPixbuf *pixbuf; - gchar *display_name; if (!path) return; @@ -1530,6 +1816,7 @@ model_update_current_folder (GtkFileChooserButton *button, 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; } @@ -1546,19 +1833,14 @@ model_update_current_folder (GtkFileChooserButton *button, model_free_row_data (button, &iter); } - pixbuf = gtk_file_system_render_icon (button->priv->fs, path, - GTK_WIDGET (button), - button->priv->icon_size, NULL); - display_name = get_display_name_for_path (button->priv->fs, path); gtk_list_store_set (store, &iter, - ICON_COLUMN, pixbuf, - DISPLAY_NAME_COLUMN, display_name, + 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); - if (pixbuf) - g_object_unref (pixbuf); - g_free (display_name); + set_info_for_path_at_iter (button, path, &iter); } static inline void @@ -1577,6 +1859,7 @@ model_add_other (GtkFileChooserButton *button) 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++; @@ -1587,6 +1870,7 @@ model_add_other (GtkFileChooserButton *button) DISPLAY_NAME_COLUMN, _("Other..."), TYPE_COLUMN, ROW_TYPE_OTHER, DATA_COLUMN, NULL, + IS_FOLDER_COLUMN, FALSE, -1); } @@ -1620,42 +1904,17 @@ model_remove_rows (GtkFileChooserButton *button, static inline gboolean test_if_path_is_visible (GtkFileSystem *fs, const GtkFilePath *path, - gboolean local_only) + gboolean local_only, + gboolean is_folder) { - GtkFilePath *parent_path; - GtkFileFolder *folder; - GtkFileInfo *info; - if (!path) return FALSE; if (local_only && !gtk_file_system_path_is_local (fs, path)) return FALSE; - parent_path = NULL; - gtk_file_system_get_parent (fs, path, &parent_path, NULL); - - folder = gtk_file_system_get_folder (fs, parent_path ? parent_path : path, - GTK_FILE_INFO_IS_FOLDER, NULL); - gtk_file_path_free (parent_path); - - if (folder) - { - info = gtk_file_folder_get_info (folder, path, NULL); - g_object_unref (folder); - } - else - info = NULL; - - if (!info) + if (!is_folder) return FALSE; - else if (!gtk_file_info_get_is_folder (info)) - { - gtk_file_info_free (info); - return FALSE; - } - - gtk_file_info_free (info); return TRUE; } @@ -1669,7 +1928,7 @@ filter_model_visible_func (GtkTreeModel *model, GtkFileChooserButtonPrivate *priv = button->priv; gchar type; gpointer data; - gboolean local_only, retval; + gboolean local_only, retval, is_folder; type = ROW_TYPE_INVALID; data = NULL; @@ -1678,6 +1937,7 @@ filter_model_visible_func (GtkTreeModel *model, gtk_tree_model_get (model, iter, TYPE_COLUMN, &type, DATA_COLUMN, &data, + IS_FOLDER_COLUMN, &is_folder, -1); switch (type) @@ -1688,7 +1948,7 @@ filter_model_visible_func (GtkTreeModel *model, case ROW_TYPE_SPECIAL: case ROW_TYPE_SHORTCUT: case ROW_TYPE_BOOKMARK: - retval = test_if_path_is_visible (priv->fs, data, local_only); + retval = test_if_path_is_visible (priv->fs, data, local_only, is_folder); break; case ROW_TYPE_VOLUME: { @@ -1836,6 +2096,43 @@ update_combo_box (GtkFileChooserButton *button) } /* 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 (priv->image)), + FALLBACK_ICON_NAME, + priv->icon_size, 0, NULL); + + 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_and_image (GtkFileChooserButton *button) { @@ -1848,11 +2145,10 @@ update_label_and_image (GtkFileChooserButton *button) label_text = NULL; pixbuf = NULL; - if (paths) + if (paths && paths->data) { - GtkFilePath *path, *parent_path; - GtkFileSystemVolume *volume; - GtkFileFolder *folder; + GtkFilePath *path; + GtkFileSystemVolume *volume = NULL; path = paths->data; @@ -1881,32 +2177,14 @@ update_label_and_image (GtkFileChooserButton *button) goto out; } - if (!pixbuf) - pixbuf = gtk_file_system_render_icon (priv->fs, path, - GTK_WIDGET (button), - priv->icon_size, NULL); + if (priv->update_button_handle) + gtk_file_system_cancel_operation (priv->update_button_handle); - parent_path = NULL; - gtk_file_system_get_parent (priv->fs, path, &parent_path, NULL); - - folder = gtk_file_system_get_folder (priv->fs, - parent_path ? parent_path : path, - GTK_FILE_INFO_DISPLAY_NAME, NULL); - gtk_file_path_free (parent_path); - - if (folder) - { - GtkFileInfo *info; - - info = gtk_file_folder_get_info (folder, path, NULL); - g_object_unref (folder); - - if (info) - { - label_text = g_strdup (gtk_file_info_get_display_name (info)); - gtk_file_info_free (info); - } - } + 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)); out: gtk_file_paths_free (paths); @@ -1919,15 +2197,6 @@ update_label_and_image (GtkFileChooserButton *button) } else gtk_label_set_text (GTK_LABEL (priv->label), _(FALLBACK_DISPLAY_NAME)); - - if (!pixbuf) - pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)), - FALLBACK_ICON_NAME, - priv->icon_size, 0, NULL); - - gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf); - if (pixbuf) - g_object_unref (pixbuf); } diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index b78370a64..27a7df701 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -170,6 +170,7 @@ enum { SHORTCUTS_COL_IS_VOLUME, SHORTCUTS_COL_REMOVABLE, SHORTCUTS_COL_PIXBUF_VISIBLE, + SHORTCUTS_COL_HANDLE, SHORTCUTS_COL_NUM_COLUMNS }; @@ -379,6 +380,8 @@ static void add_bookmark_button_clicked_cb (GtkButton *button, GtkFileChooserDefault *impl); static void remove_bookmark_button_clicked_cb (GtkButton *button, GtkFileChooserDefault *impl); +static void save_folder_combo_changed_cb (GtkComboBox *combo, + GtkFileChooserDefault *impl); static void list_icon_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, @@ -699,11 +702,17 @@ shortcuts_free_row_data (GtkFileChooserDefault *impl, { gpointer col_data; gboolean is_volume; + GtkFileSystemHandle *handle; gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), iter, SHORTCUTS_COL_DATA, &col_data, SHORTCUTS_COL_IS_VOLUME, &is_volume, + SHORTCUTS_COL_HANDLE, &handle, -1); + + if (handle) + gtk_file_system_cancel_operation (handle); + if (!col_data) return; @@ -809,10 +818,6 @@ gtk_file_chooser_default_finalize (GObject *object) shortcuts_free (impl); - g_signal_handler_disconnect (impl->file_system, impl->volumes_changed_id); - impl->volumes_changed_id = 0; - g_signal_handler_disconnect (impl->file_system, impl->bookmarks_changed_id); - impl->bookmarks_changed_id = 0; g_object_unref (impl->file_system); for (l = impl->filters; l; l = l->next) @@ -836,8 +841,6 @@ gtk_file_chooser_default_finalize (GObject *object) if (impl->preview_path) gtk_file_path_free (impl->preview_path); - pending_select_paths_free (impl); - load_remove_timer (impl); /* Free all the Models we have */ @@ -1099,9 +1102,57 @@ set_preview_widget (GtkFileChooserDefault *impl, } /* Re-reads all the icons for the shortcuts, used when the theme changes */ +struct ReloadIconsData +{ + GtkFileChooserDefault *impl; + GtkTreeRowReference *row_ref; +}; + +static void +shortcuts_reload_icons_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) +{ + GdkPixbuf *pixbuf; + GtkTreeIter iter; + GtkTreePath *path; + gboolean cancelled = handle->cancelled; + struct ReloadIconsData *data = user_data; + + if (!g_slist_find (data->impl->reload_icon_handles, handle)) + goto out; + + data->impl->reload_icon_handles = g_slist_remove (data->impl->reload_icon_handles, handle); + + if (cancelled || error) + goto out; + + pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (data->impl), + data->impl->icon_size, NULL); + + path = gtk_tree_row_reference_get_path (data->row_ref); + gtk_tree_model_get_iter (GTK_TREE_MODEL (data->impl->shortcuts_model), &iter, path); + gtk_list_store_set (data->impl->shortcuts_model, &iter, + SHORTCUTS_COL_PIXBUF, pixbuf, + -1); + gtk_tree_path_free (path); + + if (pixbuf) + g_object_unref (pixbuf); + +out: + gtk_tree_row_reference_free (data->row_ref); + g_object_unref (data->impl); + g_free (data); + + g_object_unref (handle); +} + static void shortcuts_reload_icons (GtkFileChooserDefault *impl) { + GSList *l; GtkTreeIter iter; profile_start ("start", NULL); @@ -1109,44 +1160,68 @@ shortcuts_reload_icons (GtkFileChooserDefault *impl) if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (impl->shortcuts_model), &iter)) goto out; - do { - gpointer data; - gboolean is_volume; - gboolean pixbuf_visible; - GdkPixbuf *pixbuf; + for (l = impl->reload_icon_handles; l; l = l->next) + { + GtkFileSystemHandle *handle = GTK_FILE_SYSTEM_HANDLE (l->data); + gtk_file_system_cancel_operation (handle); + } + g_slist_free (impl->reload_icon_handles); + impl->reload_icon_handles = NULL; - gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, - SHORTCUTS_COL_DATA, &data, - SHORTCUTS_COL_IS_VOLUME, &is_volume, - SHORTCUTS_COL_PIXBUF_VISIBLE, &pixbuf_visible, - -1); + do + { + gpointer data; + gboolean is_volume; + gboolean pixbuf_visible; + GdkPixbuf *pixbuf; - if (pixbuf_visible && data) - { - if (is_volume) - { - GtkFileSystemVolume *volume; + gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, + SHORTCUTS_COL_DATA, &data, + SHORTCUTS_COL_IS_VOLUME, &is_volume, + SHORTCUTS_COL_PIXBUF_VISIBLE, &pixbuf_visible, + -1); - volume = data; - pixbuf = gtk_file_system_volume_render_icon (impl->file_system, volume, GTK_WIDGET (impl), - impl->icon_size, NULL); - } - else - { - const GtkFilePath *path; + if (pixbuf_visible && data) + { + if (is_volume) + { + GtkFileSystemVolume *volume; - path = data; - pixbuf = gtk_file_system_render_icon (impl->file_system, path, GTK_WIDGET (impl), - impl->icon_size, NULL); - } + volume = data; + pixbuf = gtk_file_system_volume_render_icon (impl->file_system, volume, GTK_WIDGET (impl), + impl->icon_size, NULL); - gtk_list_store_set (impl->shortcuts_model, &iter, - SHORTCUTS_COL_PIXBUF, pixbuf, - -1); - if (pixbuf) - g_object_unref (pixbuf); - } - } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model),&iter)); + gtk_list_store_set (impl->shortcuts_model, &iter, + SHORTCUTS_COL_PIXBUF, pixbuf, + -1); + + if (pixbuf) + g_object_unref (pixbuf); + } + else + { + const GtkFilePath *path; + struct ReloadIconsData *info; + GtkTreePath *tree_path; + GtkFileSystemHandle *handle; + + path = data; + + info = g_new0 (struct ReloadIconsData, 1); + info->impl = g_object_ref (impl); + tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter); + info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->shortcuts_model), tree_path); + gtk_tree_path_free (tree_path); + + handle = gtk_file_system_get_info (impl->file_system, path, + GTK_FILE_INFO_ICON, + shortcuts_reload_icons_get_info_cb, + info); + impl->reload_icon_handles = g_slist_append (impl->reload_icon_handles, handle); + } + } + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model),&iter)); out: @@ -1183,83 +1258,203 @@ shortcuts_find_current_folder (GtkFileChooserDefault *impl) shortcuts_find_folder (impl, impl->current_folder); } -/* Convenience function to get the display name and icon info for a path */ -static GtkFileInfo * -get_file_info (GtkFileSystem *file_system, - const GtkFilePath *path, - gboolean name_only, - GError **error) +/* Removes the specified number of rows from the shortcuts list */ +static void +shortcuts_remove_rows (GtkFileChooserDefault *impl, + int start_row, + int n_rows) +{ + GtkTreePath *path; + + path = gtk_tree_path_new_from_indices (start_row, -1); + + for (; n_rows; n_rows--) + { + GtkTreeIter iter; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->shortcuts_model), &iter, path)) + g_assert_not_reached (); + + shortcuts_free_row_data (impl, &iter); + gtk_list_store_remove (impl->shortcuts_model, &iter); + } + + gtk_tree_path_free (path); +} + +static void +shortcuts_update_count (GtkFileChooserDefault *impl, + ShortcutsIndex type, + gint value) { - GtkFilePath *parent_path; - GtkFileFolder *parent_folder; - GtkFileInfo *info; - GError *tmp = NULL; + switch (type) + { + case SHORTCUTS_HOME: + if (value < 0) + impl->has_home = FALSE; + else + impl->has_home = TRUE; + break; - profile_start ("start", (char *) path); + case SHORTCUTS_DESKTOP: + if (value < 0) + impl->has_desktop = FALSE; + else + impl->has_desktop = TRUE; + break; + + case SHORTCUTS_VOLUMES: + impl->num_volumes += value; + break; + + case SHORTCUTS_SHORTCUTS: + impl->num_shortcuts += value; + break; + + case SHORTCUTS_BOOKMARKS: + impl->num_bookmarks += value; + break; + + case SHORTCUTS_CURRENT_FOLDER: + if (value < 0) + impl->shortcuts_current_folder_active = FALSE; + else + impl->shortcuts_current_folder_active = TRUE; + break; + + default: + /* nothing */ + break; + } +} + +struct ShortcutsInsertRequest +{ + GtkFileChooserDefault *impl; + GtkFilePath *parent_path; + GtkFilePath *path; + int pos; + char *label_copy; + GtkTreeRowReference *row_ref; + ShortcutsIndex type; + gboolean name_only; + gboolean removable; +}; - parent_path = NULL; - info = NULL; +static void +get_file_info_finished (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer data) +{ + gint pos = -1; + gboolean cancelled = handle->cancelled; + gboolean is_volume = FALSE; + GdkPixbuf *pixbuf; + GtkTreePath *path; + GtkTreeIter iter; + GtkFileSystemHandle *model_handle; + struct ShortcutsInsertRequest *request = data; - if (!gtk_file_system_get_parent (file_system, path, &parent_path, &tmp)) + path = gtk_tree_row_reference_get_path (request->row_ref); + if (!path) + /* Handle doesn't exist anymore in the model */ goto out; - parent_folder = gtk_file_system_get_folder (file_system, parent_path ? parent_path : path, - GTK_FILE_INFO_DISPLAY_NAME - | (name_only ? 0 : GTK_FILE_INFO_IS_FOLDER), - &tmp); - if (!parent_folder) + pos = gtk_tree_path_get_indices (path)[0]; + gtk_tree_model_get_iter (GTK_TREE_MODEL (request->impl->shortcuts_model), + &iter, path); + gtk_tree_path_free (path); + + /* validate handle, else goto out */ + gtk_tree_model_get (GTK_TREE_MODEL (request->impl->shortcuts_model), &iter, + SHORTCUTS_COL_HANDLE, &model_handle, + -1); + if (handle != model_handle) goto out; - info = gtk_file_folder_get_info (parent_folder, parent_path ? path : NULL, &tmp); - g_object_unref (parent_folder); + /* set the handle to NULL in the model (we unref later on) */ + gtk_list_store_set (request->impl->shortcuts_model, &iter, + SHORTCUTS_COL_HANDLE, NULL, + -1); - out: - if (parent_path) - gtk_file_path_free (parent_path); + if (cancelled) + goto out; - if (tmp) + if (!info) { - g_set_error (error, - GTK_FILE_CHOOSER_ERROR, - GTK_FILE_CHOOSER_ERROR_BAD_FILENAME, - _("Could not get information about '%s': %s"), - gtk_file_path_get_string (path), - tmp->message); - g_error_free (tmp); - } + gtk_list_store_remove (request->impl->shortcuts_model, &iter); + shortcuts_update_count (request->impl, request->type, -1); - profile_end ("end", (char *) path); + if (request->type == SHORTCUTS_HOME) + { + const char *home = g_get_home_dir (); + GtkFilePath *home_path; - return info; -} + home_path = gtk_file_system_filename_to_path (request->impl->file_system, home); + error_getting_info_dialog (request->impl, home_path, g_error_copy (error)); + gtk_file_path_free (home_path); + } + else if (request->type == SHORTCUTS_CURRENT_FOLDER) + { + /* Remove the current folder separator */ + gint separator_pos = shortcuts_get_index (request->impl, SHORTCUTS_CURRENT_FOLDER_SEPARATOR); + shortcuts_remove_rows (request->impl, separator_pos, 1); + } -/* Returns whether a path is a folder */ -static gboolean -check_is_folder (GtkFileSystem *file_system, - const GtkFilePath *path, - GError **error) -{ - GtkFileFolder *folder; + goto out; + } + + if (!request->label_copy) + request->label_copy = g_strdup (gtk_file_info_get_display_name (info)); + pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (request->impl), + request->impl->icon_size, NULL); - profile_start ("start", (char *) path); + gtk_list_store_set (request->impl->shortcuts_model, &iter, + SHORTCUTS_COL_PIXBUF, pixbuf, + SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE, + SHORTCUTS_COL_NAME, request->label_copy, + SHORTCUTS_COL_IS_VOLUME, is_volume, + SHORTCUTS_COL_REMOVABLE, request->removable, + -1); - folder = gtk_file_system_get_folder (file_system, path, 0, error); - if (!folder) + if (request->impl->shortcuts_filter_model) + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (request->impl->shortcuts_filter_model)); + + if (request->type == SHORTCUTS_CURRENT_FOLDER + && request->impl->save_folder_combo != NULL) { - profile_end ("end - is not folder", (char *) path); - return FALSE; + /* The current folder is updated via _activate_iter(), don't + * have save_folder_combo_changed_cb() call _activate_iter() + * again. + */ + g_signal_handlers_block_by_func (request->impl->save_folder_combo, + G_CALLBACK (save_folder_combo_changed_cb), + request->impl); + gtk_combo_box_set_active (GTK_COMBO_BOX (request->impl->save_folder_combo), pos); + g_signal_handlers_unblock_by_func (request->impl->save_folder_combo, + G_CALLBACK (save_folder_combo_changed_cb), + request->impl); } - g_object_unref (folder); + if (pixbuf) + g_object_unref (pixbuf); - profile_end ("end", (char *) path); - return TRUE; +out: + g_object_unref (request->impl); + gtk_file_path_free (request->parent_path); + gtk_file_path_free (request->path); + gtk_tree_row_reference_free (request->row_ref); + g_free (request->label_copy); + g_free (request); + + g_object_unref (handle); } /* Inserts a path in the shortcuts tree, making a copy of it; alternatively, * inserts a volume. A position of -1 indicates the end of the tree. */ -static gboolean +static void shortcuts_insert_path (GtkFileChooserDefault *impl, int pos, gboolean is_volume, @@ -1267,11 +1462,11 @@ shortcuts_insert_path (GtkFileChooserDefault *impl, const GtkFilePath *path, const char *label, gboolean removable, - GError **error) + ShortcutsIndex type) { char *label_copy; - GdkPixbuf *pixbuf; - gpointer data; + GdkPixbuf *pixbuf = NULL; + gpointer data = NULL; GtkTreeIter iter; profile_start ("start", is_volume ? "volume" : (char *) path); @@ -1285,32 +1480,54 @@ shortcuts_insert_path (GtkFileChooserDefault *impl, } else { + struct ShortcutsInsertRequest *request; + GtkFileSystemHandle *handle; + GtkTreePath *p; + + request = g_new0 (struct ShortcutsInsertRequest, 1); + request->impl = g_object_ref (impl); + request->path = gtk_file_path_copy (path); + request->name_only = TRUE; + request->removable = removable; + request->pos = pos; + request->type = type; if (label) - label_copy = g_strdup (label); + request->label_copy = g_strdup (label); + + if (pos == -1) + gtk_list_store_append (impl->shortcuts_model, &iter); else - { - GtkFileInfo *info = get_file_info (impl->file_system, path, TRUE, error); + gtk_list_store_insert (impl->shortcuts_model, &iter, pos); - if (!info) - { - profile_end ("end - could not get info", (char *) path); - return FALSE; - } + p = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter); + request->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->shortcuts_model), p); + gtk_tree_path_free (p); - label_copy = g_strdup (gtk_file_info_get_display_name (info)); - gtk_file_info_free (info); - } + handle = gtk_file_system_get_info (request->impl->file_system, request->path, + GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_HIDDEN | GTK_FILE_INFO_ICON, + get_file_info_finished, request); - data = gtk_file_path_copy (path); - pixbuf = gtk_file_system_render_icon (impl->file_system, path, GTK_WIDGET (impl), - impl->icon_size, NULL); + gtk_list_store_set (impl->shortcuts_model, &iter, + SHORTCUTS_COL_DATA, gtk_file_path_copy (path), + SHORTCUTS_COL_IS_VOLUME, is_volume, + SHORTCUTS_COL_HANDLE, handle, + -1); + + shortcuts_update_count (impl, type, 1); + + return; } + if (!data) + data = gtk_file_path_copy (path); + if (pos == -1) gtk_list_store_append (impl->shortcuts_model, &iter); else gtk_list_store_insert (impl->shortcuts_model, &iter, pos); + shortcuts_update_count (impl, type, 1); + gtk_list_store_set (impl->shortcuts_model, &iter, SHORTCUTS_COL_PIXBUF, pixbuf, SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE, @@ -1318,16 +1535,34 @@ shortcuts_insert_path (GtkFileChooserDefault *impl, SHORTCUTS_COL_DATA, data, SHORTCUTS_COL_IS_VOLUME, is_volume, SHORTCUTS_COL_REMOVABLE, removable, + SHORTCUTS_COL_HANDLE, NULL, -1); + if (impl->shortcuts_filter_model) + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model)); + + if (type == SHORTCUTS_CURRENT_FOLDER && impl->save_folder_combo != NULL) + { + /* The current folder is updated via _activate_iter(), don't + * have save_folder_combo_changed_cb() call _activate_iter() + * again. + */ + gint combo_pos = shortcuts_get_index (impl, SHORTCUTS_CURRENT_FOLDER); + g_signal_handlers_block_by_func (impl->save_folder_combo, + G_CALLBACK (save_folder_combo_changed_cb), + impl); + gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), combo_pos); + g_signal_handlers_unblock_by_func (impl->save_folder_combo, + G_CALLBACK (save_folder_combo_changed_cb), + impl); + } + g_free (label_copy); if (pixbuf) g_object_unref (pixbuf); profile_end ("end", NULL); - - return TRUE; } /* Appends an item for the user's home directory to the shortcuts model */ @@ -1336,7 +1571,6 @@ shortcuts_append_home (GtkFileChooserDefault *impl) { const char *home; GtkFilePath *home_path; - GError *error; profile_start ("start", NULL); @@ -1349,10 +1583,7 @@ shortcuts_append_home (GtkFileChooserDefault *impl) home_path = gtk_file_system_filename_to_path (impl->file_system, home); - error = NULL; - impl->has_home = shortcuts_insert_path (impl, -1, FALSE, NULL, home_path, NULL, FALSE, &error); - if (!impl->has_home) - error_getting_info_dialog (impl, home_path, error); + shortcuts_insert_path (impl, -1, FALSE, NULL, home_path, NULL, FALSE, SHORTCUTS_HOME); gtk_file_path_free (home_path); @@ -1385,7 +1616,7 @@ shortcuts_append_desktop (GtkFileChooserDefault *impl) path = gtk_file_system_filename_to_path (impl->file_system, name); g_free (name); - impl->has_desktop = shortcuts_insert_path (impl, -1, FALSE, NULL, path, _("Desktop"), FALSE, NULL); + shortcuts_insert_path (impl, -1, FALSE, NULL, path, _("Desktop"), FALSE, SHORTCUTS_DESKTOP); /* We do not actually pop up an error dialog if there is no desktop directory * because some people may really not want to have one. */ @@ -1424,8 +1655,8 @@ shortcuts_append_paths (GtkFileChooserDefault *impl, label = gtk_file_system_get_bookmark_label (impl->file_system, path); /* NULL GError, but we don't really want to show error boxes here */ - if (shortcuts_insert_path (impl, start_row + num_inserted, FALSE, NULL, path, label, TRUE, NULL)) - num_inserted++; + shortcuts_insert_path (impl, start_row + num_inserted, FALSE, NULL, path, label, TRUE, SHORTCUTS_BOOKMARKS); + num_inserted++; g_free (label); } @@ -1490,30 +1721,6 @@ shortcuts_get_index (GtkFileChooserDefault *impl, return n; } -/* Removes the specified number of rows from the shortcuts list */ -static void -shortcuts_remove_rows (GtkFileChooserDefault *impl, - int start_row, - int n_rows) -{ - GtkTreePath *path; - - path = gtk_tree_path_new_from_indices (start_row, -1); - - for (; n_rows; n_rows--) - { - GtkTreeIter iter; - - if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->shortcuts_model), &iter, path)) - g_assert_not_reached (); - - shortcuts_free_row_data (impl, &iter); - gtk_list_store_remove (impl->shortcuts_model, &iter); - } - - gtk_tree_path_free (path); -} - /* Adds all the file system volumes to the shortcuts model */ static void shortcuts_add_volumes (GtkFileChooserDefault *impl) @@ -1563,10 +1770,8 @@ shortcuts_add_volumes (GtkFileChooserDefault *impl) } } - if (shortcuts_insert_path (impl, start_row + n, TRUE, volume, NULL, NULL, FALSE, NULL)) - n++; - else - gtk_file_system_volume_free (impl->file_system, volume); + shortcuts_insert_path (impl, start_row + n, TRUE, volume, NULL, NULL, FALSE, SHORTCUTS_VOLUMES); + n++; } impl->num_volumes = n; @@ -1613,6 +1818,7 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl) profile_start ("start", NULL); + old_changing_folders = impl->changing_folder; impl->changing_folder = TRUE; @@ -1647,8 +1853,10 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl) shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS_SEPARATOR), impl->num_bookmarks + 1); + impl->num_bookmarks = 0; + bookmarks = gtk_file_system_list_bookmarks (impl->file_system); - impl->num_bookmarks = shortcuts_append_paths (impl, bookmarks); + shortcuts_append_paths (impl, bookmarks); gtk_file_paths_free (bookmarks); if (impl->num_bookmarks > 0) @@ -1715,26 +1923,15 @@ shortcuts_add_current_folder (GtkFileChooserDefault *impl) if (base_path && strcmp (gtk_file_path_get_string (base_path), gtk_file_path_get_string (impl->current_folder)) == 0) { - success = shortcuts_insert_path (impl, pos, TRUE, volume, NULL, NULL, FALSE, NULL); - if (success) - volume = NULL; + shortcuts_insert_path (impl, pos, TRUE, volume, NULL, NULL, FALSE, SHORTCUTS_CURRENT_FOLDER); } else - success = shortcuts_insert_path (impl, pos, FALSE, NULL, impl->current_folder, NULL, FALSE, NULL); - - if (volume) - gtk_file_system_volume_free (impl->file_system, volume); + shortcuts_insert_path (impl, pos, FALSE, NULL, impl->current_folder, NULL, FALSE, SHORTCUTS_CURRENT_FOLDER); if (base_path) gtk_file_path_free (base_path); - - if (!success) - shortcuts_remove_rows (impl, pos - 1, 1); /* remove the separator */ - - impl->shortcuts_current_folder_active = success; } - - if (success && impl->save_folder_combo != NULL) + else if (impl->save_folder_combo != NULL) gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), pos); } @@ -1788,7 +1985,8 @@ shortcuts_model_create (GtkFileChooserDefault *impl) G_TYPE_POINTER, /* path or volume */ G_TYPE_BOOLEAN, /* is the previous column a volume? */ G_TYPE_BOOLEAN, /* removable */ - G_TYPE_BOOLEAN); /* pixbuf cell visibility */ + G_TYPE_BOOLEAN, /* pixbuf cell visibility */ + G_TYPE_OBJECT); /* GtkFileSystemHandle */ if (impl->file_system) { @@ -1837,6 +2035,33 @@ new_folder_button_clicked (GtkButton *button, gtk_tree_path_free (path); } +static void +edited_idle_create_folder_cb (GtkFileSystemHandle *handle, + const GtkFilePath *path, + const GError *error, + gpointer data) +{ + gboolean cancelled = handle->cancelled; + GtkFileChooserDefault *impl = data; + + if (!g_slist_find (impl->pending_handles, handle)) + goto out; + + impl->pending_handles = g_slist_remove (impl->pending_handles, handle); + + if (cancelled) + goto out; + + if (!error) + change_folder_and_display_error (impl, path); + else + error_creating_folder_dialog (impl, path, g_error_copy (error)); + + out: + g_object_unref (impl); + g_object_unref (handle); +} + /* Idle handler for creating a new folder after editing its name cell, or for * canceling the editing. */ @@ -1859,15 +2084,18 @@ edited_idle_cb (GtkFileChooserDefault *impl) GtkFilePath *file_path; error = NULL; - file_path = gtk_file_system_make_path (impl->file_system, impl->current_folder, impl->edited_new_text, + file_path = gtk_file_system_make_path (impl->file_system, + impl->current_folder, + impl->edited_new_text, &error); if (file_path) { - error = NULL; - if (gtk_file_system_create_folder (impl->file_system, file_path, &error)) - change_folder_and_display_error (impl, file_path); - else - error_creating_folder_dialog (impl, file_path, error); + GtkFileSystemHandle *handle; + + handle = gtk_file_system_create_folder (impl->file_system, file_path, + edited_idle_create_folder_cb, + g_object_ref (impl)); + impl->pending_handles = g_slist_append (impl->pending_handles, handle); gtk_file_path_free (file_path); } @@ -1981,6 +2209,12 @@ shortcut_find_position (GtkFileChooserDefault *impl, current_folder_separator_idx = shortcuts_get_index (impl, SHORTCUTS_CURRENT_FOLDER_SEPARATOR); +#if 0 + /* FIXME: is this still needed? */ + if (current_folder_separator_idx >= impl->shortcuts_model->length) + return -1; +#endif + for (i = 0; i < current_folder_separator_idx; i++) { gpointer col_data; @@ -3415,52 +3649,134 @@ error_selecting_dragged_file_dialog (GtkFileChooserDefault *impl, } static void -file_list_drag_data_received_cb (GtkWidget *widget, - GdkDragContext *context, - gint x, - gint y, - GtkSelectionData *selection_data, - guint info, - guint time_, - gpointer data) +file_list_drag_data_select_uris (GtkFileChooserDefault *impl, + gchar **uris) { - GtkFileChooserDefault *impl; - GtkFileChooser *chooser; - gchar **uris; + int i; char *uri; - GtkFilePath *path; - GError *error = NULL; - gint i; - - impl = GTK_FILE_CHOOSER_DEFAULT (data); - chooser = GTK_FILE_CHOOSER (data); - - /* Parse the text/uri-list string, navigate to the first one */ - uris = g_uri_list_extract_uris ((const char *) selection_data->data); - if (uris[0]) + GtkFileChooser *chooser = GTK_FILE_CHOOSER (impl); + + for (i = 1; uris[i]; i++) { - uri = uris[0]; - path = gtk_file_system_uri_to_path (impl->file_system, uri); - + GtkFilePath *path; + + uri = uris[i]; + path = gtk_file_system_uri_to_path (impl->file_system, uri); + if (path) - { - if ((impl->action == GTK_FILE_CHOOSER_ACTION_OPEN || - impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) && - uris[1] == 0 && - check_is_folder (impl->file_system, path, NULL)) - change_folder_and_display_error (impl, path); - else - { - gtk_file_chooser_default_unselect_all (chooser); - gtk_file_chooser_default_select_path (chooser, path, &error); - if (error) - error_selecting_dragged_file_dialog (impl, path, error); - else - browse_files_center_selected_row (impl); - } + { + GError *error = NULL; + + gtk_file_chooser_default_select_path (chooser, path, &error); + if (error) + error_selecting_dragged_file_dialog (impl, path, error); gtk_file_path_free (path); } + } +} + +struct FileListDragData +{ + GtkFileChooserDefault *impl; + gchar **uris; + GtkFilePath *path; +}; + +static void +file_list_drag_data_received_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = handle->cancelled; + struct FileListDragData *data = user_data; + GtkFileChooser *chooser = GTK_FILE_CHOOSER (data->impl); + + if (handle != data->impl->file_list_drag_data_received_handle) + goto out; + + data->impl->file_list_drag_data_received_handle = NULL; + + if (cancelled || error) + goto out; + + if ((data->impl->action == GTK_FILE_CHOOSER_ACTION_OPEN || + data->impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) && + data->uris[1] == 0 && !error && + gtk_file_info_get_is_folder (info)) + change_folder_and_display_error (data->impl, data->path); + else + { + GError *error = NULL; + + gtk_file_chooser_default_unselect_all (chooser); + gtk_file_chooser_default_select_path (chooser, data->path, &error); + if (error) + error_selecting_dragged_file_dialog (data->impl, data->path, error); + else + browse_files_center_selected_row (data->impl); + } + + if (data->impl->select_multiple) + file_list_drag_data_select_uris (data->impl, data->uris); + +out: + g_object_unref (data->impl); + g_strfreev (data->uris); + gtk_file_path_free (data->path); + g_free (data); + + g_object_unref (handle); +} + +static void +file_list_drag_data_received_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time_, + gpointer data) +{ + GtkFileChooserDefault *impl; + GtkFileChooser *chooser; + gchar **uris; + char *uri; + GtkFilePath *path; + GError *error = NULL; + gint i; + + impl = GTK_FILE_CHOOSER_DEFAULT (data); + chooser = GTK_FILE_CHOOSER (data); + + /* Parse the text/uri-list string, navigate to the first one */ + uris = g_uri_list_extract_uris ((const char *) selection_data->data); + if (uris[0]) + { + uri = uris[0]; + path = gtk_file_system_uri_to_path (impl->file_system, uri); + + if (path) + { + struct FileListDragData *data; + + data = g_new0 (struct FileListDragData, 1); + data->impl = g_object_ref (impl); + data->uris = uris; + data->path = path; + + if (impl->file_list_drag_data_received_handle) + gtk_file_system_cancel_operation (impl->file_list_drag_data_received_handle); + + impl->file_list_drag_data_received_handle = + gtk_file_system_get_info (impl->file_system, path, + GTK_FILE_INFO_IS_FOLDER, + file_list_drag_data_received_get_info_cb, + data); + goto out; + } else { g_set_error (&error, @@ -3472,28 +3788,13 @@ file_list_drag_data_received_cb (GtkWidget *widget, error_selecting_dragged_file_dialog (impl, NULL, error); } - if (impl->select_multiple) - { - for (i = 1; uris[i]; i++) - { - uri = uris[i]; - path = gtk_file_system_uri_to_path (impl->file_system, uri); - - if (path) - { - gtk_file_chooser_default_select_path (chooser, path, &error); - if (error) - error_selecting_dragged_file_dialog (impl, path, error); - - gtk_file_path_free (path); - } - } - } + file_list_drag_data_select_uris (impl, uris); } g_strfreev (uris); +out: g_signal_stop_emission_by_name (widget, "drag-data-received"); } @@ -4518,6 +4819,7 @@ remove_settings_signal (GtkFileChooserDefault *impl, static void gtk_file_chooser_default_dispose (GObject *object) { + GSList *l; GtkFileChooserDefault *impl = (GtkFileChooserDefault *) object; if (impl->extra_widget) @@ -4526,6 +4828,90 @@ gtk_file_chooser_default_dispose (GObject *object) impl->extra_widget = NULL; } + if (impl->volumes_changed_id > 0) + { + g_signal_handler_disconnect (impl->file_system, impl->volumes_changed_id); + impl->volumes_changed_id = 0; + } + + if (impl->bookmarks_changed_id > 0) + { + g_signal_handler_disconnect (impl->file_system, impl->bookmarks_changed_id); + impl->bookmarks_changed_id = 0; + } + + pending_select_paths_free (impl); + + /* cancel all pending operations */ + if (impl->pending_handles) + { + for (l = impl->pending_handles; l; l = l->next) + { + GtkFileSystemHandle *handle =l->data; + gtk_file_system_cancel_operation (handle); + } + g_slist_free (impl->pending_handles); + impl->pending_handles = NULL; + } + + if (impl->reload_icon_handles) + { + for (l = impl->reload_icon_handles; l; l = l->next) + { + GtkFileSystemHandle *handle =l->data; + gtk_file_system_cancel_operation (handle); + } + g_slist_free (impl->reload_icon_handles); + impl->reload_icon_handles = NULL; + } + + if (impl->loading_shortcuts) + { + for (l = impl->loading_shortcuts; l; l = l->next) + { + GtkFileSystemHandle *handle =l->data; + gtk_file_system_cancel_operation (handle); + } + g_slist_free (impl->loading_shortcuts); + impl->loading_shortcuts = NULL; + } + + if (impl->file_list_drag_data_received_handle) + { + gtk_file_system_cancel_operation (impl->file_list_drag_data_received_handle); + impl->file_list_drag_data_received_handle = NULL; + } + + if (impl->update_current_folder_handle) + { + gtk_file_system_cancel_operation (impl->update_current_folder_handle); + impl->update_current_folder_handle = NULL; + } + + if (impl->show_and_select_paths_handle) + { + gtk_file_system_cancel_operation (impl->show_and_select_paths_handle); + impl->show_and_select_paths_handle = NULL; + } + + if (impl->should_respond_get_info_handle) + { + gtk_file_system_cancel_operation (impl->should_respond_get_info_handle); + impl->should_respond_get_info_handle = NULL; + } + + if (impl->update_from_entry_handle) + { + gtk_file_system_cancel_operation (impl->update_from_entry_handle); + impl->update_from_entry_handle = NULL; + } + + if (impl->shortcuts_activate_iter_handle) + { + gtk_file_system_cancel_operation (impl->shortcuts_activate_iter_handle); + impl->shortcuts_activate_iter_handle = NULL; + } + remove_settings_signal (impl, gtk_widget_get_screen (GTK_WIDGET (impl))); G_OBJECT_CLASS (parent_class)->dispose (object); @@ -4777,11 +5163,12 @@ gtk_file_chooser_default_map (GtkWidget *widget) break; case RELOAD_WAS_UNMAPPED: - /* Just reload the current folder */ - g_assert (impl->current_folder != NULL); - - pending_select_paths_store_selection (impl); - change_folder_and_display_error (impl, impl->current_folder); + /* Just reload the current folder; else continue the pending load. */ + if (impl->current_folder) + { + pending_select_paths_store_selection (impl); + change_folder_and_display_error (impl, impl->current_folder); + } break; default: @@ -5092,36 +5479,25 @@ browse_files_center_selected_row (GtkFileChooserDefault *impl) gtk_tree_selection_selected_foreach (selection, center_selected_row_foreach_cb, &closure); } -static gboolean -show_and_select_paths (GtkFileChooserDefault *impl, - const GtkFilePath *parent_path, - GSList *paths, - GError **error) +struct ShowAndSelectPathsData +{ + GtkFileChooserDefault *impl; + GSList *paths; +}; + +static void +show_and_select_paths_finished_loading (GtkFileFolder *folder, + gpointer user_data) { - GtkFileFolder *folder; gboolean have_hidden; gboolean have_filtered; GSList *l; - - profile_start ("start", NULL); - - if (!paths) - { - profile_end ("end", NULL); - return TRUE; - } - - folder = gtk_file_system_get_folder (impl->file_system, parent_path, GTK_FILE_INFO_IS_FOLDER | GTK_FILE_INFO_IS_HIDDEN, error); - if (!folder) - { - profile_end ("end", NULL); - return FALSE; - } + struct ShowAndSelectPathsData *data = user_data; have_hidden = FALSE; have_filtered = FALSE; - for (l = paths; l; l = l->next) + for (l = data->paths; l; l = l->next) { const GtkFilePath *path; GtkFileInfo *info; @@ -5136,7 +5512,7 @@ show_and_select_paths (GtkFileChooserDefault *impl, have_hidden = gtk_file_info_get_is_hidden (info); if (!have_filtered) - have_filtered = !gtk_file_info_get_is_folder (info) && get_is_file_filtered (impl, path, info); + have_filtered = !gtk_file_info_get_is_folder (info) && get_is_file_filtered (data->impl, path, info); gtk_file_info_free (info); @@ -5145,22 +5521,95 @@ show_and_select_paths (GtkFileChooserDefault *impl, } } + g_signal_handlers_disconnect_by_func (folder, + show_and_select_paths_finished_loading, + user_data); + g_object_unref (folder); if (have_hidden) - g_object_set (impl, "show-hidden", TRUE, NULL); + g_object_set (data->impl, "show-hidden", TRUE, NULL); if (have_filtered) - set_current_filter (impl, NULL); + set_current_filter (data->impl, NULL); - for (l = paths; l; l = l->next) + for (l = data->paths; l; l = l->next) { const GtkFilePath *path; path = l->data; - _gtk_file_system_model_path_do (impl->browse_files_model, path, select_func, impl); + _gtk_file_system_model_path_do (data->impl->browse_files_model, path, + select_func, data->impl); + } + + gtk_file_paths_free (data->paths); + g_free (data); +} + +static void +show_and_select_paths_get_folder_cb (GtkFileSystemHandle *handle, + GtkFileFolder *folder, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = handle->cancelled; + struct ShowAndSelectPathsData *data = user_data; + + if (data->impl->show_and_select_paths_handle != handle) + goto out; + + data->impl->show_and_select_paths_handle = NULL; + + if (cancelled || error) + goto out; + + g_object_unref (handle); + + if (gtk_file_folder_is_finished_loading (folder)) + show_and_select_paths_finished_loading (folder, user_data); + else + g_signal_connect (folder, "finished-loading", + G_CALLBACK (show_and_select_paths_finished_loading), + user_data); + + return; + +out: + g_object_unref (data->impl); + gtk_file_paths_free (data->paths); + g_free (data); + + g_object_unref (handle); +} + +static gboolean +show_and_select_paths (GtkFileChooserDefault *impl, + const GtkFilePath *parent_path, + GSList *paths, + GError **error) +{ + struct ShowAndSelectPathsData *info; + + profile_start ("start", NULL); + + if (!paths) + { + profile_end ("end", NULL); + return TRUE; } + info = g_new (struct ShowAndSelectPathsData, 1); + info->impl = g_object_ref (impl); + info->paths = gtk_file_paths_copy (paths); + + if (impl->show_and_select_paths_handle) + gtk_file_system_cancel_operation (impl->show_and_select_paths_handle); + + impl->show_and_select_paths_handle = + gtk_file_system_get_folder (impl->file_system, parent_path, + GTK_FILE_INFO_IS_FOLDER | GTK_FILE_INFO_IS_HIDDEN, + show_and_select_paths_get_folder_cb, info); + profile_end ("end", NULL); return TRUE; } @@ -5343,50 +5792,52 @@ gtk_file_chooser_default_set_current_folder (GtkFileChooser *chooser, return gtk_file_chooser_default_update_current_folder (chooser, path, FALSE, error); } -static gboolean -gtk_file_chooser_default_update_current_folder (GtkFileChooser *chooser, - const GtkFilePath *path, - gboolean keep_trail, - GError **error) + +struct UpdateCurrentFolderData { - GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - gboolean result; + GtkFileChooserDefault *impl; + GtkFilePath *path; + gboolean keep_trail; +}; - profile_start ("start", (char *) path); +static void +update_current_folder_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = handle->cancelled; + struct UpdateCurrentFolderData *data = user_data; + GtkFileChooserDefault *impl = data->impl; - g_assert (path != NULL); + if (handle != impl->update_current_folder_handle) + goto out; - if (impl->local_only && - !gtk_file_system_path_is_local (impl->file_system, path)) - { - g_set_error (error, - GTK_FILE_CHOOSER_ERROR, - GTK_FILE_CHOOSER_ERROR_BAD_FILENAME, - _("Cannot change to folder because it is not local")); + impl->update_current_folder_handle = NULL; - profile_end ("end - not local", (char *) path); - return FALSE; - } + set_busy_cursor (impl, FALSE); - /* Test validity of path here. */ - if (!check_is_folder (impl->file_system, path, error)) - { - profile_end ("end - not a folder", (char *) path); - return FALSE; - } + if (cancelled) + goto out; - if (!_gtk_path_bar_set_path (GTK_PATH_BAR (impl->browse_path_bar), path, keep_trail, error)) + if (error) { - profile_end ("end - could not set path bar", (char *) path); - return FALSE; + error_changing_folder_dialog (impl, data->path, g_error_copy (error)); + goto out; } - if (impl->current_folder != path) + if (!gtk_file_info_get_is_folder (info)) + goto out; + + if (!_gtk_path_bar_set_path (GTK_PATH_BAR (impl->browse_path_bar), data->path, data->keep_trail, NULL)) + goto out; + + if (impl->current_folder != data->path) { if (impl->current_folder) gtk_file_path_free (impl->current_folder); - impl->current_folder = gtk_file_path_copy (path); + impl->current_folder = gtk_file_path_copy (data->path); impl->reload_state = RELOAD_HAS_FOLDER; } @@ -5412,7 +5863,7 @@ gtk_file_chooser_default_update_current_folder (GtkFileChooser *chooser, * but perform more actions rather than returning immediately even if it * generates an error. */ - result = set_list_model (impl, error); + set_list_model (impl, NULL); /* Refresh controls */ @@ -5425,8 +5876,56 @@ gtk_file_chooser_default_update_current_folder (GtkFileChooser *chooser, g_signal_emit_by_name (impl, "selection-changed", 0); +out: + gtk_file_path_free (data->path); + g_free (data); + + g_object_unref (handle); +} + +static gboolean +gtk_file_chooser_default_update_current_folder (GtkFileChooser *chooser, + const GtkFilePath *path, + gboolean keep_trail, + GError **error) +{ + GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); + struct UpdateCurrentFolderData *data; + + profile_start ("start", (char *) path); + + g_assert (path != NULL); + + if (impl->local_only && + !gtk_file_system_path_is_local (impl->file_system, path)) + { + g_set_error (error, + GTK_FILE_CHOOSER_ERROR, + GTK_FILE_CHOOSER_ERROR_BAD_FILENAME, + _("Cannot change to folder because it is not local")); + + profile_end ("end - not local", (char *) path); + return FALSE; + } + + if (impl->update_current_folder_handle) + gtk_file_system_cancel_operation (impl->update_current_folder_handle); + + /* Test validity of path here. */ + data = g_new0 (struct UpdateCurrentFolderData, 1); + data->impl = impl; + data->path = gtk_file_path_copy (path); + data->keep_trail = keep_trail; + + impl->update_current_folder_handle = + gtk_file_system_get_info (impl->file_system, path, GTK_FILE_INFO_IS_FOLDER, + update_current_folder_get_info_cb, + data); + + set_busy_cursor (impl, TRUE); + profile_end ("end", NULL); - return result; + return TRUE; } static GtkFilePath * @@ -5621,7 +6120,8 @@ check_save_entry (GtkFileChooserDefault *impl, GtkFilePath **path_ret, gboolean *is_well_formed_ret, gboolean *is_empty_ret, - gboolean *is_file_part_empty_ret) + gboolean *is_file_part_empty_ret, + gboolean *is_folder) { GtkFileChooserEntry *chooser_entry; const GtkFilePath *current_folder; @@ -5640,6 +6140,7 @@ check_save_entry (GtkFileChooserDefault *impl, *is_well_formed_ret = TRUE; *is_empty_ret = TRUE; *is_file_part_empty_ret = TRUE; + *is_folder = FALSE; return; } @@ -5654,6 +6155,7 @@ check_save_entry (GtkFileChooserDefault *impl, *path_ret = gtk_file_path_copy (current_folder); *is_well_formed_ret = TRUE; *is_file_part_empty_ret = TRUE; + *is_folder = TRUE; return; } @@ -5668,12 +6170,14 @@ check_save_entry (GtkFileChooserDefault *impl, error_building_filename_dialog (impl, current_folder, file_part, error); *path_ret = NULL; *is_well_formed_ret = FALSE; + *is_folder = FALSE; return; } *path_ret = path; *is_well_formed_ret = TRUE; + *is_folder = _gtk_file_chooser_entry_get_is_folder (chooser_entry, path); } struct get_paths_closure { @@ -5719,9 +6223,9 @@ gtk_file_chooser_default_get_paths (GtkFileChooser *chooser) if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) { - gboolean is_well_formed, is_empty, is_file_part_empty; + gboolean is_well_formed, is_empty, is_file_part_empty, is_folder; - check_save_entry (impl, &info.path_from_entry, &is_well_formed, &is_empty, &is_file_part_empty); + check_save_entry (impl, &info.path_from_entry, &is_well_formed, &is_empty, &is_file_part_empty, &is_folder); if (!is_well_formed) return NULL; @@ -5872,19 +6376,53 @@ shortcuts_get_pos_for_shortcut_folder (GtkFileChooserDefault *impl, return pos + shortcuts_get_index (impl, SHORTCUTS_SHORTCUTS); } +struct AddShortcutData +{ + GtkFileChooserDefault *impl; + GtkFilePath *path; +}; + +static void +add_shortcut_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) +{ + int pos; + gboolean cancelled = handle->cancelled; + struct AddShortcutData *data = user_data; + + if (!g_slist_find (data->impl->loading_shortcuts, handle)) + goto out; + + data->impl->loading_shortcuts = g_slist_remove (data->impl->loading_shortcuts, handle); + + if (cancelled || error || !gtk_file_info_get_is_folder (info)) + goto out; + + pos = shortcuts_get_pos_for_shortcut_folder (data->impl, data->impl->num_shortcuts); + + shortcuts_insert_path (data->impl, pos, FALSE, NULL, data->path, NULL, FALSE, SHORTCUTS_SHORTCUTS); + +out: + g_object_unref (data->impl); + gtk_file_path_free (data->path); + g_free (data); + + g_object_unref (handle); +} + static gboolean gtk_file_chooser_default_add_shortcut_folder (GtkFileChooser *chooser, const GtkFilePath *path, GError **error) { + GtkFileSystemHandle *handle; GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - gboolean result; + struct AddShortcutData *data; + GSList *l; int pos; - /* Test validity of path here. */ - if (!check_is_folder (impl->file_system, path, error)) - return FALSE; - /* Avoid adding duplicates */ pos = shortcut_find_position (impl, path); if (pos >= 0 && pos < shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS_SEPARATOR)) @@ -5902,17 +6440,43 @@ gtk_file_chooser_default_add_shortcut_folder (GtkFileChooser *chooser, return FALSE; } - pos = shortcuts_get_pos_for_shortcut_folder (impl, impl->num_shortcuts); + for (l = impl->loading_shortcuts; l; l = l->next) + { + GtkFileSystemHandle *h = l->data; + GtkFilePath *p; - result = shortcuts_insert_path (impl, pos, FALSE, NULL, path, NULL, FALSE, error); + p = g_object_get_data (G_OBJECT (h), "add-shortcut-path-key"); + if (p && !gtk_file_path_compare (path, p)) + { + gchar *uri; - if (result) - impl->num_shortcuts++; + uri = gtk_file_system_path_to_uri (impl->file_system, path); + g_set_error (error, + GTK_FILE_CHOOSER_ERROR, + GTK_FILE_CHOOSER_ERROR_ALREADY_EXISTS, + _("shortcut %s already exists"), + uri); + g_free (uri); - if (impl->shortcuts_filter_model) - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model)); + return FALSE; + } + } - return result; + data = g_new0 (struct AddShortcutData, 1); + data->impl = g_object_ref (impl); + data->path = gtk_file_path_copy (path); + + handle = gtk_file_system_get_info (impl->file_system, path, + GTK_FILE_INFO_IS_FOLDER, + add_shortcut_get_info_cb, data); + + if (!handle) + return FALSE; + + impl->loading_shortcuts = g_slist_append (impl->loading_shortcuts, handle); + g_object_set_data (G_OBJECT (handle), "add-shortcut-path-key", data->path); + + return TRUE; } static gboolean @@ -5923,9 +6487,24 @@ gtk_file_chooser_default_remove_shortcut_folder (GtkFileChooser *chooser, GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); int pos; GtkTreeIter iter; + GSList *l; char *uri; int i; + for (l = impl->loading_shortcuts; l; l = l->next) + { + GtkFileSystemHandle *h = l->data; + GtkFilePath *p; + + p = g_object_get_data (G_OBJECT (h), "add-shortcut-path-key"); + if (p && !gtk_file_path_compare (path, p)) + { + impl->loading_shortcuts = g_slist_remove (impl->loading_shortcuts, h); + gtk_file_system_cancel_operation (h); + return TRUE; + } + } + if (impl->num_shortcuts == 0) goto out; @@ -6240,48 +6819,46 @@ confirm_dialog_should_accept_filename (GtkFileChooserDefault *impl, return (response == GTK_RESPONSE_ACCEPT); } -static char * -get_display_name_for_folder (GtkFileChooserDefault *impl, - const GtkFilePath *path) +struct GetDisplayNameData { - char *display_name; - GtkFilePath *parent_path; - GtkFileFolder *parent_folder; - GtkFileInfo *info; + GtkFileChooserDefault *impl; + gchar *file_part; +}; - display_name = NULL; - parent_path = NULL; - parent_folder = NULL; - info = NULL; +static void +confirmation_confirm_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = handle->cancelled; + gboolean should_respond = FALSE; + struct GetDisplayNameData *data = user_data; - if (!gtk_file_system_get_parent (impl->file_system, path, &parent_path, NULL)) + if (handle != data->impl->should_respond_get_info_handle) goto out; - parent_folder = gtk_file_system_get_folder (impl->file_system, - parent_path ? parent_path : path, - GTK_FILE_INFO_DISPLAY_NAME, - NULL); - if (!parent_folder) - goto out; + data->impl->should_respond_get_info_handle = NULL; - info = gtk_file_folder_get_info (parent_folder, parent_path ? path : NULL, NULL); - if (!info) + if (cancelled) goto out; - display_name = g_strdup (gtk_file_info_get_display_name (info)); - - out: - - if (parent_path) - gtk_file_path_free (parent_path); + if (error) + /* Huh? Did the folder disappear? Let the caller deal with it */ + should_respond = TRUE; + else + should_respond = confirm_dialog_should_accept_filename (data->impl, data->file_part, gtk_file_info_get_display_name (info)); - if (parent_folder) - g_object_unref (parent_folder); + set_busy_cursor (data->impl, FALSE); + if (should_respond) + g_signal_emit_by_name (data->impl, "response-requested"); - if (info) - gtk_file_info_free (info); +out: + g_object_unref (data->impl); + g_free (data->file_part); + g_free (data); - return display_name; + g_object_unref (handle); } /* Does overwrite confirmation if appropriate, and returns whether the dialog @@ -6305,18 +6882,24 @@ should_respond_after_confirm_overwrite (GtkFileChooserDefault *impl, { case GTK_FILE_CHOOSER_CONFIRMATION_CONFIRM: { - char *parent_display_name; - gboolean retval; + struct GetDisplayNameData *data; g_assert (file_part != NULL); - parent_display_name = get_display_name_for_folder (impl, parent_path); - if (!parent_display_name) - return TRUE; /* Huh? Did the folder disappear? Let the caller deal with it */ + data = g_new0 (struct GetDisplayNameData, 1); + data->impl = g_object_ref (impl); + data->file_part = g_strdup (file_part); + + if (impl->should_respond_get_info_handle) + gtk_file_system_cancel_operation (impl->should_respond_get_info_handle); - retval = confirm_dialog_should_accept_filename (impl, file_part, parent_display_name); - g_free (parent_display_name); - return retval; + impl->should_respond_get_info_handle = + gtk_file_system_get_info (impl->file_system, parent_path, + GTK_FILE_INFO_DISPLAY_NAME, + confirmation_confirm_get_info_cb, + data); + set_busy_cursor (data->impl, TRUE); + return FALSE; } case GTK_FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME: @@ -6331,6 +6914,114 @@ should_respond_after_confirm_overwrite (GtkFileChooserDefault *impl, } } +static void +action_create_folder_cb (GtkFileSystemHandle *handle, + const GtkFilePath *path, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = handle->cancelled; + GtkFileChooserDefault *impl = user_data; + + if (!g_slist_find (impl->pending_handles, handle)) + goto out; + + impl->pending_handles = g_slist_remove (impl->pending_handles, handle); + + set_busy_cursor (impl, FALSE); + + if (cancelled) + goto out; + + if (error) + error_creating_folder_dialog (impl, path, g_error_copy (error)); + else + g_signal_emit_by_name (impl, "response-requested"); + +out: + g_object_unref (impl); + g_object_unref (handle); +} + +struct SaveEntryData +{ + GtkFileChooserDefault *impl; + gboolean file_exists_and_is_not_folder; + GtkFilePath *parent_path; + GtkFilePath *path; +}; + +static void +save_entry_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean parent_is_folder; + gboolean cancelled = handle->cancelled; + struct SaveEntryData *data = user_data; + + if (handle != data->impl->should_respond_get_info_handle) + goto out; + + data->impl->should_respond_get_info_handle = NULL; + + set_busy_cursor (data->impl, FALSE); + + if (cancelled) + goto out; + + if (!info) + parent_is_folder = FALSE; + else + parent_is_folder = gtk_file_info_get_is_folder (info); + + if (parent_is_folder) + { + if (data->impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + if (data->file_exists_and_is_not_folder) + { + gboolean retval; + const char *file_part; + + file_part = _gtk_file_chooser_entry_get_file_part (GTK_FILE_CHOOSER_ENTRY (data->impl->save_file_name_entry)); + retval = should_respond_after_confirm_overwrite (data->impl, file_part, data->parent_path); + + if (retval) + g_signal_emit_by_name (data->impl, "response-requested"); + } + else + g_signal_emit_by_name (data->impl, "response-requested"); + } + else /* GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER */ + { + GtkFileSystemHandle *handle; + + g_object_ref (data->impl); + handle = gtk_file_system_create_folder (data->impl->file_system, + data->path, + action_create_folder_cb, + data->impl); + data->impl->pending_handles = g_slist_append (data->impl->pending_handles, handle); + set_busy_cursor (data->impl, TRUE); + } + } + else + { + /* This will display an error, which is what we want */ + change_folder_and_display_error (data->impl, data->parent_path); + } + +out: + g_object_unref (data->impl); + gtk_file_path_free (data->path); + gtk_file_path_free (data->parent_path); + g_free (data); + + g_object_unref (handle); +} + /* Implementation for GtkFileChooserEmbed::should_respond() */ static gboolean gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) @@ -6437,7 +7128,7 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER); entry = GTK_FILE_CHOOSER_ENTRY (impl->save_file_name_entry); - check_save_entry (impl, &path, &is_well_formed, &is_empty, &is_file_part_empty); + check_save_entry (impl, &path, &is_well_formed, &is_empty, &is_file_part_empty, &is_folder); if (is_empty || !is_well_formed) return FALSE; @@ -6445,7 +7136,6 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) g_assert (path != NULL); error = NULL; - is_folder = check_is_folder (impl->file_system, path, &error); if (is_folder) { if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) @@ -6478,48 +7168,29 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) else { GtkFilePath *parent_path; - gboolean parent_is_folder; + struct SaveEntryData *data; /* check that everything up to the last component exists */ parent_path = gtk_file_path_copy (_gtk_file_chooser_entry_get_current_folder (entry)); - parent_is_folder = check_is_folder (impl->file_system, parent_path, NULL); - if (parent_is_folder) - { - if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) - { - if (file_exists_and_is_not_folder) - { - const char *file_part; - - file_part = _gtk_file_chooser_entry_get_file_part (GTK_FILE_CHOOSER_ENTRY (impl->save_file_name_entry)); - retval = should_respond_after_confirm_overwrite (impl, file_part, parent_path); - } - else - retval = TRUE; - } - else /* GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER */ - { - GError *create_error; - - create_error = NULL; - if (gtk_file_system_create_folder (impl->file_system, path, &create_error)) - retval = TRUE; - else - { - error_creating_folder_dialog (impl, path, create_error); - retval = FALSE; - } - } - } - else - { - /* This will display an error, which is what we want */ - change_folder_and_display_error (impl, parent_path); - retval = FALSE; - } - gtk_file_path_free (parent_path); + data = g_new0 (struct SaveEntryData, 1); + data->impl = g_object_ref (impl); + data->file_exists_and_is_not_folder = file_exists_and_is_not_folder; + data->parent_path = parent_path; /* Takes ownership */ + data->path = gtk_file_path_copy (path); + + if (impl->should_respond_get_info_handle) + gtk_file_system_cancel_operation (impl->should_respond_get_info_handle); + + impl->should_respond_get_info_handle = + gtk_file_system_get_info (impl->file_system, parent_path, + GTK_FILE_INFO_IS_FOLDER, + save_entry_get_info_cb, + data); + set_busy_cursor (impl, TRUE); + + retval = FALSE; } if (error != NULL) @@ -6691,6 +7362,51 @@ check_preview_change (GtkFileChooserDefault *impl) } } +static void +shortcuts_activate_volume_mount_cb (GtkFileSystemHandle *handle, + GtkFileSystemVolume *volume, + const GError *error, + gpointer data) +{ + GtkFilePath *path; + gboolean cancelled = handle->cancelled; + GtkFileChooserDefault *impl = data; + + if (handle != impl->shortcuts_activate_iter_handle) + goto out; + + impl->shortcuts_activate_iter_handle = NULL; + + set_busy_cursor (impl, FALSE); + + if (cancelled) + goto out; + + if (error) + { + char *msg; + + msg = g_strdup_printf (_("Could not mount %s"), + gtk_file_system_volume_get_display_name (impl->file_system, volume)); + error_message (impl, msg, error->message); + g_free (msg); + + goto out; + } + + path = gtk_file_system_volume_get_base_path (impl->file_system, volume); + if (path != NULL) + { + change_folder_and_display_error (impl, path); + gtk_file_path_free (path); + } + +out: + g_object_unref (impl); + g_object_unref (handle); +} + + /* Activates a volume by mounting it if necessary and then switching to its * base path. */ @@ -6707,44 +7423,63 @@ shortcuts_activate_volume (GtkFileChooserDefault *impl, if (!gtk_file_system_volume_get_is_mounted (impl->file_system, volume)) { - GError *error; - gboolean result; - set_busy_cursor (impl, TRUE); - error = NULL; - result = gtk_file_system_volume_mount (impl->file_system, volume, &error); + impl->shortcuts_activate_iter_handle = + gtk_file_system_volume_mount (impl->file_system, volume, + shortcuts_activate_volume_mount_cb, + g_object_ref (impl)); + } + else + { + path = gtk_file_system_volume_get_base_path (impl->file_system, volume); + if (path != NULL) + { + change_folder_and_display_error (impl, path); + gtk_file_path_free (path); + } + } - if (!result) - { - char *msg; + g_object_unref (impl); +} - msg = g_strdup_printf (_("Could not mount %s"), - gtk_file_system_volume_get_display_name (impl->file_system, volume)); - error_message (impl, msg, error->message); - g_free (msg); - g_error_free (error); - } +/* Opens the folder or volume at the specified iter in the shortcuts model */ +struct ShortcutsActivateData +{ + GtkFileChooserDefault *impl; + GtkFilePath *path; +}; - set_busy_cursor (impl, FALSE); +static void +shortcuts_activate_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = handle->cancelled; + struct ShortcutsActivateData *data = user_data; - if (!result) - goto out; - } + if (handle != data->impl->shortcuts_activate_iter_handle) + goto out; - path = gtk_file_system_volume_get_base_path (impl->file_system, volume); - if (path != NULL) - { - change_folder_and_display_error (impl, path); - gtk_file_path_free (path); - } + data->impl->shortcuts_activate_iter_handle = NULL; - out: + if (cancelled) + goto out; - g_object_unref (impl); + if (!error && gtk_file_info_get_is_folder (info)) + change_folder_and_display_error (data->impl, data->path); + else + gtk_file_chooser_default_select_path (GTK_FILE_CHOOSER (data->impl), data->path, NULL); + +out: + g_object_unref (data->impl); + gtk_file_path_free (data->path); + g_free (data); + + g_object_unref (handle); } -/* Opens the folder or volume at the specified iter in the shortcuts model */ static void shortcuts_activate_iter (GtkFileChooserDefault *impl, GtkTreeIter *iter) @@ -6760,6 +7495,12 @@ shortcuts_activate_iter (GtkFileChooserDefault *impl, if (!col_data) return; /* We are on a separator */ + if (impl->shortcuts_activate_iter_handle) + { + gtk_file_system_cancel_operation (impl->shortcuts_activate_iter_handle); + impl->shortcuts_activate_iter_handle = NULL; + } + if (is_volume) { GtkFileSystemVolume *volume; @@ -6770,13 +7511,16 @@ shortcuts_activate_iter (GtkFileChooserDefault *impl, } else { - const GtkFilePath *file_path; + struct ShortcutsActivateData *data; - file_path = col_data; - if (check_is_folder (impl->file_system, file_path, NULL)) - change_folder_and_display_error (impl, file_path); - else - gtk_file_chooser_default_select_path (GTK_FILE_CHOOSER (impl), file_path, NULL); + data = g_new0 (struct ShortcutsActivateData, 1); + data->impl = g_object_ref (impl); + data->path = gtk_file_path_copy (col_data); + + impl->shortcuts_activate_iter_handle = + gtk_file_system_get_info (impl->file_system, data->path, + GTK_FILE_INFO_IS_FOLDER, + shortcuts_activate_get_info_cb, data); } } @@ -6989,9 +7733,14 @@ list_icon_data_func (GtkTreeViewColumn *tree_column, if (path) { - /* FIXME: NULL GError */ - pixbuf = gtk_file_system_render_icon (impl->file_system, path, GTK_WIDGET (impl), - impl->icon_size, NULL); + pixbuf = NULL; + + if (info) + { + /* FIXME: NULL GError */ + pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (impl), + impl->icon_size, NULL); + } } else { @@ -7199,6 +7948,75 @@ location_entry_create (GtkFileChooserDefault *impl, return GTK_WIDGET (entry); } +struct UpdateFromEntryData +{ + GtkFileChooserDefault *impl; + GtkFilePath *subfolder_path; + GtkFilePath *folder_path; + char *file_part; +}; + +static void +update_from_entry_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *file_info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = handle->cancelled; + struct UpdateFromEntryData *data = user_data; + + if (handle != data->impl->update_from_entry_handle) + goto out; + + data->impl->update_from_entry_handle = NULL; + + if (cancelled) + goto out; + + if (!file_info) + { + if (data->impl->action == GTK_FILE_CHOOSER_ACTION_SAVE + || data->impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + { + if (!change_folder_and_display_error (data->impl, data->folder_path)) + goto out; + + gtk_file_chooser_default_set_current_name (GTK_FILE_CHOOSER (data->impl), data->file_part); + } + else + { + GError *err = g_error_copy (error); + + error_getting_info_dialog (data->impl, data->subfolder_path, err); + } + + goto out; + } + + if (gtk_file_info_get_is_folder (file_info)) + change_folder_and_display_error (data->impl, data->subfolder_path); + else + { + gboolean result; + GError *select_error = NULL; + + result = _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (data->impl), data->subfolder_path, &select_error); + + if (!result) + error_dialog (data->impl, _("Could not select item"), + data->subfolder_path, select_error); + } + +out: + g_object_unref (data->impl); + gtk_file_path_free (data->subfolder_path); + gtk_file_path_free (data->folder_path); + g_free (data->file_part); + g_free (data); + + g_object_unref (handle); +} + static gboolean update_from_entry (GtkFileChooserDefault *impl, GtkWindow *parent, @@ -7222,29 +8040,16 @@ update_from_entry (GtkFileChooserDefault *impl, return change_folder_and_display_error (impl, folder_path); else { - GtkFileFolder *folder = NULL; GtkFilePath *subfolder_path = NULL; - GtkFileInfo *info = NULL; - GError *error; - gboolean result; - - result = FALSE; + GError *error = NULL; + gboolean result = FALSE; + struct UpdateFromEntryData *data; /* If the file part is non-empty, we need to figure out if it refers to a * folder within folder. We could optimize the case here where the folder * is already loaded for one of our tree models. */ - error = NULL; - folder = gtk_file_system_get_folder (impl->file_system, folder_path, GTK_FILE_INFO_IS_FOLDER, &error); - - if (!folder) - { - error_getting_info_dialog (impl, folder_path, error); - goto out; - } - - error = NULL; subfolder_path = gtk_file_system_make_path (impl->file_system, folder_path, file_part, &error); if (!subfolder_path) @@ -7256,54 +8061,29 @@ update_from_entry (GtkFileChooserDefault *impl, msg = g_strdup_printf (_("Could not build file name from '%s' and '%s'"), uri, file_part); error_message (impl, msg, error->message); + g_free (uri); g_free (msg); - goto out; - } + gtk_file_path_free (subfolder_path); - error = NULL; - info = gtk_file_folder_get_info (folder, subfolder_path, &error); - - if (!info) - { - if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE - || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - { - if (!change_folder_and_display_error (impl, folder_path)) - goto out; - - gtk_file_chooser_default_set_current_name (GTK_FILE_CHOOSER (impl), file_part); - } - else - error_getting_info_dialog (impl, subfolder_path, error); - - goto out; - } - - if (gtk_file_info_get_is_folder (info)) - result = change_folder_and_display_error (impl, subfolder_path); - else - { - GError *error; - - error = NULL; - result = _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (impl), subfolder_path, &error); - if (!result) - error_dialog (impl, _("Could not select item"), - subfolder_path, error); + return result; } - out: + data = g_new0 (struct UpdateFromEntryData, 1); + data->impl = g_object_ref (impl); + data->folder_path = gtk_file_path_copy (folder_path); + data->subfolder_path = subfolder_path; + data->file_part = g_strdup (file_part); - if (folder) - g_object_unref (folder); + if (impl->update_from_entry_handle) + gtk_file_system_cancel_operation (impl->update_from_entry_handle); - gtk_file_path_free (subfolder_path); + impl->update_from_entry_handle = + gtk_file_system_get_info (impl->file_system, subfolder_path, + GTK_FILE_INFO_IS_FOLDER, + update_from_entry_get_info_cb, data); - if (info) - gtk_file_info_free (info); - - return result; + return TRUE; } g_assert_not_reached (); diff --git a/gtk/gtkfilechooserdialog.c b/gtk/gtkfilechooserdialog.c index 32a9fa9ce..9db98970a 100644 --- a/gtk/gtkfilechooserdialog.c +++ b/gtk/gtkfilechooserdialog.c @@ -129,6 +129,7 @@ gtk_file_chooser_dialog_init (GtkFileChooserDialog *dialog) dialog->priv->default_height = -1; dialog->priv->resize_horizontally = TRUE; dialog->priv->resize_vertically = TRUE; + dialog->priv->response_requested = FALSE; gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); @@ -355,6 +356,39 @@ file_chooser_widget_default_size_changed (GtkWidget *widget, else file_chooser_widget_default_unrealized_size_changed (widget, dialog); } + +static void +file_chooser_widget_response_requested (GtkWidget *widget, + GtkFileChooserDialog *dialog) +{ + GList *children, *l; + + /* There probably isn't a default widget, so make things easier for the + * programmer by looking for a reasonable button on our own. + */ + + children = gtk_container_get_children (GTK_CONTAINER (GTK_DIALOG (dialog)->action_area)); + + for (l = children; l; l = l->next) + { + GtkWidget *widget; + int response_id; + + widget = GTK_WIDGET (l->data); + response_id = gtk_dialog_get_response_for_widget (GTK_DIALOG (dialog), widget); + if (response_id == GTK_RESPONSE_ACCEPT + || response_id == GTK_RESPONSE_OK + || response_id == GTK_RESPONSE_YES + || response_id == GTK_RESPONSE_APPLY) + { + dialog->priv->response_requested = TRUE; + gtk_widget_activate (widget); /* Should we gtk_dialog_response (dialog, response_id) instead? */ + break; + } + } + + g_list_free (children); +} static GObject* gtk_file_chooser_dialog_constructor (GType type, @@ -382,6 +416,8 @@ gtk_file_chooser_dialog_constructor (GType type, G_CALLBACK (file_chooser_widget_file_activated), object); g_signal_connect (priv->widget, "default-size-changed", G_CALLBACK (file_chooser_widget_default_size_changed), object); + g_signal_connect (priv->widget, "response-requested", + G_CALLBACK (file_chooser_widget_response_requested), object); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (object)->vbox), priv->widget, TRUE, TRUE, 0); @@ -550,8 +586,11 @@ response_cb (GtkDialog *dialog, || response_id == GTK_RESPONSE_APPLY)) return; - if (!_gtk_file_chooser_embed_should_respond (GTK_FILE_CHOOSER_EMBED (priv->widget))) - g_signal_stop_emission_by_name (dialog, "response"); + if (!priv->response_requested && !_gtk_file_chooser_embed_should_respond (GTK_FILE_CHOOSER_EMBED (priv->widget))) + { + g_signal_stop_emission_by_name (dialog, "response"); + priv->response_requested = FALSE; + } } static GtkWidget * diff --git a/gtk/gtkfilechooserembed.c b/gtk/gtkfilechooserembed.c index 1f584a3d8..8871fb7ad 100644 --- a/gtk/gtkfilechooserembed.c +++ b/gtk/gtkfilechooserembed.c @@ -35,6 +35,8 @@ static gboolean delegate_should_respond (GtkFileChooserEmbed *chooser_embe static void delegate_initial_focus (GtkFileChooserEmbed *chooser_embed); static void delegate_default_size_changed (GtkFileChooserEmbed *chooser_embed, gpointer data); +static void delegate_response_requested (GtkFileChooserEmbed *chooser_embed, + gpointer data); static GtkFileChooserEmbed * get_delegate (GtkFileChooserEmbed *receiver) @@ -81,6 +83,8 @@ _gtk_file_chooser_embed_set_delegate (GtkFileChooserEmbed *receiver, g_signal_connect (delegate, "default_size_changed", G_CALLBACK (delegate_default_size_changed), receiver); + g_signal_connect (delegate, "response_requested", + G_CALLBACK (delegate_response_requested), receiver); } @@ -120,6 +124,13 @@ delegate_default_size_changed (GtkFileChooserEmbed *chooser_embed, g_signal_emit_by_name (data, "default-size-changed"); } +static void +delegate_response_requested (GtkFileChooserEmbed *chooser_embed, + gpointer data) +{ + g_signal_emit_by_name (data, "response-requested"); +} + /* publicly callable functions */ @@ -160,6 +171,13 @@ gtk_file_chooser_embed_class_init (gpointer g_iface) NULL, NULL, _gtk_marshal_VOID__VOID, G_TYPE_NONE, 0); + g_signal_new (_("response-requested"), + iface_type, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkFileChooserEmbedIface, response_requested), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); } void diff --git a/gtk/gtkfilechooserembed.h b/gtk/gtkfilechooserembed.h index 98b0ae54e..9477da068 100644 --- a/gtk/gtkfilechooserembed.h +++ b/gtk/gtkfilechooserembed.h @@ -53,6 +53,7 @@ struct _GtkFileChooserEmbedIface /* Signals */ void (*default_size_changed) (GtkFileChooserEmbed *chooser_embed); + void (*response_requested) (GtkFileChooserEmbed *chooser_embed); }; GType _gtk_file_chooser_embed_get_type (void) G_GNUC_CONST; diff --git a/gtk/gtkfilechooserentry.c b/gtk/gtkfilechooserentry.c index acbd1206c..cbae54c12 100644 --- a/gtk/gtkfilechooserentry.c +++ b/gtk/gtkfilechooserentry.c @@ -55,6 +55,7 @@ struct _GtkFileChooserEntry GSource *load_directory_idle; GtkFileFolder *current_folder; + GtkFileSystemHandle *load_folder_handle; GtkListStore *completion_store; @@ -75,6 +76,7 @@ static void gtk_file_chooser_entry_iface_init (GtkEditableClass *iface); static void gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry); static void gtk_file_chooser_entry_finalize (GObject *object); +static void gtk_file_chooser_entry_dispose (GObject *object); static gboolean gtk_file_chooser_entry_focus (GtkWidget *widget, GtkDirectionType direction); static void gtk_file_chooser_entry_activate (GtkEntry *entry); @@ -156,6 +158,7 @@ gtk_file_chooser_entry_class_init (GtkFileChooserEntryClass *class) parent_class = g_type_class_peek_parent (class); gobject_class->finalize = gtk_file_chooser_entry_finalize; + gobject_class->dispose = gtk_file_chooser_entry_dispose; widget_class->focus = gtk_file_chooser_entry_focus; @@ -211,8 +214,29 @@ gtk_file_chooser_entry_finalize (GObject *object) { GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object); + gtk_file_path_free (chooser_entry->base_folder); + gtk_file_path_free (chooser_entry->current_folder_path); + g_free (chooser_entry->file_part); + + parent_class->finalize (object); +} + +static void +gtk_file_chooser_entry_dispose (GObject *object) +{ + GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object); + if (chooser_entry->completion_store) - g_object_unref (chooser_entry->completion_store); + { + g_object_unref (chooser_entry->completion_store); + chooser_entry->completion_store = NULL; + } + + if (chooser_entry->load_folder_handle) + { + gtk_file_system_cancel_operation (chooser_entry->load_folder_handle); + chooser_entry->load_folder_handle = NULL; + } if (chooser_entry->current_folder) { @@ -221,16 +245,16 @@ gtk_file_chooser_entry_finalize (GObject *object) g_signal_handlers_disconnect_by_func (chooser_entry->current_folder, G_CALLBACK (files_deleted_cb), chooser_entry); g_object_unref (chooser_entry->current_folder); + chooser_entry->current_folder = NULL; } if (chooser_entry->file_system) - g_object_unref (chooser_entry->file_system); - - gtk_file_path_free (chooser_entry->base_folder); - gtk_file_path_free (chooser_entry->current_folder_path); - g_free (chooser_entry->file_part); + { + g_object_unref (chooser_entry->file_system); + chooser_entry->file_system = NULL; + } - parent_class->finalize (object); + parent_class->dispose (object); } /* Match functions for the GtkEntryCompletion */ @@ -601,6 +625,41 @@ files_deleted_cb (GtkFileSystem *file_system, /* FIXME: gravy... */ } +static void +load_directory_get_folder_callback (GtkFileSystemHandle *handle, + GtkFileFolder *folder, + const GError *error, + gpointer data) +{ + gboolean cancelled = handle->cancelled; + GtkFileChooserEntry *chooser_entry = data; + + if (handle != chooser_entry->load_folder_handle) + goto out; + + chooser_entry->load_folder_handle = NULL; + + if (cancelled || error) + goto out; + + chooser_entry->current_folder = folder; + g_signal_connect (chooser_entry->current_folder, "files-added", + G_CALLBACK (files_added_cb), chooser_entry); + g_signal_connect (chooser_entry->current_folder, "files-removed", + G_CALLBACK (files_deleted_cb), chooser_entry); + + chooser_entry->completion_store = gtk_list_store_new (N_COLUMNS, + G_TYPE_STRING, + GTK_TYPE_FILE_PATH); + + gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), + GTK_TREE_MODEL (chooser_entry->completion_store)); + +out: + g_object_unref (chooser_entry); + g_object_unref (handle); +} + static gboolean load_directory_callback (GtkFileChooserEntry *chooser_entry) { @@ -623,38 +682,15 @@ load_directory_callback (GtkFileChooserEntry *chooser_entry) g_assert (chooser_entry->completion_store == NULL); /* Load the folder */ - chooser_entry->current_folder = gtk_file_system_get_folder (chooser_entry->file_system, - chooser_entry->current_folder_path, - GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_FOLDER, - NULL); /* NULL-GError */ - - /* There is no folder by that name */ - if (!chooser_entry->current_folder) - goto done; - g_signal_connect (chooser_entry->current_folder, "files-added", - G_CALLBACK (files_added_cb), chooser_entry); - g_signal_connect (chooser_entry->current_folder, "files-removed", - G_CALLBACK (files_deleted_cb), chooser_entry); - - chooser_entry->completion_store = gtk_list_store_new (N_COLUMNS, - G_TYPE_STRING, - GTK_TYPE_FILE_PATH); + if (chooser_entry->load_folder_handle) + gtk_file_system_cancel_operation (chooser_entry->load_folder_handle); - if (chooser_entry->file_part_pos != -1) - { - gtk_file_folder_list_children (chooser_entry->current_folder, - &child_paths, - NULL); /* NULL-GError */ - if (child_paths) - { - update_current_folder_files (chooser_entry, child_paths); - add_completion_idle (chooser_entry); - gtk_file_paths_free (child_paths); - } - } - - gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), - GTK_TREE_MODEL (chooser_entry->completion_store)); + chooser_entry->load_folder_handle = + gtk_file_system_get_folder (chooser_entry->file_system, + chooser_entry->current_folder_path, + GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_FOLDER, + load_directory_get_folder_callback, + g_object_ref (chooser_entry)); done: @@ -1041,5 +1077,27 @@ _gtk_file_chooser_entry_get_action (GtkFileChooserEntry *chooser_entry) return chooser_entry->action; } +gboolean +_gtk_file_chooser_entry_get_is_folder (GtkFileChooserEntry *chooser_entry, + const GtkFilePath *path) +{ + gboolean retval = FALSE; + + if (chooser_entry->current_folder) + { + GtkFileInfo *file_info; + + file_info = gtk_file_folder_get_info (chooser_entry->current_folder, + path, NULL); + if (file_info) + { + retval = gtk_file_info_get_is_folder (file_info); + gtk_file_info_free (file_info); + } + } + + return retval; +} + #define __GTK_FILE_CHOOSER_ENTRY_C__ #include "gtkaliasdef.c" diff --git a/gtk/gtkfilechooserentry.h b/gtk/gtkfilechooserentry.h index 04c70dd4d..19d06b87c 100644 --- a/gtk/gtkfilechooserentry.h +++ b/gtk/gtkfilechooserentry.h @@ -46,6 +46,8 @@ void _gtk_file_chooser_entry_set_file_part (GtkFileChooserEnt const gchar *file_part); const GtkFilePath *_gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry); const gchar * _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry); +gboolean _gtk_file_chooser_entry_get_is_folder (GtkFileChooserEntry *chooser_entry, + const GtkFilePath *path); G_END_DECLS diff --git a/gtk/gtkfilechooserprivate.h b/gtk/gtkfilechooserprivate.h index e9d45aae8..34ad17b40 100644 --- a/gtk/gtkfilechooserprivate.h +++ b/gtk/gtkfilechooserprivate.h @@ -113,6 +113,7 @@ struct _GtkFileChooserDialogPrivate gint default_height; gboolean resize_horizontally; gboolean resize_vertically; + gboolean response_requested; }; @@ -187,6 +188,17 @@ struct _GtkFileChooserDefault GtkTreeModelSort *sort_model; + /* Handles */ + GSList *loading_shortcuts; + GSList *reload_icon_handles; + GtkFileSystemHandle *file_list_drag_data_received_handle; + GtkFileSystemHandle *update_current_folder_handle; + GtkFileSystemHandle *show_and_select_paths_handle; + GtkFileSystemHandle *should_respond_get_info_handle; + GtkFileSystemHandle *update_from_entry_handle; + GtkFileSystemHandle *shortcuts_activate_iter_handle; + GSList *pending_handles; + LoadState load_state; ReloadState reload_state; guint load_timeout_id; @@ -267,9 +279,10 @@ struct _GtkFileSystemModel GSList *idle_clears; GSource *idle_clear_source; - GSource *idle_finished_loading_source; gushort max_depth; + + GSList *pending_handles; guint show_hidden : 1; guint show_folders : 1; @@ -300,6 +313,7 @@ struct _FileModelNode guint is_visible : 1; guint loaded : 1; guint idle_clear : 1; + guint load_pending : 1; }; diff --git a/gtk/gtkfilesystem.c b/gtk/gtkfilesystem.c index 4ccc1cac1..d1af8fe93 100644 --- a/gtk/gtkfilesystem.c +++ b/gtk/gtkfilesystem.c @@ -25,6 +25,7 @@ #include "gtkmodules.h" #include "gtkintl.h" #include "gtkalias.h" +#include "gtkstock.h" #include @@ -35,6 +36,7 @@ struct _GtkFileInfo gchar *display_name; gchar *display_key; gchar *mime_type; + gchar *icon_name; guint is_folder : 1; guint is_hidden : 1; }; @@ -88,6 +90,10 @@ gtk_file_info_copy (GtkFileInfo *info) new_info->display_key = g_strdup (new_info->display_key); if (new_info->mime_type) new_info->mime_type = g_strdup (new_info->mime_type); + if (new_info->icon_name) + new_info->icon_name = g_strdup (new_info->icon_name); + if (new_info->display_key) + new_info->display_key = g_strdup (new_info->display_key); return new_info; } @@ -103,6 +109,8 @@ gtk_file_info_free (GtkFileInfo *info) g_free (info->mime_type); if (info->display_key) g_free (info->display_key); + if (info->icon_name) + g_free (info->icon_name); g_free (info); } @@ -250,6 +258,171 @@ gtk_file_info_set_size (GtkFileInfo *info, info->size = size; } +void +gtk_file_info_set_icon_name (GtkFileInfo *info, + const gchar *icon_name) +{ + g_return_if_fail (info != NULL); + + if (info->icon_name) + g_free (info->icon_name); + + info->icon_name = g_strdup (icon_name); +} + +G_CONST_RETURN gchar * +gtk_file_info_get_icon_name (const GtkFileInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + + return info->icon_name; +} + +GdkPixbuf * +gtk_file_info_render_icon (const GtkFileInfo *info, + GtkWidget *widget, + gint pixel_size, + GError **error) +{ + GdkPixbuf *pixbuf = NULL; + + g_return_val_if_fail (info != NULL, NULL); + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + + if (info->icon_name) + { + if (g_path_is_absolute (info->icon_name)) + pixbuf = gdk_pixbuf_new_from_file_at_size (info->icon_name, + pixel_size, + pixel_size, + NULL); + else + { + GtkIconTheme *icon_theme; + + icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); + pixbuf = gtk_icon_theme_load_icon (icon_theme, info->icon_name, + pixel_size, 0, NULL); + } + } + + if (!pixbuf) + { + /* load a fallback icon */ + pixbuf = gtk_widget_render_icon (widget, GTK_STOCK_FILE, GTK_ICON_SIZE_SMALL_TOOLBAR, NULL); + if (!pixbuf && error) + g_set_error (error, + GTK_FILE_SYSTEM_ERROR, + GTK_FILE_SYSTEM_ERROR_FAILED, + _("Could not get a stock icon for %s\n"), + info->icon_name); + } + + return pixbuf; +} + +/***************************************** + * GtkFileSystemHandle * + *****************************************/ + +static void gtk_file_system_handle_init (GtkFileSystemHandle *handle); +static void gtk_file_system_handle_class_init (GtkFileSystemHandleClass *klass); + +enum +{ + PROP_0, + PROP_CANCELLED +}; + +GType +gtk_file_system_handle_get_type (void) +{ + static GType file_system_handle_type = 0; + + if (!file_system_handle_type) + { + static const GTypeInfo file_system_handle_info = + { + sizeof (GtkFileSystemHandleClass), + NULL, + NULL, + (GClassInitFunc) gtk_file_system_handle_class_init, + NULL, + NULL, + sizeof (GtkFileSystemHandle), + 0, + (GInstanceInitFunc) gtk_file_system_handle_init, + }; + + file_system_handle_type = g_type_register_static (G_TYPE_OBJECT, + I_("GtkFileSystemHandle"), + &file_system_handle_info, 0); + } + + return file_system_handle_type; +} + +#if 0 +GtkFileSystemHandle * +gtk_file_system_handle_new (void) +{ + return g_object_new (GTK_TYPE_FILE_SYSTEM_HANDLE, NULL); +} +#endif + +static void +gtk_file_system_handle_init (GtkFileSystemHandle *handle) +{ + handle->file_system = NULL; + handle->cancelled = FALSE; +} + +static void +gtk_file_system_handle_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +gtk_file_system_handle_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkFileSystemHandle *handle = GTK_FILE_SYSTEM_HANDLE (object); + + switch (prop_id) + { + case PROP_CANCELLED: + g_value_set_boolean (value, handle->cancelled); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_file_system_handle_class_init (GtkFileSystemHandleClass *klass) +{ + GObjectClass *o_class; + + o_class = (GObjectClass *)klass; + o_class->set_property = gtk_file_system_handle_set_property; + o_class->get_property = gtk_file_system_handle_get_property; + + g_object_class_install_property (o_class, + PROP_CANCELLED, + g_param_spec_boolean ("cancelled", + P_("Cancelled"), + P_("Whether or not the operation has been successfully cancelled"), + FALSE, + G_PARAM_READABLE)); +} /***************************************** * GtkFileSystem * @@ -314,29 +487,53 @@ gtk_file_system_list_volumes (GtkFileSystem *file_system) return GTK_FILE_SYSTEM_GET_IFACE (file_system)->list_volumes (file_system); } -GtkFileFolder * -gtk_file_system_get_folder (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkFileInfoType types, - GError **error) +GtkFileSystemHandle * +gtk_file_system_get_folder (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetFolderCallback callback, + gpointer data) { g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL); g_return_val_if_fail (path != NULL, NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); + g_return_val_if_fail (callback != NULL, NULL); - return GTK_FILE_SYSTEM_GET_IFACE (file_system)->get_folder (file_system, path, types, error); + return GTK_FILE_SYSTEM_GET_IFACE (file_system)->get_folder (file_system, path, types, callback, data); } -gboolean -gtk_file_system_create_folder(GtkFileSystem *file_system, - const GtkFilePath *path, - GError **error) +GtkFileSystemHandle * +gtk_file_system_get_info (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetInfoCallback callback, + gpointer data) { - g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), FALSE); - g_return_val_if_fail (path != NULL, FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL); + g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (callback != NULL, NULL); + + return GTK_FILE_SYSTEM_GET_IFACE (file_system)->get_info (file_system, path, types, callback, data); +} + +GtkFileSystemHandle * +gtk_file_system_create_folder (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileSystemCreateFolderCallback callback, + gpointer data) +{ + g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL); + g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (callback != NULL, NULL); - return GTK_FILE_SYSTEM_GET_IFACE (file_system)->create_folder (file_system, path, error); + return GTK_FILE_SYSTEM_GET_IFACE (file_system)->create_folder (file_system, path, callback, data); +} + +void +gtk_file_system_cancel_operation (GtkFileSystemHandle *handle) +{ + g_return_if_fail (GTK_IS_FILE_SYSTEM_HANDLE (handle)); + + return GTK_FILE_SYSTEM_GET_IFACE (handle->file_system)->cancel_operation (handle); } /** @@ -432,16 +629,18 @@ gtk_file_system_volume_get_is_mounted (GtkFileSystem *file_system, * * Return value: TRUE if the @volume was mounted successfully, FALSE otherwise. **/ -gboolean -gtk_file_system_volume_mount (GtkFileSystem *file_system, - GtkFileSystemVolume *volume, - GError **error) +/* FIXME XXX: update documentation above */ +GtkFileSystemHandle * +gtk_file_system_volume_mount (GtkFileSystem *file_system, + GtkFileSystemVolume *volume, + GtkFileSystemVolumeMountCallback callback, + gpointer data) { - g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), FALSE); - g_return_val_if_fail (volume != NULL, FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL); + g_return_val_if_fail (volume != NULL, NULL); + g_return_val_if_fail (callback != NULL, NULL); - return GTK_FILE_SYSTEM_GET_IFACE (file_system)->volume_mount (file_system, volume, error); + return GTK_FILE_SYSTEM_GET_IFACE (file_system)->volume_mount (file_system, volume, callback, data); } /** @@ -485,17 +684,53 @@ gtk_file_system_volume_render_icon (GtkFileSystem *file_system, gint pixel_size, GError **error) { + gchar *icon_name; + GdkPixbuf *pixbuf; + g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL); g_return_val_if_fail (volume != NULL, NULL); g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); g_return_val_if_fail (pixel_size > 0, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - return GTK_FILE_SYSTEM_GET_IFACE (file_system)->volume_render_icon (file_system, - volume, - widget, - pixel_size, - error); + icon_name = gtk_file_system_volume_get_icon_name (file_system, volume, + error); + if (!icon_name) + { + return NULL; + } + + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)), + icon_name, pixel_size, 0, NULL); + g_free (icon_name); + + return pixbuf; +} + +/** + * gtk_file_system_volume_get_icon_name: + * @file_system: a #GtkFileSystem + * @volume: a #GtkFileSystemVolume + * @error: location to store error, or %NULL + * + * Gets an icon name suitable for a #GtkFileSystemVolume. + * + * Return value: An icon name which can be used for rendering an icon for + * this volume, or %NULL if no icon name could be found. In the latter + * case, the @error value will be set as appropriate. + **/ +gchar * +gtk_file_system_volume_get_icon_name (GtkFileSystem *file_system, + GtkFileSystemVolume *volume, + GError **error) +{ + g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL); + g_return_val_if_fail (volume != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + return GTK_FILE_SYSTEM_GET_IFACE (file_system)->volume_get_icon_name (file_system, + volume, + error); } /** @@ -682,21 +917,6 @@ gtk_file_system_path_is_local (GtkFileSystem *file_system, return result; } -GdkPixbuf * -gtk_file_system_render_icon (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkWidget *widget, - gint pixel_size, - GError **error) -{ - g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL); - g_return_val_if_fail (path != NULL, NULL); - g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); - g_return_val_if_fail (pixel_size > 0, NULL); - - return GTK_FILE_SYSTEM_GET_IFACE (file_system)->render_icon (file_system, path, widget, pixel_size, error); -} - /** * gtk_file_system_insert_bookmark: * @file_system: a #GtkFileSystem diff --git a/gtk/gtkfilesystem.h b/gtk/gtkfilesystem.h index 6f038fdbc..57c458a02 100644 --- a/gtk/gtkfilesystem.h +++ b/gtk/gtkfilesystem.h @@ -55,7 +55,8 @@ typedef enum { GTK_FILE_INFO_MIME_TYPE = 1 << 3, GTK_FILE_INFO_MODIFICATION_TIME = 1 << 4, GTK_FILE_INFO_SIZE = 1 << 5, - GTK_FILE_INFO_ALL = (1 << 6) - 1 + GTK_FILE_INFO_ICON = 1 << 6, + GTK_FILE_INFO_ALL = (1 << 7) - 1 } GtkFileInfoType; /* GError enumeration for GtkFileSystem @@ -106,6 +107,43 @@ gint64 gtk_file_info_get_size (const GtkFileInfo *in void gtk_file_info_set_size (GtkFileInfo *info, gint64 size); +void gtk_file_info_set_icon_name (GtkFileInfo *info, + const gchar *con_name); +G_CONST_RETURN gchar *gtk_file_info_get_icon_name (const GtkFileInfo *info); +GdkPixbuf *gtk_file_info_render_icon (const GtkFileInfo *info, + GtkWidget *widget, + gint pixel_size, + GError **error); + +/* GtkFileSystemHandle + */ + +#define GTK_TYPE_FILE_SYSTEM_HANDLE (gtk_file_system_handle_get_type ()) +#define GTK_FILE_SYSTEM_HANDLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FILE_SYSTEM_HANDLE, GtkFileSystemHandle)) +#define GTK_IS_FILE_SYSTEM_HANDLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FILE_SYSTEM_HANDLE)) +#define GTK_FILE_SYSTEM_HANDLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_SYSTEM_HANDLE, GtkFileSystemHandleUnixClass)) +#define GTK_IS_FILE_SYSTEM_HANDLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_SYSTEM_HANDLE)) +#define GTK_FILE_SYSTEM_HANDLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_SYSTEM_HANDLE, GtkFileSystemHandleClass)) + +typedef struct _GtkFileSystemHandle GtkFileSystemHandle; +typedef struct _GtkFileSystemHandleClass GtkFileSystemHandleClass; + +struct _GtkFileSystemHandle +{ + GObject parent_instance; + + GtkFileSystem *file_system; + + guint cancelled : 1; +}; + +struct _GtkFileSystemHandleClass +{ + GObjectClass parent_class; +}; + +GType gtk_file_system_handle_get_type (void); + /* The base GtkFileSystem interface */ #define GTK_TYPE_FILE_SYSTEM (gtk_file_system_get_type ()) @@ -113,6 +151,29 @@ void gtk_file_info_set_size (GtkFileInfo *in #define GTK_IS_FILE_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FILE_SYSTEM)) #define GTK_FILE_SYSTEM_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GTK_TYPE_FILE_SYSTEM, GtkFileSystemIface)) +/* Callbacks for the asynchronous GtkFileSystem operations + */ + +typedef void (* GtkFileSystemGetInfoCallback) (GtkFileSystemHandle *handle, + const GtkFileInfo *file_info, + const GError *error, + gpointer data); +typedef void (* GtkFileSystemGetFolderCallback) (GtkFileSystemHandle *handle, + GtkFileFolder *folder, + const GError *error, + gpointer data); +typedef void (* GtkFileSystemCreateFolderCallback) (GtkFileSystemHandle *handle, + const GtkFilePath *path, + const GError *error, + gpointer data); +typedef void (* GtkFileSystemVolumeMountCallback) (GtkFileSystemHandle *handle, + GtkFileSystemVolume *volume, + const GError *error, + gpointer data); + +/* + */ + struct _GtkFileSystemIface { GTypeInterface base_iface; @@ -123,13 +184,22 @@ struct _GtkFileSystemIface GtkFileSystemVolume * (*get_volume_for_path) (GtkFileSystem *file_system, const GtkFilePath *path); - GtkFileFolder * (*get_folder) (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkFileInfoType types, - GError **error); - gboolean (*create_folder) (GtkFileSystem *file_system, - const GtkFilePath *path, - GError **error); + GtkFileSystemHandle * (*get_folder) (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetFolderCallback callback, + gpointer data); + GtkFileSystemHandle * (*get_info) (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetInfoCallback callback, + gpointer data); + GtkFileSystemHandle * (*create_folder) (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileSystemCreateFolderCallback callback, + gpointer data); + + void (*cancel_operation) (GtkFileSystemHandle *handle); /* Volumes */ @@ -139,15 +209,14 @@ struct _GtkFileSystemIface GtkFileSystemVolume *volume); gboolean (*volume_get_is_mounted) (GtkFileSystem *file_system, GtkFileSystemVolume *volume); - gboolean (*volume_mount) (GtkFileSystem *file_system, - GtkFileSystemVolume *volume, - GError **error); - char * (*volume_get_display_name) (GtkFileSystem *file_system, + GtkFileSystemHandle * (*volume_mount) (GtkFileSystem *file_system, + GtkFileSystemVolume *volume, + GtkFileSystemVolumeMountCallback callback, + gpointer data); + char * (*volume_get_display_name) (GtkFileSystem *file_system, GtkFileSystemVolume *volume); - GdkPixbuf * (*volume_render_icon) (GtkFileSystem *file_system, + gchar * (*volume_get_icon_name) (GtkFileSystem *file_system, GtkFileSystemVolume *volume, - GtkWidget *widget, - gint pixel_size, GError **error); /* Path Manipulation @@ -175,14 +244,6 @@ struct _GtkFileSystemIface GtkFilePath *(*filename_to_path) (GtkFileSystem *file_system, const gchar *path); - /* Icons - */ - GdkPixbuf * (*render_icon) (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkWidget *widget, - gint pixel_size, - GError **error); - /* Bookmarks */ gboolean (*insert_bookmark) (GtkFileSystem *file_system, @@ -221,9 +282,10 @@ GtkFilePath * gtk_file_system_volume_get_base_path (GtkFileSystem GtkFileSystemVolume *volume); gboolean gtk_file_system_volume_get_is_mounted (GtkFileSystem *file_system, GtkFileSystemVolume *volume); -gboolean gtk_file_system_volume_mount (GtkFileSystem *file_system, - GtkFileSystemVolume *volume, - GError **error); +GtkFileSystemHandle *gtk_file_system_volume_mount (GtkFileSystem *file_system, + GtkFileSystemVolume *volume, + GtkFileSystemVolumeMountCallback callback, + gpointer data); char * gtk_file_system_volume_get_display_name (GtkFileSystem *file_system, GtkFileSystemVolume *volume); GdkPixbuf * gtk_file_system_volume_render_icon (GtkFileSystem *file_system, @@ -231,18 +293,29 @@ GdkPixbuf * gtk_file_system_volume_render_icon (GtkFileSystem GtkWidget *widget, gint pixel_size, GError **error); +gchar * gtk_file_system_volume_get_icon_name (GtkFileSystem *file_system, + GtkFileSystemVolume *volume, + GError **error); gboolean gtk_file_system_get_parent (GtkFileSystem *file_system, const GtkFilePath *path, GtkFilePath **parent, GError **error); -GtkFileFolder *gtk_file_system_get_folder (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkFileInfoType types, - GError **error); -gboolean gtk_file_system_create_folder (GtkFileSystem *file_system, - const GtkFilePath *path, - GError **error); +GtkFileSystemHandle *gtk_file_system_get_folder (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetFolderCallback callback, + gpointer data); +GtkFileSystemHandle *gtk_file_system_get_info (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetInfoCallback callback, + gpointer data); +GtkFileSystemHandle *gtk_file_system_create_folder (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileSystemCreateFolderCallback callback, + gpointer data); +void gtk_file_system_cancel_operation (GtkFileSystemHandle *handle); GtkFilePath * gtk_file_system_make_path (GtkFileSystem *file_system, const GtkFilePath *base_path, const gchar *display_name, @@ -266,12 +339,6 @@ GtkFilePath *gtk_file_system_filename_to_path (GtkFileSystem *file_system, gboolean gtk_file_system_path_is_local (GtkFileSystem *filesystem, const GtkFilePath *path); -GdkPixbuf *gtk_file_system_render_icon (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkWidget *widget, - gint pixel_size, - GError **error); - gboolean gtk_file_system_insert_bookmark (GtkFileSystem *file_system, const GtkFilePath *path, gint position, diff --git a/gtk/gtkfilesystemmodel.c b/gtk/gtkfilesystemmodel.c index b2ad218ea..d365351ad 100644 --- a/gtk/gtkfilesystemmodel.c +++ b/gtk/gtkfilesystemmodel.c @@ -50,6 +50,7 @@ static void gtk_file_system_model_class_init (GtkFileSystemModelClass *class); static void gtk_file_system_model_iface_init (GtkTreeModelIface *iface); static void gtk_file_system_model_init (GtkFileSystemModel *model); static void gtk_file_system_model_finalize (GObject *object); +static void gtk_file_system_model_dispose (GObject *object); static void drag_source_iface_init (GtkTreeDragSourceIface *iface); @@ -205,6 +206,7 @@ gtk_file_system_model_class_init (GtkFileSystemModelClass *class) parent_class = g_type_class_peek_parent (class); gobject_class->finalize = gtk_file_system_model_finalize; + gobject_class->dispose = gtk_file_system_model_dispose; file_system_model_signals[FINISHED_LOADING] = g_signal_new (I_("finished-loading"), @@ -258,9 +260,6 @@ gtk_file_system_model_finalize (GObject *object) if (model->file_system) g_object_unref (model->file_system); - if (model->idle_finished_loading_source) - g_source_destroy (model->idle_finished_loading_source); - children = model->roots; while (children) { @@ -272,6 +271,25 @@ gtk_file_system_model_finalize (GObject *object) G_OBJECT_CLASS (parent_class)->finalize (object); } + +static void +gtk_file_system_model_dispose (GObject *object) +{ + GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (object); + + if (model->pending_handles) + { + GSList *l; + + for (l = model->pending_handles; l; l = l->next) + gtk_file_system_cancel_operation (l->data); + g_slist_free (model->pending_handles); + model->pending_handles = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + static void drag_source_iface_init (GtkTreeDragSourceIface *iface) { @@ -630,33 +648,75 @@ root_folder_finished_loading_cb (GtkFileFolder *folder, g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0); } -/* Emits the "finished-loading" signal as an idle handler; see the comment in - * _gtk_file_system_model_new() - */ -static gboolean -idle_finished_loading_cb (GtkFileSystemModel *model) +static void +got_root_folder_cb (GtkFileSystemHandle *handle, + GtkFileFolder *folder, + const GError *error, + gpointer data) { - GDK_THREADS_ENTER (); + GSList *roots = NULL; + GSList *tmp_list; + gboolean cancelled = handle->cancelled; + GtkFileSystemModel *model = data; - g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0); + tmp_list = g_slist_find (model->pending_handles, handle); + if (!tmp_list) + goto out; - g_source_destroy (model->idle_finished_loading_source); - model->idle_finished_loading_source = NULL; + model->pending_handles = g_slist_remove_link (model->pending_handles, + tmp_list); - GDK_THREADS_LEAVE (); + if (cancelled || !folder) + goto out; - return FALSE; -} + model->root_folder = folder; -/* Queues an idle handler to emit the "finished-loading" signal */ -static void -queue_finished_loading (GtkFileSystemModel *model) -{ - model->idle_finished_loading_source = g_idle_source_new (); - g_source_set_closure (model->idle_finished_loading_source, - g_cclosure_new_object (G_CALLBACK (idle_finished_loading_cb), - G_OBJECT (model))); - g_source_attach (model->idle_finished_loading_source, NULL); + if (gtk_file_folder_is_finished_loading (model->root_folder)) + g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0); + else + g_signal_connect_object (model->root_folder, "finished-loading", + G_CALLBACK (root_folder_finished_loading_cb), model, 0); + + gtk_file_folder_list_children (model->root_folder, &roots, NULL); + + g_signal_connect_object (model->root_folder, "deleted", + G_CALLBACK (root_deleted_callback), model, 0); + g_signal_connect_object (model->root_folder, "files-added", + G_CALLBACK (root_files_added_callback), model, 0); + g_signal_connect_object (model->root_folder, "files-changed", + G_CALLBACK (root_files_changed_callback), model, 0); + g_signal_connect_object (model->root_folder, "files-removed", + G_CALLBACK (root_files_removed_callback), model, 0); + + roots = gtk_file_paths_sort (roots); + + for (tmp_list = roots; tmp_list; tmp_list = tmp_list->next) + { + FileModelNode *node = file_model_node_new (model, tmp_list->data); + gtk_file_path_free (tmp_list->data); + node->is_visible = file_model_node_is_visible (model, node); + node->next = model->roots; + node->depth = 0; + model->roots = node; + + if (node->is_visible) + { + GtkTreeIter iter; + GtkTreePath *path; + + iter.user_data = node; + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); + gtk_tree_path_free (path); + } + } + g_slist_free (roots); + + model->roots = (FileModelNode *) g_slist_reverse ((GSList *)model->roots); + +out: + g_object_unref (model); + g_object_unref (handle); } /** @@ -691,28 +751,15 @@ _gtk_file_system_model_new (GtkFileSystem *file_system, GError **error) { GtkFileSystemModel *model; - GtkFileFolder *root_folder; - GSList *roots; - GSList *tmp_list; + GtkFileSystemHandle *handle; g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL); g_return_val_if_fail (root_path != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - /* First, try to load the folder */ + /* First, start loading the root folder */ types |= GTK_FILE_INFO_IS_FOLDER | GTK_FILE_INFO_IS_HIDDEN; - - root_folder = gtk_file_system_get_folder (file_system, root_path, types, error); - - if (!root_folder) - return NULL; - - if (!gtk_file_folder_list_children (root_folder, &roots, error)) - { - g_object_unref (root_folder); - return NULL; - } /* Then, actually create the model and the root nodes */ @@ -724,39 +771,32 @@ _gtk_file_system_model_new (GtkFileSystem *file_system, model->max_depth = MIN (max_depth, G_MAXUSHORT); model->types = types; - model->root_folder = root_folder; + model->root_folder = NULL; model->root_path = gtk_file_path_copy (root_path); - if (gtk_file_folder_is_finished_loading (model->root_folder)) - queue_finished_loading (model); /* done in an idle because we are being created */ - else - g_signal_connect_object (model->root_folder, "finished-loading", - G_CALLBACK (root_folder_finished_loading_cb), model, 0); - - g_signal_connect_object (model->root_folder, "deleted", - G_CALLBACK (root_deleted_callback), model, 0); - g_signal_connect_object (model->root_folder, "files-added", - G_CALLBACK (root_files_added_callback), model, 0); - g_signal_connect_object (model->root_folder, "files-changed", - G_CALLBACK (root_files_changed_callback), model, 0); - g_signal_connect_object (model->root_folder, "files-removed", - G_CALLBACK (root_files_removed_callback), model, 0); + model->roots = NULL; - roots = gtk_file_paths_sort (roots); - - for (tmp_list = roots; tmp_list; tmp_list = tmp_list->next) + handle = gtk_file_system_get_folder (file_system, root_path, types, + got_root_folder_cb, + g_object_ref (model)); + if (!handle) { - FileModelNode *node = file_model_node_new (model, tmp_list->data); - gtk_file_path_free (tmp_list->data); - node->is_visible = file_model_node_is_visible (model, node); - node->next = model->roots; - node->depth = 0; - model->roots = node; + /* In this case got_root_folder_cb() will never be called, so we + * need to unref model twice. + */ + g_object_unref (model); + g_object_unref (model); + + g_set_error (error, + GTK_FILE_CHOOSER_ERROR, + GTK_FILE_CHOOSER_ERROR_NONEXISTENT, + _("Could not obtain root folder")); + + return NULL; } - g_slist_free (roots); - model->roots = (FileModelNode *) g_slist_reverse ((GSList *)model->roots); - + model->pending_handles = g_slist_append (model->pending_handles, handle); + return model; } @@ -988,58 +1028,6 @@ find_child_node (GtkFileSystemModel *model, return NULL; } - - -static FileModelNode * -find_and_ref_path (GtkFileSystemModel *model, - const GtkFilePath *path, - GSList **cleanups) -{ - GtkFilePath *parent_path; - FileModelNode *parent_node; - FileModelNode *child_node; - GtkFileFolder *folder; - - if (gtk_file_path_compare (path, model->root_path) == 0 - || !gtk_file_system_get_parent (model->file_system, path, &parent_path, NULL)) - return NULL; - - if (parent_path) - { - parent_node = find_and_ref_path (model, parent_path, cleanups); - gtk_file_path_free (parent_path); - } - else - parent_node = NULL; - - child_node = find_child_node (model, parent_node, path); - if (child_node) - { - file_model_node_ref (child_node); - return child_node; - } - - folder = gtk_file_system_get_folder (model->file_system, - path, - model->types, - NULL); /* NULL-GError */ - if (folder) - { - *cleanups = g_slist_prepend (*cleanups, folder); - - child_node = find_child_node (model, parent_node, path); - if (child_node) - { - file_model_node_ref (child_node); - return child_node; - } - } - - if (parent_node) - unref_node_and_parents (model, parent_node); - - return NULL; -} /** * _gtk_file_system_model_set_filter: @@ -1064,6 +1052,126 @@ _gtk_file_system_model_set_filter (GtkFileSystemModel *model, model_refilter_all (model); } + +struct RefPathData +{ + GtkFileSystemModel *model; + FileModelNode *node; + FileModelNode *parent_node; + GSList *paths; + GSList *cleanups; + GtkFileSystemModelPathFunc func; + gpointer user_data; +}; + +/* FIXME: maybe we have to wait on finished-loading? */ +static void +ref_path_cb (GtkFileSystemHandle *handle, + GtkFileFolder *folder, + const GError *error, + gpointer data) +{ + struct RefPathData *info = data; + gboolean cancelled = handle->cancelled; + + if (!g_slist_find (info->model->pending_handles, handle)) + goto out; + + info->model->pending_handles = g_slist_remove (info->model->pending_handles, handle); + + /* Note that !folder means that the child node was already + * found, without using get_folder. + */ + if (cancelled || error) + goto out; + + if (folder) + info->cleanups = g_slist_prepend (info->cleanups, folder); + else if (g_slist_length (info->paths) == 1 + && gtk_file_path_compare (info->node->path, info->paths->data) == 0) + { + /* Done, now call the function */ + if (info->node) + { + GtkTreeIter iter; + GtkTreePath *path; + + iter.user_data = info->node; + path = gtk_tree_model_get_path (GTK_TREE_MODEL (info->model), &iter); + + (* info->func) (info->model, path, &iter, info->user_data); + + gtk_tree_path_free (path); + } + + goto out; + } + + info->node = find_child_node (info->model, info->parent_node, info->paths->data); + if (info->node) + file_model_node_ref (info->node); + else + { + goto out; + } + + gtk_file_path_free (info->paths->data); + info->paths = g_slist_remove (info->paths, info->paths->data); + + if (g_slist_length (info->paths) < 1) + { + /* Done, now call the function */ + if (info->node) + { + GtkTreeIter iter; + GtkTreePath *path; + + iter.user_data = info->node; + path = gtk_tree_model_get_path (GTK_TREE_MODEL (info->model), &iter); + + (* info->func) (info->model, path, &iter, info->user_data); + + gtk_tree_path_free (path); + } + + goto out; + } + else + { + info->parent_node = info->node; + + if (info->parent_node->loaded) + { + info->node = find_child_node (info->model, info->parent_node, info->paths->data); + ref_path_cb (NULL, NULL, NULL, info); + } + else + { + GtkFileSystemHandle *handle; + + handle = gtk_file_system_get_folder (info->model->file_system, + info->paths->data, + info->model->types, + ref_path_cb, data); + info->model->pending_handles = + g_slist_append (info->model->pending_handles, handle); + } + + return; + } + +out: + if (info->node) + unref_node_and_parents (info->model, info->node); + gtk_file_paths_free (info->paths); + g_slist_foreach (info->cleanups, (GFunc)g_object_unref, NULL); + g_slist_free (info->cleanups); + g_object_unref (info->model); + g_free (info); + + g_object_unref (handle); +} + /** * _gtk_file_system_model_path_do: * @model: a #GtkFileSystemModel @@ -1090,33 +1198,90 @@ _gtk_file_system_model_set_filter (GtkFileSystemModel *model, * Return value: %TRUE if the path was successfully * found in @model and @func was called. **/ -gboolean -_gtk_file_system_model_path_do (GtkFileSystemModel *model, - const GtkFilePath *path, - GtkFileSystemModelPathFunc func, - gpointer user_data) +void +_gtk_file_system_model_path_do (GtkFileSystemModel *model, + const GtkFilePath *path, + GtkFileSystemModelPathFunc func, + gpointer user_data) { - GSList *cleanups = NULL; - FileModelNode *node = find_and_ref_path (model, path, &cleanups); + GtkFilePath *parent_path; + GSList *paths = NULL; + FileModelNode *node; + struct RefPathData *info; - if (node) - { - GtkTreeIter iter; - GtkTreePath *path; + if (gtk_file_path_compare (path, model->root_path) == 0 + || !gtk_file_system_get_parent (model->file_system, path, &parent_path, NULL)) + return; - iter.user_data = node; - path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + paths = g_slist_prepend (paths, gtk_file_path_copy (path)); + while (gtk_file_path_compare (parent_path, model->root_path) != 0) + { + paths = g_slist_prepend (paths, parent_path); + if (!gtk_file_system_get_parent (model->file_system, parent_path, &parent_path, NULL)) + { + gtk_file_paths_free (paths); + return; + } + } - (*func) (model, path, &iter, user_data); + if (g_slist_length (paths) < 1) + return; - gtk_tree_path_free (path); - unref_node_and_parents (model, node); + /* Now we have all paths, except the root path */ + node = find_child_node (model, NULL, paths->data); + if (!node) + { + gtk_file_paths_free (paths); + return; } - g_slist_foreach (cleanups, (GFunc)g_object_unref, NULL); - g_slist_free (cleanups); + file_model_node_ref (node); - return node != NULL; + gtk_file_path_free (paths->data); + paths = g_slist_remove (paths, paths->data); + + if (g_slist_length (paths) < 1) + { + /* Done, now call the function */ + if (node) + { + GtkTreeIter iter; + GtkTreePath *path; + + iter.user_data = node; + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + + (* func) (model, path, &iter, user_data); + + gtk_tree_path_free (path); + unref_node_and_parents (model, node); + } + } + else + { + info = g_new0 (struct RefPathData, 1); + info->paths = paths; + info->model = g_object_ref (model); + info->func = func; + info->user_data = user_data; + info->node = node; + + if (info->node->loaded) + { + info->parent_node = info->node; + info->node = find_child_node (model, info->parent_node, info->paths->data); + ref_path_cb (NULL, NULL, NULL, info); + } + else + { + GtkFileSystemHandle *handle; + + handle = gtk_file_system_get_folder (model->file_system, + paths->data, model->types, + ref_path_cb, info); + model->pending_handles = g_slist_append (model->pending_handles, handle); + } + } } /** @@ -1389,6 +1554,114 @@ file_model_node_child_unref (FileModelNode *parent) file_model_node_idle_clear (parent); } +struct GetChildrenData +{ + GtkFileSystemModel *model; + FileModelNode *node; +}; + +static void +get_children_get_folder_cb (GtkFileSystemHandle *handle, + GtkFileFolder *folder, + const GError *error, + gpointer callback_data) +{ + GSList *child_paths, *tmp_list; + gboolean has_children = FALSE; + gboolean cancelled = handle->cancelled; + struct GetChildrenData *data = callback_data; + + tmp_list = g_slist_find (data->model->pending_handles, handle); + + if (!tmp_list) + goto out; + + data->model->pending_handles = g_slist_remove_link (data->model->pending_handles, tmp_list); + + if (cancelled || !folder) + { + /* error, no folder, remove dummy child */ + if (data->node->parent && data->node->parent->has_dummy) + { + data->node->parent->children = NULL; + data->node->parent->has_dummy = FALSE; + } + + file_model_node_free (data->node); + + goto out; + } + + data->node->folder = folder; + data->node->load_pending = FALSE; + + if (gtk_file_folder_list_children (folder, &child_paths, NULL)) /* NULL-GError */ + { + child_paths = gtk_file_paths_sort (child_paths); + + for (tmp_list = child_paths; tmp_list; tmp_list = tmp_list->next) + { + FileModelNode *child_node = file_model_node_new (data->model, tmp_list->data); + gtk_file_path_free (tmp_list->data); + child_node->next = data->node->children; + child_node->parent = data->node; + child_node->depth = data->node->depth + 1; + child_node->is_visible = file_model_node_is_visible (data->model, child_node); + + if (child_node->is_visible) + { + GtkTreeIter iter; + GtkTreePath *path; + + has_children = TRUE; + + iter.user_data = child_node; + path = gtk_tree_model_get_path (GTK_TREE_MODEL (data->model), &iter); + gtk_tree_model_row_inserted (GTK_TREE_MODEL (data->model), path, &iter); + gtk_tree_path_free (path); + } + + data->node->children = child_node; + } + g_slist_free (child_paths); + } + + data->node->children = (FileModelNode *)g_slist_reverse ((GSList *)data->node->children); + + g_signal_connect (data->node->folder, "deleted", + G_CALLBACK (deleted_callback), data->node); + g_signal_connect (data->node->folder, "files-added", + G_CALLBACK (files_added_callback), data->node); + g_signal_connect (data->node->folder, "files-changed", + G_CALLBACK (files_changed_callback), data->node); + g_signal_connect (data->node->folder, "files-removed", + G_CALLBACK (files_removed_callback), data->node); + + data->node->loaded = TRUE; + + if (!has_children) + { + /* The hard case ... we claimed this folder had children, but actually + * it didn't. We have to add a dummy child, possibly to remove later. + */ + FileModelNode *child_node = file_model_node_new (data->model, NULL); + child_node->is_visible = TRUE; + child_node->parent = data->node; + child_node->is_dummy = TRUE; + + data->node->children = child_node; + data->node->has_dummy = TRUE; + } + + g_object_set_data (G_OBJECT (data->node->folder), I_("model-node"), data->node); + +out: + g_object_unref (data->model); + g_free (data); + + g_object_unref (handle); +} + static FileModelNode * file_model_node_get_children (GtkFileSystemModel *model, FileModelNode *node) @@ -1396,7 +1669,7 @@ file_model_node_get_children (GtkFileSystemModel *model, if (node->ref_count == 0) return NULL; - if (!node->loaded) + if (!node->loaded && !node->load_pending) { const GtkFileInfo *info = file_model_node_get_info (model, node); gboolean has_children = FALSE; @@ -1405,48 +1678,25 @@ file_model_node_get_children (GtkFileSystemModel *model, file_model_node_idle_clear_cancel (node); if (is_folder) - node->folder = gtk_file_system_get_folder (model->file_system, - node->path, - model->types, - NULL); /* NULL-GError */ - - if (node->folder) - { - GSList *child_paths, *tmp_list; - - if (gtk_file_folder_list_children (node->folder, &child_paths, NULL)) /* NULL-GError */ - { - child_paths = gtk_file_paths_sort (child_paths); - - for (tmp_list = child_paths; tmp_list; tmp_list = tmp_list->next) - { - FileModelNode *child_node = file_model_node_new (model, tmp_list->data); - gtk_file_path_free (tmp_list->data); - child_node->next = node->children; - child_node->parent = node; - child_node->depth = node->depth + 1; - child_node->is_visible = file_model_node_is_visible (model, child_node); - if (child_node->is_visible) - has_children = TRUE; - node->children = child_node; - } - g_slist_free (child_paths); - } - - node->children = (FileModelNode *)g_slist_reverse ((GSList *)node->children); - - g_signal_connect (node->folder, "deleted", - G_CALLBACK (deleted_callback), node); - g_signal_connect (node->folder, "files-added", - G_CALLBACK (files_added_callback), node); - g_signal_connect (node->folder, "files-changed", - G_CALLBACK (files_changed_callback), node); - g_signal_connect (node->folder, "files-removed", - G_CALLBACK (files_removed_callback), node); - - g_object_set_data (G_OBJECT (node->folder), I_("model-node"), node); + { + struct GetChildrenData *data; + GtkFileSystemHandle *handle; + + data = g_new (struct GetChildrenData, 1); + data->model = g_object_ref (model); + data->node = node; + + handle = + gtk_file_system_get_folder (model->file_system, + node->path, + model->types, + get_children_get_folder_cb, + data); + + model->pending_handles = g_slist_append (model->pending_handles, handle); + node->load_pending = TRUE; } - + if (is_folder && !has_children) { /* The hard case ... we claimed this folder had children, but actually @@ -1460,8 +1710,6 @@ file_model_node_get_children (GtkFileSystemModel *model, node->children = child_node; node->has_dummy = TRUE; } - - node->loaded = TRUE; } return node->children; diff --git a/gtk/gtkfilesystemmodel.h b/gtk/gtkfilesystemmodel.h index 2057a142e..c2fa493db 100644 --- a/gtk/gtkfilesystemmodel.h +++ b/gtk/gtkfilesystemmodel.h @@ -71,7 +71,7 @@ typedef void (*GtkFileSystemModelPathFunc) (GtkFileSystemModel *model, GtkTreeIter *iter, gpointer user_data); -gboolean _gtk_file_system_model_path_do (GtkFileSystemModel *model, +void _gtk_file_system_model_path_do (GtkFileSystemModel *model, const GtkFilePath *path, GtkFileSystemModelPathFunc func, gpointer user_data); diff --git a/gtk/gtkfilesystemunix.c b/gtk/gtkfilesystemunix.c index 0dfdcd078..39f98a1ab 100644 --- a/gtk/gtkfilesystemunix.c +++ b/gtk/gtkfilesystemunix.c @@ -111,10 +111,12 @@ struct _GtkFileFolderUnix GtkFileInfoType types; gchar *filename; GHashTable *stat_info; + guint load_folder_id; guint have_stat : 1; guint have_mime_type : 1; guint is_network_dir : 1; guint have_hidden : 1; + guint is_finished_loading : 1; time_t asof; }; @@ -127,7 +129,8 @@ struct stat_info_entry { static const GtkFileInfoType STAT_NEEDED_MASK = (GTK_FILE_INFO_IS_FOLDER | GTK_FILE_INFO_MODIFICATION_TIME | - GTK_FILE_INFO_SIZE); + GTK_FILE_INFO_SIZE | + GTK_FILE_INFO_ICON); static GObjectClass *system_parent_class; static GObjectClass *folder_parent_class; @@ -141,13 +144,21 @@ static GSList * gtk_file_system_unix_list_volumes (GtkFileSys static GtkFileSystemVolume *gtk_file_system_unix_get_volume_for_path (GtkFileSystem *file_system, const GtkFilePath *path); -static GtkFileFolder *gtk_file_system_unix_get_folder (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkFileInfoType types, - GError **error); -static gboolean gtk_file_system_unix_create_folder (GtkFileSystem *file_system, - const GtkFilePath *path, - GError **error); +static GtkFileSystemHandle *gtk_file_system_unix_get_folder (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetFolderCallback callback, + gpointer data); +static GtkFileSystemHandle *gtk_file_system_unix_get_info (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetInfoCallback callback, + gpointer data); +static GtkFileSystemHandle *gtk_file_system_unix_create_folder (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileSystemCreateFolderCallback callback, + gpointer data); +static void gtk_file_system_unix_cancel_operation (GtkFileSystemHandle *handle); static void gtk_file_system_unix_volume_free (GtkFileSystem *file_system, GtkFileSystemVolume *volume); @@ -155,15 +166,14 @@ static GtkFilePath *gtk_file_system_unix_volume_get_base_path (GtkFileSystem GtkFileSystemVolume *volume); static gboolean gtk_file_system_unix_volume_get_is_mounted (GtkFileSystem *file_system, GtkFileSystemVolume *volume); -static gboolean gtk_file_system_unix_volume_mount (GtkFileSystem *file_system, +static GtkFileSystemHandle *gtk_file_system_unix_volume_mount (GtkFileSystem *file_system, GtkFileSystemVolume *volume, - GError **error); + GtkFileSystemVolumeMountCallback callback, + gpointer data); static gchar * gtk_file_system_unix_volume_get_display_name (GtkFileSystem *file_system, GtkFileSystemVolume *volume); -static GdkPixbuf * gtk_file_system_unix_volume_render_icon (GtkFileSystem *file_system, +static gchar * gtk_file_system_unix_volume_get_icon_name (GtkFileSystem *file_system, GtkFileSystemVolume *volume, - GtkWidget *widget, - gint pixel_size, GError **error); static gboolean gtk_file_system_unix_get_parent (GtkFileSystem *file_system, @@ -190,11 +200,6 @@ static GtkFilePath *gtk_file_system_unix_uri_to_path (GtkFileSystem *fi static GtkFilePath *gtk_file_system_unix_filename_to_path (GtkFileSystem *file_system, const gchar *filename); -static GdkPixbuf *gtk_file_system_unix_render_icon (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkWidget *widget, - gint pixel_size, - GError **error); static gboolean gtk_file_system_unix_insert_bookmark (GtkFileSystem *file_system, const GtkFilePath *path, @@ -229,13 +234,35 @@ static GtkFilePath *filename_to_path (const gchar *filename); static gboolean filename_is_root (const char *filename); +static gboolean get_is_hidden_for_file (const char *filename, + const char *basename); static gboolean file_is_hidden (GtkFileFolderUnix *folder_unix, const char *basename); -static gboolean fill_in_names (GtkFileFolderUnix *folder_unix, GError **error); -static void fill_in_stats (GtkFileFolderUnix *folder_unix); -static void fill_in_mime_type (GtkFileFolderUnix *folder_unix); -static void fill_in_hidden (GtkFileFolderUnix *folder_unix); +static GtkFileInfo *file_info_for_root_with_error (const char *root_name, + GError **error); +static gboolean stat_with_error (const char *filename, + struct stat *statbuf, + GError **error); +static GtkFileInfo *create_file_info (GtkFileFolderUnix *folder_unix, + const char *filename, + const char *basename, + GtkFileInfoType types, + struct stat *statbuf, + const char *mime_type); + +static gboolean fill_in_names (GtkFileFolderUnix *folder_unix, + GError **error); +static void fill_in_stats (GtkFileFolderUnix *folder_unix); +static void fill_in_mime_type (GtkFileFolderUnix *folder_unix); +static void fill_in_hidden (GtkFileFolderUnix *folder_unix); + +static gboolean cb_fill_in_stats (gpointer key, + gpointer value, + gpointer user_data); +static gboolean cb_fill_in_mime_type (gpointer key, + gpointer value, + gpointer user_data); static char * get_parent_dir (const char *filename); @@ -311,13 +338,15 @@ gtk_file_system_unix_iface_init (GtkFileSystemIface *iface) iface->list_volumes = gtk_file_system_unix_list_volumes; iface->get_volume_for_path = gtk_file_system_unix_get_volume_for_path; iface->get_folder = gtk_file_system_unix_get_folder; + iface->get_info = gtk_file_system_unix_get_info; iface->create_folder = gtk_file_system_unix_create_folder; + iface->cancel_operation = gtk_file_system_unix_cancel_operation; iface->volume_free = gtk_file_system_unix_volume_free; iface->volume_get_base_path = gtk_file_system_unix_volume_get_base_path; iface->volume_get_is_mounted = gtk_file_system_unix_volume_get_is_mounted; iface->volume_mount = gtk_file_system_unix_volume_mount; iface->volume_get_display_name = gtk_file_system_unix_volume_get_display_name; - iface->volume_render_icon = gtk_file_system_unix_volume_render_icon; + iface->volume_get_icon_name = gtk_file_system_unix_volume_get_icon_name; iface->get_parent = gtk_file_system_unix_get_parent; iface->make_path = gtk_file_system_unix_make_path; iface->parse = gtk_file_system_unix_parse; @@ -325,7 +354,6 @@ gtk_file_system_unix_iface_init (GtkFileSystemIface *iface) iface->path_to_filename = gtk_file_system_unix_path_to_filename; iface->uri_to_path = gtk_file_system_unix_uri_to_path; iface->filename_to_path = gtk_file_system_unix_filename_to_path; - iface->render_icon = gtk_file_system_unix_render_icon; iface->insert_bookmark = gtk_file_system_unix_insert_bookmark; iface->remove_bookmark = gtk_file_system_unix_remove_bookmark; iface->list_bookmarks = gtk_file_system_unix_list_bookmarks; @@ -395,14 +423,386 @@ remove_trailing_slash (const char *filename) return g_memdup (filename, len + 1); } -static GtkFileFolder * -gtk_file_system_unix_get_folder (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkFileInfoType types, - GError **error) +/* Delay callback dispatching + */ + +enum callback_types +{ + CALLBACK_GET_INFO, + CALLBACK_GET_FOLDER, + CALLBACK_CREATE_FOLDER, + CALLBACK_VOLUME_MOUNT +}; + +static void queue_callback (enum callback_types type, gpointer data); + +struct get_info_callback +{ + GtkFileSystemGetInfoCallback callback; + GtkFileSystemHandle *handle; + GtkFileInfo *file_info; + GError *error; + gpointer data; +}; + +static inline void +dispatch_get_info_callback (struct get_info_callback *info) +{ + (* info->callback) (info->handle, info->file_info, info->error, info->data); + + if (info->file_info) + gtk_file_info_free (info->file_info); + + if (info->error) + g_error_free (info->error); + + g_object_unref (info->handle); + + g_free (info); +} + +static inline void +queue_get_info_callback (GtkFileSystemGetInfoCallback callback, + GtkFileSystemHandle *handle, + GtkFileInfo *file_info, + GError *error, + gpointer data) +{ + struct get_info_callback *info; + + info = g_new (struct get_info_callback, 1); + info->callback = callback; + info->handle = handle; + info->file_info = file_info; + info->error = error; + info->data = data; + + queue_callback (CALLBACK_GET_INFO, info); +} + + +struct get_folder_callback +{ + GtkFileSystemGetFolderCallback callback; + GtkFileSystemHandle *handle; + GtkFileFolder *folder; + GError *error; + gpointer data; +}; + +static inline void +dispatch_get_folder_callback (struct get_folder_callback *info) +{ + (* info->callback) (info->handle, info->folder, info->error, info->data); + + if (info->error) + g_error_free (info->error); + + g_object_unref (info->handle); + + g_free (info); +} + +static inline void +queue_get_folder_callback (GtkFileSystemGetFolderCallback callback, + GtkFileSystemHandle *handle, + GtkFileFolder *folder, + GError *error, + gpointer data) +{ + struct get_folder_callback *info; + + info = g_new (struct get_folder_callback, 1); + info->callback = callback; + info->handle = handle; + info->folder = folder; + info->error = error; + info->data = data; + + queue_callback (CALLBACK_GET_FOLDER, info); +} + + +struct create_folder_callback { + GtkFileSystemCreateFolderCallback callback; + GtkFileSystemHandle *handle; + GtkFilePath *path; + GError *error; + gpointer data; +}; + +static inline void +dispatch_create_folder_callback (struct create_folder_callback *info) +{ + (* info->callback) (info->handle, info->path, info->error, info->data); + + if (info->error) + g_error_free (info->error); + + if (info->path) + gtk_file_path_free (info->path); + + g_object_unref (info->handle); + + g_free (info); +} + +static inline void +queue_create_folder_callback (GtkFileSystemCreateFolderCallback callback, + GtkFileSystemHandle *handle, + const GtkFilePath *path, + GError *error, + gpointer data) +{ + struct create_folder_callback *info; + + info = g_new (struct create_folder_callback, 1); + info->callback = callback; + info->handle = handle; + info->path = gtk_file_path_copy (path); + info->error = error; + info->data = data; + + queue_callback (CALLBACK_CREATE_FOLDER, info); +} + + +struct volume_mount_callback +{ + GtkFileSystemVolumeMountCallback callback; + GtkFileSystemHandle *handle; + GtkFileSystemVolume *volume; + GError *error; + gpointer data; +}; + +static inline void +dispatch_volume_mount_callback (struct volume_mount_callback *info) +{ + (* info->callback) (info->handle, info->volume, info->error, info->data); + + if (info->error) + g_error_free (info->error); + + g_object_unref (info->handle); + + g_free (info); +} + +static inline void +queue_volume_mount_callback (GtkFileSystemVolumeMountCallback callback, + GtkFileSystemHandle *handle, + GtkFileSystemVolume *volume, + GError *error, + gpointer data) +{ + struct volume_mount_callback *info; + + info = g_new (struct volume_mount_callback, 1); + info->callback = callback; + info->handle = handle; + info->volume = volume; + info->error = error; + info->data = data; + + queue_callback (CALLBACK_VOLUME_MOUNT, info); +} + + +struct callback_info +{ + enum callback_types type; + + union + { + struct get_info_callback *get_info; + struct get_folder_callback *get_folder; + struct create_folder_callback *create_folder; + struct volume_mount_callback *volume_mount; + } info; +}; + + +static guint execute_callbacks_idle_id = 0; +static GSList *callbacks = NULL; + +static gboolean +execute_callbacks_idle (gpointer data) +{ + GSList *l; + + GDK_THREADS_ENTER (); + + for (l = callbacks; l; l = l->next) + { + struct callback_info *info = l->data; + + switch (info->type) + { + case CALLBACK_GET_INFO: + dispatch_get_info_callback (info->info.get_info); + break; + + case CALLBACK_GET_FOLDER: + dispatch_get_folder_callback (info->info.get_folder); + break; + + case CALLBACK_CREATE_FOLDER: + dispatch_create_folder_callback (info->info.create_folder); + break; + + case CALLBACK_VOLUME_MOUNT: + dispatch_volume_mount_callback (info->info.volume_mount); + break; + } + + g_free (info); + } + + g_slist_free (callbacks); + callbacks = NULL; + + execute_callbacks_idle_id = 0; + + GDK_THREADS_LEAVE (); + + return FALSE; +} + +static void +queue_callback (enum callback_types type, gpointer data) +{ + struct callback_info *info; + + info = g_new (struct callback_info, 1); + info->type = type; + + switch (type) + { + case CALLBACK_GET_INFO: + info->info.get_info = data; + break; + + case CALLBACK_GET_FOLDER: + info->info.get_folder = data; + break; + + case CALLBACK_CREATE_FOLDER: + info->info.create_folder = data; + break; + + case CALLBACK_VOLUME_MOUNT: + info->info.volume_mount = data; + break; + } + + callbacks = g_slist_append (callbacks, info); + + if (!execute_callbacks_idle_id) + execute_callbacks_idle_id = g_idle_add (execute_callbacks_idle, NULL); +} + +static GtkFileSystemHandle * +create_handle (GtkFileSystem *file_system) +{ + GtkFileSystemHandle *handle; + + handle = g_object_new (GTK_TYPE_FILE_SYSTEM_HANDLE, NULL); + handle->file_system = file_system; + + return handle; +} + + + +static GtkFileSystemHandle * +gtk_file_system_unix_get_info (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetInfoCallback callback, + gpointer data) +{ + GError *error = NULL; + GtkFileSystemUnix *system_unix; + GtkFileSystemHandle *handle; + const char *filename; + GtkFileInfo *info; + gchar *basename; + struct stat statbuf; + const char *mime_type; + + system_unix = GTK_FILE_SYSTEM_UNIX (file_system); + handle = create_handle (file_system); + + filename = gtk_file_path_get_string (path); + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (g_path_is_absolute (filename), FALSE); + + if (!stat_with_error (filename, &statbuf, &error)) + { + g_object_ref (handle); + queue_get_info_callback (callback, handle, NULL, error, data); + return handle; + } + + if ((types & GTK_FILE_INFO_MIME_TYPE) != 0) + mime_type = xdg_mime_get_mime_type_for_file (filename, &statbuf); + else + mime_type = NULL; + + basename = g_path_get_basename (filename); + + info = create_file_info (NULL, filename, basename, types, &statbuf, + mime_type); + g_free (basename); + g_object_ref (handle); + queue_get_info_callback (callback, handle, info, NULL, data); + + return handle; +} + +static gboolean +load_folder (gpointer data) +{ + GtkFileFolderUnix *folder_unix = data; + GSList *children; + + GDK_THREADS_ENTER (); + + if ((folder_unix->types & STAT_NEEDED_MASK) != 0) + fill_in_stats (folder_unix); + + if ((folder_unix->types & GTK_FILE_INFO_MIME_TYPE) != 0) + fill_in_mime_type (folder_unix); + + if (gtk_file_folder_unix_list_children (GTK_FILE_FOLDER (folder_unix), &children, NULL)) + { + folder_unix->is_finished_loading = TRUE; + g_signal_emit_by_name (folder_unix, "files-added", children); + gtk_file_paths_free (children); + } + + folder_unix->load_folder_id = 0; + + g_signal_emit_by_name (folder_unix, "finished-loading", 0); + + GDK_THREADS_LEAVE (); + + return FALSE; +} + +static GtkFileSystemHandle * +gtk_file_system_unix_get_folder (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileInfoType types, + GtkFileSystemGetFolderCallback callback, + gpointer data) +{ + GError *error = NULL; GtkFileSystemUnix *system_unix; GtkFileFolderUnix *folder_unix; + GtkFileSystemHandle *handle; const char *filename; char *filename_copy; gboolean set_asof = FALSE; @@ -413,6 +813,8 @@ gtk_file_system_unix_get_folder (GtkFileSystem *file_system, g_return_val_if_fail (filename != NULL, NULL); g_return_val_if_fail (g_path_is_absolute (filename), NULL); + handle = create_handle (file_system); + filename_copy = remove_trailing_slash (filename); folder_unix = g_hash_table_lookup (system_unix->folder_hash, filename_copy); @@ -470,16 +872,19 @@ gtk_file_system_unix_get_folder (GtkFileSystem *file_system, if (result != 0) { gchar *display_name = g_filename_display_name (filename); - g_set_error (error, + g_set_error (&error, GTK_FILE_SYSTEM_ERROR, code, _("Error getting information for '%s': %s"), display_name, g_strerror (my_errno)); + g_object_ref (handle); + queue_get_folder_callback (callback, handle, NULL, error, data); + g_free (display_name); g_free (filename_copy); - return NULL; + return handle; } folder_unix = g_object_new (GTK_TYPE_FILE_FOLDER_UNIX, NULL); @@ -487,9 +892,11 @@ gtk_file_system_unix_get_folder (GtkFileSystem *file_system, folder_unix->filename = filename_copy; folder_unix->types = types; folder_unix->stat_info = NULL; + folder_unix->load_folder_id = 0; folder_unix->have_mime_type = FALSE; folder_unix->have_stat = FALSE; folder_unix->have_hidden = FALSE; + folder_unix->is_finished_loading = FALSE; set_asof = TRUE; if ((system_unix->have_afs && @@ -507,24 +914,28 @@ gtk_file_system_unix_get_folder (GtkFileSystem *file_system, folder_unix); } - if ((types & STAT_NEEDED_MASK) != 0) - fill_in_stats (folder_unix); - - if ((types & GTK_FILE_INFO_MIME_TYPE) != 0) - fill_in_mime_type (folder_unix); - if (set_asof) folder_unix->asof = time (NULL); - return GTK_FILE_FOLDER (folder_unix); + g_object_ref (handle); + queue_get_folder_callback (callback, handle, GTK_FILE_FOLDER (folder_unix), NULL, data); + + /* Start loading the folder contents in an idle */ + folder_unix->load_folder_id = + g_idle_add ((GSourceFunc) load_folder, folder_unix); + + return handle; } -static gboolean -gtk_file_system_unix_create_folder (GtkFileSystem *file_system, - const GtkFilePath *path, - GError **error) +static GtkFileSystemHandle * +gtk_file_system_unix_create_folder (GtkFileSystem *file_system, + const GtkFilePath *path, + GtkFileSystemCreateFolderCallback callback, + gpointer data) { + GError *error = NULL; GtkFileSystemUnix *system_unix; + GtkFileSystemHandle *handle; const char *filename; gboolean result; char *parent, *tmp; @@ -536,6 +947,8 @@ gtk_file_system_unix_create_folder (GtkFileSystem *file_system, g_return_val_if_fail (filename != NULL, FALSE); g_return_val_if_fail (g_path_is_absolute (filename), FALSE); + handle = create_handle (file_system); + tmp = remove_trailing_slash (filename); errno = 0; result = mkdir (tmp, 0777) == 0; @@ -545,18 +958,22 @@ gtk_file_system_unix_create_folder (GtkFileSystem *file_system, if (!result) { gchar *display_name = g_filename_display_name (filename); - g_set_error (error, + g_set_error (&error, GTK_FILE_SYSTEM_ERROR, GTK_FILE_SYSTEM_ERROR_NONEXISTENT, _("Error creating directory '%s': %s"), display_name, g_strerror (save_errno)); + + g_object_ref (handle); + queue_create_folder_callback (callback, handle, path, error, data); + g_free (display_name); - return FALSE; + return handle; } - if (filename_is_root (filename)) - return TRUE; /* hmmm, but with no notification */ + g_object_ref (handle); + queue_create_folder_callback (callback, handle, path, NULL, data); parent = get_parent_dir (filename); if (parent) @@ -566,34 +983,52 @@ gtk_file_system_unix_create_folder (GtkFileSystem *file_system, folder_unix = g_hash_table_lookup (system_unix->folder_hash, parent); if (folder_unix) { - GtkFileInfoType types; - GtkFilePath *parent_path; GSList *paths; - GtkFileFolder *folder; + char *basename; + struct stat_info_entry *entry; - /* This is sort of a hack. We re-get the folder, to ensure that the - * newly-created directory gets read into the folder's info hash table. - */ - - types = folder_unix->types; + /* Make sure the new folder exists in the parent's folder */ + entry = g_new0 (struct stat_info_entry, 1); + if (folder_unix->is_network_dir) + { + entry->statbuf.st_mode = S_IFDIR; + entry->mime_type = g_strdup ("x-directory/normal"); + } - parent_path = gtk_file_path_new_dup (parent); - folder = gtk_file_system_get_folder (file_system, parent_path, types, NULL); - gtk_file_path_free (parent_path); + basename = g_path_get_basename (filename); + g_hash_table_insert (folder_unix->stat_info, + basename, + entry); - if (folder) + if (folder_unix->have_stat) { - paths = g_slist_append (NULL, (GtkFilePath *) path); - g_signal_emit_by_name (folder, "files-added", paths); - g_slist_free (paths); - g_object_unref (folder); + /* Cheating */ + if ((folder_unix->types & STAT_NEEDED_MASK) != 0) + cb_fill_in_stats (basename, entry, folder_unix); + + if ((folder_unix->types & GTK_FILE_INFO_MIME_TYPE) != 0) + cb_fill_in_mime_type (basename, entry, folder_unix); } + + paths = g_slist_append (NULL, (GtkFilePath *) path); + g_signal_emit_by_name (folder_unix, "files-added", paths); + g_slist_free (paths); } g_free (parent); } - return TRUE; + return handle; +} + +static void +gtk_file_system_unix_cancel_operation (GtkFileSystemHandle *handle) +{ + /* We don't set "cancelled" to TRUE here, since the actual operation + * is executed in the function itself and not in a callback. So + * the operations can never be cancelled (since they will be already + * completed at this point. + */ } static void @@ -620,16 +1055,24 @@ gtk_file_system_unix_volume_get_is_mounted (GtkFileSystem *file_system, return TRUE; } -static gboolean -gtk_file_system_unix_volume_mount (GtkFileSystem *file_system, - GtkFileSystemVolume *volume, - GError **error) +static GtkFileSystemHandle * +gtk_file_system_unix_volume_mount (GtkFileSystem *file_system, + GtkFileSystemVolume *volume, + GtkFileSystemVolumeMountCallback callback, + gpointer data) { - g_set_error (error, + GError *error = NULL; + GtkFileSystemHandle *handle = create_handle (file_system); + + g_set_error (&error, GTK_FILE_SYSTEM_ERROR, GTK_FILE_SYSTEM_ERROR_FAILED, _("This file system does not support mounting")); - return FALSE; + + g_object_ref (handle); + queue_volume_mount_callback (callback, handle, volume, error, data); + + return handle; } static gchar * @@ -691,83 +1134,11 @@ get_icon_type (const char *filename, return get_icon_type_from_stat (&statbuf); } -typedef struct -{ - gint size; - GdkPixbuf *pixbuf; -} IconCacheElement; - -static void -icon_cache_element_free (IconCacheElement *element) -{ - if (element->pixbuf) - g_object_unref (element->pixbuf); - g_slice_free (IconCacheElement, element); -} - -static void -icon_theme_changed (GtkIconTheme *icon_theme) -{ - GHashTable *cache; - - /* Difference from the initial creation is that we don't - * reconnect the signal - */ - cache = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify)g_free, - (GDestroyNotify)icon_cache_element_free); - g_object_set_data_full (G_OBJECT (icon_theme), I_("gtk-file-icon-cache"), - cache, (GDestroyNotify)g_hash_table_destroy); -} - -static GdkPixbuf * -get_cached_icon (GtkWidget *widget, - const gchar *name, - gint pixel_size) -{ - GtkIconTheme *icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); - GHashTable *cache = g_object_get_data (G_OBJECT (icon_theme), "gtk-file-icon-cache"); - IconCacheElement *element; - - if (!cache) - { - cache = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify)g_free, - (GDestroyNotify)icon_cache_element_free); - - g_object_set_data_full (G_OBJECT (icon_theme), I_("gtk-file-icon-cache"), - cache, (GDestroyNotify)g_hash_table_destroy); - g_signal_connect (icon_theme, "changed", - G_CALLBACK (icon_theme_changed), NULL); - } - - element = g_hash_table_lookup (cache, name); - if (!element) - { - element = g_slice_new0 (IconCacheElement); - g_hash_table_insert (cache, g_strdup (name), element); - } - - if (element->size != pixel_size) - { - if (element->pixbuf) - g_object_unref (element->pixbuf); - element->size = pixel_size; - element->pixbuf = gtk_icon_theme_load_icon (icon_theme, name, - pixel_size, 0, NULL); - } - - return element->pixbuf ? g_object_ref (element->pixbuf) : NULL; -} - /* Renders a fallback icon from the stock system */ -static GdkPixbuf * -get_fallback_icon (GtkWidget *widget, - IconType icon_type, - GError **error) +static const gchar * +get_fallback_icon_name (IconType icon_type) { const char *stock_name; - GdkPixbuf *pixbuf; switch (icon_type) { @@ -788,38 +1159,18 @@ get_fallback_icon (GtkWidget *widget, break; } - pixbuf = gtk_widget_render_icon (widget, stock_name, GTK_ICON_SIZE_SMALL_TOOLBAR, NULL); - if (!pixbuf) - g_set_error (error, - GTK_FILE_SYSTEM_ERROR, - GTK_FILE_SYSTEM_ERROR_FAILED, - _("Could not get a stock icon for %s"), - stock_name); - - return pixbuf; + return stock_name; } -static GdkPixbuf * -gtk_file_system_unix_volume_render_icon (GtkFileSystem *file_system, - GtkFileSystemVolume *volume, - GtkWidget *widget, - gint pixel_size, - GError **error) +static gchar * +gtk_file_system_unix_volume_get_icon_name (GtkFileSystem *file_system, + GtkFileSystemVolume *volume, + GError **error) { - GdkPixbuf *pixbuf; - - pixbuf = get_cached_icon (widget, "drive-harddisk", pixel_size); - if (pixbuf) - return pixbuf; - - pixbuf = get_cached_icon (widget, "gnome-dev-harddisk", pixel_size); - if (pixbuf) - return pixbuf; - - pixbuf = get_fallback_icon (widget, ICON_BLOCK_DEVICE, error); - g_assert (pixbuf != NULL); - - return pixbuf; + /* FIXME: maybe we just always want to return GTK_STOCK_HARDDISK here? + * or the new tango icon name? + */ + return g_strdup ("gnome-dev-harddisk"); } static char * @@ -1176,26 +1527,21 @@ get_icon_name_for_directory (const char *path) return "gnome-fs-desktop"; else return "gnome-fs-directory"; + + return NULL; } /* Computes our internal icon type based on a path name; also returns the MIME * type in case we come up with ICON_REGULAR. */ static IconType -get_icon_type_from_path (GtkFileSystemUnix *system_unix, - const GtkFilePath *path, +get_icon_type_from_path (GtkFileFolderUnix *folder_unix, + struct stat *statbuf, + const char *filename, const char **mime_type) { - const char *filename; - char *dirname; - GtkFileFolderUnix *folder_unix; IconType icon_type; - filename = gtk_file_path_get_string (path); - dirname = g_path_get_dirname (filename); - folder_unix = g_hash_table_lookup (system_unix->folder_hash, dirname); - g_free (dirname); - *mime_type = NULL; if (folder_unix && folder_unix->have_stat) @@ -1226,6 +1572,9 @@ get_icon_type_from_path (GtkFileSystemUnix *system_unix, } } + if (statbuf) + return get_icon_type_from_stat (statbuf); + icon_type = get_icon_type (filename, NULL); if (icon_type == ICON_REGULAR) *mime_type = xdg_mime_get_mime_type_for_file (filename, NULL); @@ -1234,11 +1583,9 @@ get_icon_type_from_path (GtkFileSystemUnix *system_unix, } /* Renders an icon for a non-ICON_REGULAR file */ -static GdkPixbuf * -get_special_icon (IconType icon_type, - const GtkFilePath *path, - GtkWidget *widget, - gint pixel_size) +static const gchar * +get_special_icon_name (IconType icon_type, + const gchar *filename) { const char *name; @@ -1255,13 +1602,9 @@ get_special_icon (IconType icon_type, case ICON_CHARACTER_DEVICE: name = "gnome-fs-chardev"; break; - case ICON_DIRECTORY: { - const char *filename; - - filename = gtk_file_path_get_string (path); - name = get_icon_name_for_directory (filename); - break; - } + case ICON_DIRECTORY: + /* get_icon_name_for_directory() returns a dupped string */ + return get_icon_name_for_directory (filename); case ICON_EXECUTABLE: name ="gnome-fs-executable"; break; @@ -1276,17 +1619,15 @@ get_special_icon (IconType icon_type, return NULL; } - return get_cached_icon (widget, name, pixel_size); + return name; } -static GdkPixbuf * -get_icon_for_mime_type (GtkWidget *widget, - const char *mime_type, - gint pixel_size) +static gchar * +get_icon_name_for_mime_type (const char *mime_type) { + char *name; const char *separator; GString *icon_name; - GdkPixbuf *pixbuf; if (!mime_type) return NULL; @@ -1295,6 +1636,10 @@ get_icon_for_mime_type (GtkWidget *widget, if (!separator) return NULL; /* maybe we should return a GError with "invalid MIME-type" */ + /* FIXME: we default to the gnome icon naming for now. Some question + * as below, how are we going to handle a second attempt? + */ +#if 0 icon_name = g_string_new (""); g_string_append_len (icon_name, mime_type, separator - mime_type); g_string_append_c (icon_name, '-'); @@ -1311,13 +1656,19 @@ get_icon_for_mime_type (GtkWidget *widget, g_string_free (icon_name, TRUE); if (pixbuf) return pixbuf; +#endif icon_name = g_string_new ("gnome-mime-"); g_string_append_len (icon_name, mime_type, separator - mime_type); g_string_append_c (icon_name, '-'); g_string_append (icon_name, separator + 1); - pixbuf = get_cached_icon (widget, icon_name->str, pixel_size); - g_string_free (icon_name, TRUE); + name = icon_name->str; + g_string_free (icon_name, FALSE); + + return name; + + /* FIXME: how are we going to implement a second attempt? */ +#if 0 if (pixbuf) return pixbuf; @@ -1327,50 +1678,7 @@ get_icon_for_mime_type (GtkWidget *widget, g_string_free (icon_name, TRUE); return pixbuf; -} - -static GdkPixbuf * -gtk_file_system_unix_render_icon (GtkFileSystem *file_system, - const GtkFilePath *path, - GtkWidget *widget, - gint pixel_size, - GError **error) -{ - GtkFileSystemUnix *system_unix; - IconType icon_type; - const char *mime_type; - GdkPixbuf *pixbuf; - - system_unix = GTK_FILE_SYSTEM_UNIX (file_system); - - icon_type = get_icon_type_from_path (system_unix, path, &mime_type); - - switch (icon_type) { - case ICON_NONE: - goto fallback; - - case ICON_REGULAR: - pixbuf = get_icon_for_mime_type (widget, mime_type, pixel_size); - break; - - default: - pixbuf = get_special_icon (icon_type, path, widget, pixel_size); - } - - if (pixbuf) - goto out; - - fallback: - - pixbuf = get_cached_icon (widget, "gnome-fs-regular", pixel_size); - if (pixbuf) - goto out; - - pixbuf = get_fallback_icon (widget, icon_type, error); - - out: - - return pixbuf; +#endif } static void @@ -1808,6 +2116,12 @@ gtk_file_folder_unix_finalize (GObject *object) { GtkFileFolderUnix *folder_unix = GTK_FILE_FOLDER_UNIX (object); + if (folder_unix->load_folder_id) + { + g_source_remove (folder_unix->load_folder_id); + folder_unix->load_folder_id = 0; + } + g_hash_table_remove (folder_unix->system_unix->folder_hash, folder_unix->filename); if (folder_unix->stat_info) @@ -1912,8 +2226,16 @@ create_file_info (GtkFileFolderUnix *folder_unix, if (types & GTK_FILE_INFO_IS_HIDDEN) { - if (file_is_hidden (folder_unix, basename)) - gtk_file_info_set_is_hidden (info, TRUE); + if (folder_unix) + { + if (file_is_hidden (folder_unix, basename)) + gtk_file_info_set_is_hidden (info, TRUE); + } + else + { + if (get_is_hidden_for_file (filename, basename)) + gtk_file_info_set_is_hidden (info, TRUE); + } } if (types & GTK_FILE_INFO_IS_FOLDER) @@ -1928,6 +2250,40 @@ create_file_info (GtkFileFolderUnix *folder_unix, if (types & GTK_FILE_INFO_SIZE) gtk_file_info_set_size (info, (gint64) statbuf->st_size); + if (types & GTK_FILE_INFO_ICON) + { + IconType icon_type; + gboolean free_icon_name = FALSE; + const char *icon_name; + const char *icon_mime_type; + + icon_type = get_icon_type_from_path (folder_unix, statbuf, filename, &icon_mime_type); + + switch (icon_type) + { + case ICON_NONE: + icon_name = get_fallback_icon_name (icon_type); + break; + + case ICON_REGULAR: + free_icon_name = TRUE; + if (icon_mime_type) + icon_name = get_icon_name_for_mime_type (icon_mime_type); + else + icon_name = get_icon_name_for_mime_type (mime_type); + break; + + default: + icon_name = get_special_icon_name (icon_type, filename); + break; + } + + gtk_file_info_set_icon_name (info, icon_name); + + if (free_icon_name) + g_free ((char *) icon_name); + } + return info; } @@ -2052,13 +2408,11 @@ gtk_file_folder_unix_list_children (GtkFileFolder *folder, GtkFileFolderUnix *folder_unix = GTK_FILE_FOLDER_UNIX (folder); GSList *l; - if (!fill_in_names (folder_unix, error)) - return FALSE; - *children = NULL; /* Get the list of basenames. */ - g_hash_table_foreach (folder_unix->stat_info, cb_list_children, children); + if (folder_unix->stat_info) + g_hash_table_foreach (folder_unix->stat_info, cb_list_children, children); /* Turn basenames into GFilePaths. */ for (l = *children; l; l = l->next) @@ -2075,8 +2429,7 @@ gtk_file_folder_unix_list_children (GtkFileFolder *folder, static gboolean gtk_file_folder_unix_is_finished_loading (GtkFileFolder *folder) { - /* Since we don't do asynchronous loads, we are always finished loading */ - return TRUE; + return GTK_FILE_FOLDER_UNIX (folder)->is_finished_loading; } static void @@ -2207,23 +2560,40 @@ fill_in_mime_type (GtkFileFolderUnix *folder_unix) folder_unix->have_mime_type = TRUE; } +static gchar ** +read_hidden_file (const char *dirname) +{ + gchar **lines = NULL; + gchar *contents; + gchar *hidden_file; + + hidden_file = g_build_filename (dirname, HIDDEN_FILENAME, NULL); + + if (g_file_get_contents (hidden_file, &contents, NULL, NULL)) + { + lines = g_strsplit (contents, "\n", -1); + g_free (contents); + } + + g_free (hidden_file); + + return lines; +} + static void fill_in_hidden (GtkFileFolderUnix *folder_unix) { - gchar *hidden_file; - gchar *contents; + gchar **lines; if (folder_unix->have_hidden) return; - hidden_file = g_build_filename (folder_unix->filename, HIDDEN_FILENAME, NULL); + lines = read_hidden_file (folder_unix->filename); - if (g_file_get_contents (hidden_file, &contents, NULL, NULL)) + if (lines) { - gchar **lines; int i; - lines = g_strsplit (contents, "\n", -1); for (i = 0; lines[i]; i++) { if (lines[i][0]) @@ -2237,10 +2607,8 @@ fill_in_hidden (GtkFileFolderUnix *folder_unix) } g_strfreev (lines); - g_free (contents); } - g_free (hidden_file); folder_unix->have_hidden = TRUE; } @@ -2263,6 +2631,37 @@ filename_is_root (const char *filename) return (after_root != NULL && *after_root == '\0'); } +static gboolean +get_is_hidden_for_file (const char *filename, + const char *basename) +{ + gchar *dirname; + gchar **lines; + gboolean hidden = FALSE; + + dirname = g_path_get_dirname (filename); + lines = read_hidden_file (dirname); + g_free (dirname); + + if (lines) + { + int i; + + for (i = 0; lines[i]; i++) + { + if (lines[i][0] && strcmp (lines[i], basename) == 0) + { + hidden = TRUE; + break; + } + } + + g_strfreev (lines); + } + + return hidden; +} + static gboolean file_is_hidden (GtkFileFolderUnix *folder_unix, const char *basename) diff --git a/gtk/gtkpathbar.c b/gtk/gtkpathbar.c index ec62d32d3..2688c1ee3 100644 --- a/gtk/gtkpathbar.c +++ b/gtk/gtkpathbar.c @@ -65,6 +65,7 @@ struct _ButtonData GtkFilePath *path; GtkWidget *image; GtkWidget *label; + GtkFileSystemHandle *handle; guint ignore_changes : 1; guint file_is_hidden : 1; }; @@ -140,6 +141,8 @@ gtk_path_bar_init (GtkPathBar *path_bar) GTK_WIDGET_SET_FLAGS (path_bar, GTK_NO_WINDOW); gtk_widget_set_redraw_on_allocate (GTK_WIDGET (path_bar), FALSE); + path_bar->set_path_handle = NULL; + path_bar->spacing = 3; path_bar->up_slider_button = get_slider_button (path_bar, GTK_ARROW_LEFT); path_bar->down_slider_button = get_slider_button (path_bar, GTK_ARROW_RIGHT); @@ -247,8 +250,13 @@ remove_settings_signal (GtkPathBar *path_bar, static void gtk_path_bar_dispose (GObject *object) { - remove_settings_signal (GTK_PATH_BAR (object), - gtk_widget_get_screen (GTK_WIDGET (object))); + GtkPathBar *path_bar = GTK_PATH_BAR (object); + + remove_settings_signal (path_bar, gtk_widget_get_screen (GTK_WIDGET (object))); + + if (path_bar->set_path_handle) + gtk_file_system_cancel_operation (path_bar->set_path_handle); + path_bar->set_path_handle = NULL; G_OBJECT_CLASS (gtk_path_bar_parent_class)->dispose (object); } @@ -957,7 +965,11 @@ button_clicked_cb (GtkWidget *button, button_list = g_list_find (path_bar->button_list, button_data); g_assert (button_list != NULL); + g_signal_handlers_block_by_func (button, + G_CALLBACK (button_clicked_cb), data); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + g_signal_handlers_unblock_by_func (button, + G_CALLBACK (button_clicked_cb), data); if (button_list->prev) { @@ -977,22 +989,79 @@ button_clicked_cb (GtkWidget *button, button_data->path, child_path, child_is_hidden); } -static GdkPixbuf * -get_button_image (GtkPathBar *path_bar, - ButtonType button_type) +struct SetButtonImageData +{ + GtkPathBar *path_bar; + ButtonData *button_data; +}; + +static void +set_button_image_get_info_cb (GtkFileSystemHandle *handle, + const GtkFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = handle->cancelled; + GdkPixbuf *pixbuf; + struct SetButtonImageData *data = user_data; + + if (handle != data->button_data->handle) + goto out; + + data->button_data->handle = NULL; + + if (cancelled || error) + goto out; + + pixbuf = gtk_file_info_render_icon (info, GTK_WIDGET (data->path_bar), + data->path_bar->icon_size, NULL); + gtk_image_set_from_pixbuf (GTK_IMAGE (data->button_data->image), pixbuf); + + switch (data->button_data->type) + { + case HOME_BUTTON: + if (data->path_bar->home_icon) + g_object_unref (pixbuf); + else + data->path_bar->home_icon = pixbuf; + break; + + case DESKTOP_BUTTON: + if (data->path_bar->desktop_icon) + g_object_unref (pixbuf); + else + data->path_bar->desktop_icon = pixbuf; + break; + + default: + break; + }; + +out: + g_free (data); + g_object_unref (handle); +} + +static void +set_button_image (GtkPathBar *path_bar, + ButtonData *button_data) { GtkFileSystemVolume *volume; + struct SetButtonImageData *data; - switch (button_type) + switch (button_data->type) { case ROOT_BUTTON: if (path_bar->root_icon != NULL) - return path_bar->root_icon; + { + gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), path_bar->root_icon); + break; + } volume = gtk_file_system_get_volume_for_path (path_bar->file_system, path_bar->root_path); if (volume == NULL) - return NULL; + return; path_bar->root_icon = gtk_file_system_volume_render_icon (path_bar->file_system, volume, @@ -1001,37 +1070,63 @@ get_button_image (GtkPathBar *path_bar, NULL); gtk_file_system_volume_free (path_bar->file_system, volume); - return path_bar->root_icon; + gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), path_bar->root_icon); + break; + case HOME_BUTTON: if (path_bar->home_icon != NULL) - return path_bar->home_icon; - - path_bar->home_icon = gtk_file_system_render_icon (path_bar->file_system, - path_bar->home_path, - GTK_WIDGET (path_bar), - path_bar->icon_size, - NULL); - return path_bar->home_icon; + { + gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), path_bar->home_icon); + break; + } + + data = g_new0 (struct SetButtonImageData, 1); + data->path_bar = path_bar; + data->button_data = button_data; + + if (button_data->handle) + gtk_file_system_cancel_operation (button_data->handle); + + button_data->handle = + gtk_file_system_get_info (path_bar->file_system, + path_bar->home_path, + GTK_FILE_INFO_ICON, + set_button_image_get_info_cb, + data); + break; + case DESKTOP_BUTTON: if (path_bar->desktop_icon != NULL) - return path_bar->desktop_icon; - - path_bar->desktop_icon = gtk_file_system_render_icon (path_bar->file_system, - path_bar->desktop_path, - GTK_WIDGET (path_bar), - path_bar->icon_size, - NULL); - return path_bar->desktop_icon; + { + gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), path_bar->desktop_icon); + break; + } + + data = g_new0 (struct SetButtonImageData, 1); + data->path_bar = path_bar; + data->button_data = button_data; + + if (button_data->handle) + gtk_file_system_cancel_operation (button_data->handle); + + button_data->handle = + gtk_file_system_get_info (path_bar->file_system, + path_bar->desktop_path, + GTK_FILE_INFO_ICON, + set_button_image_get_info_cb, + data); + break; default: - return NULL; + break; } - - return NULL; } static void button_data_free (ButtonData *button_data) { + if (button_data->handle) + gtk_file_system_cancel_operation (button_data->handle); + gtk_file_path_free (button_data->path); g_free (button_data->dir_name); g_free (button_data); @@ -1094,9 +1189,7 @@ gtk_path_bar_update_button_appearance (GtkPathBar *path_bar, if (button_data->image != NULL) { - GdkPixbuf *pixbuf; - pixbuf = get_button_image (path_bar, button_data->type); - gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), pixbuf); + set_button_image (path_bar, button_data); } if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button)) != current_dir) @@ -1294,126 +1387,174 @@ gtk_path_bar_check_parent_path (GtkPathBar *path_bar, return FALSE; } -gboolean -_gtk_path_bar_set_path (GtkPathBar *path_bar, - const GtkFilePath *file_path, - const gboolean keep_trail, - GError **error) + +struct SetPathInfo { GtkFilePath *path; - gboolean first_directory = TRUE; - gboolean result; - GList *new_buttons = NULL; - GList *fake_root = NULL; - - g_return_val_if_fail (GTK_IS_PATH_BAR (path_bar), FALSE); - g_return_val_if_fail (file_path != NULL, FALSE); - - result = TRUE; - - /* Check whether the new path is already present in the pathbar as buttons. - * This could be a parent directory or a previous selected subdirectory. - */ - if (keep_trail && - gtk_path_bar_check_parent_path (path_bar, file_path, path_bar->file_system)) - return TRUE; + GtkFilePath *parent_path; + GtkPathBar *path_bar; + GList *new_buttons; + GList *fake_root; + gboolean first_directory; +}; - path = gtk_file_path_copy (file_path); +static void +gtk_path_bar_set_path_finish (struct SetPathInfo *info, + gboolean result) +{ + if (result) + { + GList *l; - gtk_widget_push_composite_child (); + gtk_path_bar_clear_buttons (info->path_bar); + info->path_bar->button_list = g_list_reverse (info->new_buttons); + info->path_bar->fake_root = info->fake_root; - while (path != NULL) - { - GtkFilePath *parent_path = NULL; - ButtonData *button_data; - const gchar *display_name; - gboolean is_hidden; - GtkFileFolder *file_folder; - GtkFileInfo *file_info; - gboolean valid; - - valid = gtk_file_system_get_parent (path_bar->file_system, - path, - &parent_path, - error); - if (!valid) + for (l = info->path_bar->button_list; l; l = l->next) { - result = FALSE; - gtk_file_path_free (path); - break; + GtkWidget *button = BUTTON_DATA (l->data)->button; + gtk_container_add (GTK_CONTAINER (info->path_bar), button); } + } + else + { + GList *l; - file_folder = gtk_file_system_get_folder (path_bar->file_system, - parent_path ? parent_path : path, - GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_HIDDEN, - NULL); - if (!file_folder) + for (l = info->new_buttons; l; l = l->next) { - result = FALSE; - gtk_file_path_free (parent_path); - gtk_file_path_free (path); - break; - } - - file_info = gtk_file_folder_get_info (file_folder, parent_path ? path : NULL, error); - g_object_unref (file_folder); + ButtonData *button_data; - if (!file_info) - { - result = FALSE; - gtk_file_path_free (parent_path); - gtk_file_path_free (path); - break; + button_data = BUTTON_DATA (l->data); + gtk_widget_destroy (button_data->button); } - display_name = gtk_file_info_get_display_name (file_info); - is_hidden = gtk_file_info_get_is_hidden (file_info); - - button_data = make_directory_button (path_bar, display_name, path, first_directory, is_hidden); - gtk_file_info_free (file_info); - gtk_file_path_free (path); + g_list_free (info->new_buttons); + } - new_buttons = g_list_prepend (new_buttons, button_data); + if (info->path) + gtk_file_path_free (info->path); + if (info->parent_path) + gtk_file_path_free (info->parent_path); + g_free (info); +} - if (BUTTON_IS_FAKE_ROOT (button_data)) - fake_root = new_buttons; +static void +gtk_path_bar_get_info_callback (GtkFileSystemHandle *handle, + const GtkFileInfo *file_info, + const GError *error, + gpointer data) +{ + gboolean cancelled = handle->cancelled; + struct SetPathInfo *path_info = data; + ButtonData *button_data; + const gchar *display_name; + gboolean is_hidden; + gboolean valid; - path = parent_path; - first_directory = FALSE; + if (handle != path_info->path_bar->set_path_handle) + { + gtk_path_bar_set_path_finish (path_info, FALSE); + g_object_unref (handle); + return; } - if (result) + g_object_unref (handle); + path_info->path_bar->set_path_handle = NULL; + + if (cancelled || !file_info) { - GList *l; + gtk_path_bar_set_path_finish (path_info, FALSE); + return; + } - gtk_path_bar_clear_buttons (path_bar); - path_bar->button_list = g_list_reverse (new_buttons); - path_bar->fake_root = fake_root; + display_name = gtk_file_info_get_display_name (file_info); + is_hidden = gtk_file_info_get_is_hidden (file_info); - for (l = path_bar->button_list; l; l = l->next) - { - GtkWidget *button = BUTTON_DATA (l->data)->button; - gtk_container_add (GTK_CONTAINER (path_bar), button); - } + gtk_widget_push_composite_child (); + button_data = make_directory_button (path_info->path_bar, display_name, + path_info->path, + path_info->first_directory, is_hidden); + gtk_widget_pop_composite_child (); + gtk_file_path_free (path_info->path); + + path_info->new_buttons = g_list_prepend (path_info->new_buttons, button_data); + + if (BUTTON_IS_FAKE_ROOT (button_data)) + path_info->fake_root = path_info->new_buttons; + + path_info->path = path_info->parent_path; + path_info->first_directory = FALSE; + + if (!path_info->path) + { + gtk_path_bar_set_path_finish (path_info, TRUE); + return; } - else + + valid = gtk_file_system_get_parent (path_info->path_bar->file_system, + path_info->path, + &path_info->parent_path, + NULL); + if (!valid) { - GList *l; + gtk_path_bar_set_path_finish (path_info, FALSE); + return; + } - for (l = new_buttons; l; l = l->next) - { - ButtonData *button_data; + path_info->path_bar->set_path_handle = + gtk_file_system_get_info (handle->file_system, + path_info->path, + GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_HIDDEN, + gtk_path_bar_get_info_callback, + path_info); +} - button_data = BUTTON_DATA (l->data); - gtk_widget_destroy (button_data->button); - } +gboolean +_gtk_path_bar_set_path (GtkPathBar *path_bar, + const GtkFilePath *file_path, + const gboolean keep_trail, + GError **error) +{ + struct SetPathInfo *info; + gboolean result; + + g_return_val_if_fail (GTK_IS_PATH_BAR (path_bar), FALSE); + g_return_val_if_fail (file_path != NULL, FALSE); + + result = TRUE; + + /* Check whether the new path is already present in the pathbar as buttons. + * This could be a parent directory or a previous selected subdirectory. + */ + if (keep_trail && + gtk_path_bar_check_parent_path (path_bar, file_path, path_bar->file_system)) + return TRUE; - g_list_free (new_buttons); + info = g_new0 (struct SetPathInfo, 1); + info->path = gtk_file_path_copy (file_path); + info->path_bar = path_bar; + info->first_directory = TRUE; + + result = gtk_file_system_get_parent (path_bar->file_system, + info->path, &info->parent_path, error); + if (!result) + { + gtk_file_path_free (info->path); + g_free (info); + return result; } - gtk_widget_pop_composite_child (); + if (path_bar->set_path_handle) + gtk_file_system_cancel_operation (path_bar->set_path_handle); + + path_bar->set_path_handle = + gtk_file_system_get_info (path_bar->file_system, + info->path, + GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_HIDDEN, + gtk_path_bar_get_info_callback, + info); - return result; + return TRUE; } /* FIXME: This should be a construct-only property */ diff --git a/gtk/gtkpathbar.h b/gtk/gtkpathbar.h index e395ccc0b..9474427b5 100644 --- a/gtk/gtkpathbar.h +++ b/gtk/gtkpathbar.h @@ -45,6 +45,8 @@ struct _GtkPathBar GtkFilePath *home_path; GtkFilePath *desktop_path; + GtkFileSystemHandle *set_path_handle; + GdkPixbuf *root_icon; GdkPixbuf *home_icon; GdkPixbuf *desktop_icon; -- cgit v1.2.1