From ba429d344132c088177e853cce8ff7181570b221 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 19:42:51 +0200 Subject: Adding upstream version 44.2. Signed-off-by: Daniel Baumann --- plugins/filebrowser/gedit-file-browser-store.c | 3672 ++++++++++++++++++++++++ 1 file changed, 3672 insertions(+) create mode 100644 plugins/filebrowser/gedit-file-browser-store.c (limited to 'plugins/filebrowser/gedit-file-browser-store.c') diff --git a/plugins/filebrowser/gedit-file-browser-store.c b/plugins/filebrowser/gedit-file-browser-store.c new file mode 100644 index 0000000..ef19b55 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-store.c @@ -0,0 +1,3672 @@ +/* + * gedit-file-browser-store.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "gedit-file-browser-store.h" +#include "gedit-file-browser-enum-types.h" +#include "gedit-file-browser-error.h" +#include "gedit-file-browser-utils.h" + +#define NODE_IS_DIR(node) (FILE_IS_DIR((node)->flags)) +#define NODE_IS_HIDDEN(node) (FILE_IS_HIDDEN((node)->flags)) +#define NODE_IS_TEXT(node) (FILE_IS_TEXT((node)->flags)) +#define NODE_LOADED(node) (FILE_LOADED((node)->flags)) +#define NODE_IS_FILTERED(node) (FILE_IS_FILTERED((node)->flags)) +#define NODE_IS_DUMMY(node) (FILE_IS_DUMMY((node)->flags)) + +#define FILE_BROWSER_NODE_DIR(node) ((FileBrowserNodeDir *)(node)) + +#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100 +#define STANDARD_ATTRIBUTE_TYPES G_FILE_ATTRIBUTE_STANDARD_TYPE "," \ + G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \ + G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP "," \ + G_FILE_ATTRIBUTE_STANDARD_NAME "," \ + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \ + G_FILE_ATTRIBUTE_STANDARD_ICON + +typedef struct _FileBrowserNode FileBrowserNode; +typedef struct _FileBrowserNodeDir FileBrowserNodeDir; +typedef struct _AsyncData AsyncData; +typedef struct _AsyncNode AsyncNode; + +typedef gint (*SortFunc) (FileBrowserNode *node1, + FileBrowserNode *node2); + +struct _AsyncData +{ + GeditFileBrowserStore *model; + GCancellable *cancellable; + gboolean trash; + GList *files; + GList *iter; + gboolean removed; +}; + +struct _AsyncNode +{ + FileBrowserNodeDir *dir; + GCancellable *cancellable; + GSList *original_children; +}; + +typedef struct { + GeditFileBrowserStore *model; + GFile *virtual_root; + GMountOperation *operation; + GCancellable *cancellable; +} MountInfo; + +struct _FileBrowserNode +{ + GFile *file; + guint flags; + gchar *icon_name; + gchar *name; + gchar *markup; + + GdkPixbuf *icon; + GdkPixbuf *emblem; + + FileBrowserNode *parent; + gint pos; + gboolean inserted; +}; + +struct _FileBrowserNodeDir +{ + FileBrowserNode node; + GSList *children; + + GCancellable *cancellable; + GFileMonitor *monitor; + GeditFileBrowserStore *model; +}; + +struct _GeditFileBrowserStorePrivate +{ + FileBrowserNode *root; + FileBrowserNode *virtual_root; + GType column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_NUM]; + + GeditFileBrowserStoreFilterMode filter_mode; + GeditFileBrowserStoreFilterFunc filter_func; + gpointer filter_user_data; + + gchar **binary_patterns; + GPtrArray *binary_pattern_specs; + + SortFunc sort_func; + + GSList *async_handles; + MountInfo *mount_info; +}; + +static FileBrowserNode *model_find_node (GeditFileBrowserStore *model, + FileBrowserNode *node, + GFile *uri); +static void model_remove_node (GeditFileBrowserStore *model, + FileBrowserNode *node, + GtkTreePath *path, + gboolean free_nodes); + +static void set_virtual_root_from_node (GeditFileBrowserStore *model, + FileBrowserNode *node); + +static void gedit_file_browser_store_iface_init (GtkTreeModelIface *iface); +static GtkTreeModelFlags gedit_file_browser_store_get_flags (GtkTreeModel *tree_model); +static gint gedit_file_browser_store_get_n_columns (GtkTreeModel *tree_model); +static GType gedit_file_browser_store_get_column_type (GtkTreeModel *tree_model, + gint index); +static gboolean gedit_file_browser_store_get_iter (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreePath *path); +static GtkTreePath *gedit_file_browser_store_get_path (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static void gedit_file_browser_store_get_value (GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value); +static gboolean gedit_file_browser_store_iter_next (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static gboolean gedit_file_browser_store_iter_children (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent); +static gboolean gedit_file_browser_store_iter_has_child (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static gint gedit_file_browser_store_iter_n_children (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static gboolean gedit_file_browser_store_iter_nth_child (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n); +static gboolean gedit_file_browser_store_iter_parent (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child); +static void gedit_file_browser_store_row_inserted (GtkTreeModel *tree_model, + GtkTreePath *path, + GtkTreeIter *iter); + +static void gedit_file_browser_store_drag_source_init (GtkTreeDragSourceIface *iface); +static gboolean gedit_file_browser_store_row_draggable (GtkTreeDragSource *drag_source, + GtkTreePath *path); +static gboolean gedit_file_browser_store_drag_data_delete (GtkTreeDragSource *drag_source, + GtkTreePath *path); +static gboolean gedit_file_browser_store_drag_data_get (GtkTreeDragSource *drag_source, + GtkTreePath *path, + GtkSelectionData *selection_data); + +static void file_browser_node_free (GeditFileBrowserStore *model, + FileBrowserNode *node); +static void model_add_node (GeditFileBrowserStore *model, + FileBrowserNode *child, + FileBrowserNode *parent); +static void model_clear (GeditFileBrowserStore *model, + gboolean free_nodes); +static gint model_sort_default (FileBrowserNode *node1, + FileBrowserNode *node2); +static void model_check_dummy (GeditFileBrowserStore *model, + FileBrowserNode *node); +static void next_files_async (GFileEnumerator *enumerator, + AsyncNode *async); + +static void delete_files (AsyncData *data); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserStore, gedit_file_browser_store, + G_TYPE_OBJECT, + 0, + G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserStore) + G_IMPLEMENT_INTERFACE_DYNAMIC (GTK_TYPE_TREE_MODEL, + gedit_file_browser_store_iface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC (GTK_TYPE_TREE_DRAG_SOURCE, + gedit_file_browser_store_drag_source_init)) + +/* Properties */ +enum { + PROP_0, + + PROP_ROOT, + PROP_VIRTUAL_ROOT, + PROP_FILTER_MODE, + PROP_BINARY_PATTERNS +}; + +/* Signals */ +enum +{ + BEGIN_LOADING, + END_LOADING, + ERROR, + NO_TRASH, + RENAME, + BEGIN_REFRESH, + END_REFRESH, + UNLOAD, + BEFORE_ROW_DELETED, + NUM_SIGNALS +}; + +static guint model_signals[NUM_SIGNALS] = { 0 }; + +static void +cancel_mount_operation (GeditFileBrowserStore *obj) +{ + if (obj->priv->mount_info != NULL) + { + obj->priv->mount_info->model = NULL; + g_cancellable_cancel (obj->priv->mount_info->cancellable); + obj->priv->mount_info = NULL; + } +} + +static void +gedit_file_browser_store_finalize (GObject *object) +{ + GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object); + + /* Free all the nodes */ + file_browser_node_free (obj, obj->priv->root); + + if (obj->priv->binary_patterns != NULL) + { + g_strfreev (obj->priv->binary_patterns); + g_ptr_array_unref (obj->priv->binary_pattern_specs); + } + + /* Cancel any asynchronous operations */ + for (GSList *item = obj->priv->async_handles; item; item = item->next) + { + AsyncData *data = (AsyncData *)(item->data); + g_cancellable_cancel (data->cancellable); + + data->removed = TRUE; + } + + cancel_mount_operation (obj); + + g_slist_free (obj->priv->async_handles); + G_OBJECT_CLASS (gedit_file_browser_store_parent_class)->finalize (object); +} + +static void +set_gvalue_from_node (GValue *value, + FileBrowserNode *node) +{ + if (node == NULL) + g_value_set_object (value, NULL); + else + g_value_set_object (value, node->file); +} + +static void +gedit_file_browser_store_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object); + + switch (prop_id) + { + case PROP_ROOT: + set_gvalue_from_node (value, obj->priv->root); + break; + case PROP_VIRTUAL_ROOT: + set_gvalue_from_node (value, obj->priv->virtual_root); + break; + case PROP_FILTER_MODE: + g_value_set_flags (value, obj->priv->filter_mode); + break; + case PROP_BINARY_PATTERNS: + g_value_set_boxed (value, obj->priv->binary_patterns); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_store_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object); + + switch (prop_id) + { + case PROP_ROOT: + gedit_file_browser_store_set_root (obj, G_FILE (g_value_get_object (value))); + break; + case PROP_FILTER_MODE: + gedit_file_browser_store_set_filter_mode (obj, g_value_get_flags (value)); + break; + case PROP_BINARY_PATTERNS: + gedit_file_browser_store_set_binary_patterns (obj, g_value_get_boxed (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_store_class_init (GeditFileBrowserStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_file_browser_store_finalize; + object_class->get_property = gedit_file_browser_store_get_property; + object_class->set_property = gedit_file_browser_store_set_property; + + g_object_class_install_property (object_class, PROP_ROOT, + g_param_spec_object ("root", + "Root", + "The root location", + G_TYPE_FILE, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_VIRTUAL_ROOT, + g_param_spec_object ("virtual-root", + "Virtual Root", + "The virtual root location", + G_TYPE_FILE, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_FILTER_MODE, + g_param_spec_flags ("filter-mode", + "Filter Mode", + "The filter mode", + GEDIT_TYPE_FILE_BROWSER_STORE_FILTER_MODE, + gedit_file_browser_store_filter_mode_get_default (), + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_BINARY_PATTERNS, + g_param_spec_boxed ("binary-patterns", + "Binary Patterns", + "The binary patterns", + G_TYPE_STRV, + G_PARAM_READWRITE)); + + model_signals[BEGIN_LOADING] = + g_signal_new ("begin-loading", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, begin_loading), + NULL, NULL, NULL, + G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); + model_signals[END_LOADING] = + g_signal_new ("end-loading", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, end_loading), + NULL, NULL, NULL, + G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); + model_signals[ERROR] = + g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, error), + NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); + model_signals[NO_TRASH] = + g_signal_new ("no-trash", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, no_trash), + g_signal_accumulator_true_handled, NULL, NULL, + G_TYPE_BOOLEAN, 1, G_TYPE_POINTER); + model_signals[RENAME] = + g_signal_new ("rename", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, rename), + NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_FILE, G_TYPE_FILE); + model_signals[BEGIN_REFRESH] = + g_signal_new ("begin-refresh", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, begin_refresh), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + model_signals[END_REFRESH] = + g_signal_new ("end-refresh", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, end_refresh), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + model_signals[UNLOAD] = + g_signal_new ("unload", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, unload), + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_FILE); + model_signals[BEFORE_ROW_DELETED] = + g_signal_new ("before-row-deleted", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, before_row_deleted), + NULL, NULL, NULL, + G_TYPE_NONE, 1, + GTK_TYPE_TREE_PATH | G_SIGNAL_TYPE_STATIC_SCOPE); +} + +static void +gedit_file_browser_store_class_finalize (GeditFileBrowserStoreClass *klass) +{ +} + +static void +gedit_file_browser_store_iface_init (GtkTreeModelIface *iface) +{ + iface->get_flags = gedit_file_browser_store_get_flags; + iface->get_n_columns = gedit_file_browser_store_get_n_columns; + iface->get_column_type = gedit_file_browser_store_get_column_type; + iface->get_iter = gedit_file_browser_store_get_iter; + iface->get_path = gedit_file_browser_store_get_path; + iface->get_value = gedit_file_browser_store_get_value; + iface->iter_next = gedit_file_browser_store_iter_next; + iface->iter_children = gedit_file_browser_store_iter_children; + iface->iter_has_child = gedit_file_browser_store_iter_has_child; + iface->iter_n_children = gedit_file_browser_store_iter_n_children; + iface->iter_nth_child = gedit_file_browser_store_iter_nth_child; + iface->iter_parent = gedit_file_browser_store_iter_parent; + iface->row_inserted = gedit_file_browser_store_row_inserted; +} + +static void +gedit_file_browser_store_drag_source_init (GtkTreeDragSourceIface *iface) +{ + iface->row_draggable = gedit_file_browser_store_row_draggable; + iface->drag_data_delete = gedit_file_browser_store_drag_data_delete; + iface->drag_data_get = gedit_file_browser_store_drag_data_get; +} + +static void +gedit_file_browser_store_init (GeditFileBrowserStore *obj) +{ + obj->priv = gedit_file_browser_store_get_instance_private (obj); + + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION] = G_TYPE_FILE; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP] = G_TYPE_STRING; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS] = G_TYPE_UINT; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_ICON] = GDK_TYPE_PIXBUF; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_ICON_NAME] = G_TYPE_STRING; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_NAME] = G_TYPE_STRING; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM] = GDK_TYPE_PIXBUF; + + /* Default filter mode is hiding the hidden files */ + obj->priv->filter_mode = gedit_file_browser_store_filter_mode_get_default (); + obj->priv->sort_func = model_sort_default; +} + +static gboolean +node_has_parent (FileBrowserNode *node, + FileBrowserNode *parent) +{ + if (node->parent == NULL) + return FALSE; + + if (node->parent == parent) + return TRUE; + + return node_has_parent (node->parent, parent); +} + +static gboolean +node_in_tree (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + return node_has_parent (node, model->priv->virtual_root); +} + +static gboolean +model_node_visibility (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + if (node == NULL) + return FALSE; + + if (NODE_IS_DUMMY (node)) + return !NODE_IS_HIDDEN (node); + + if (node == model->priv->virtual_root) + return TRUE; + + if (!node_has_parent (node, model->priv->virtual_root)) + return FALSE; + + return !NODE_IS_FILTERED (node); +} + +static gboolean +model_node_inserted (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + return node == model->priv->virtual_root || + (model_node_visibility (model, node) && node->inserted); +} + +/* Interface implementation */ + +static GtkTreeModelFlags +gedit_file_browser_store_get_flags (GtkTreeModel *tree_model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), (GtkTreeModelFlags) 0); + + return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static gint +gedit_file_browser_store_get_n_columns (GtkTreeModel *tree_model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), 0); + + return GEDIT_FILE_BROWSER_STORE_COLUMN_NUM; +} + +static GType +gedit_file_browser_store_get_column_type (GtkTreeModel *tree_model, + gint idx) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), G_TYPE_INVALID); + g_return_val_if_fail (idx < GEDIT_FILE_BROWSER_STORE_COLUMN_NUM && idx >= 0, G_TYPE_INVALID); + + return GEDIT_FILE_BROWSER_STORE (tree_model)->priv->column_types[idx]; +} + +static gboolean +gedit_file_browser_store_get_iter (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreePath *path) +{ + GeditFileBrowserStore *model; + FileBrowserNode *node; + gint *indices, depth; + + g_assert (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_assert (path != NULL); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + indices = gtk_tree_path_get_indices (path); + depth = gtk_tree_path_get_depth (path); + node = model->priv->virtual_root; + + for (guint i = 0; i < depth; ++i) + { + GSList *item; + gint num = 0; + + if (node == NULL) + return FALSE; + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + { + FileBrowserNode *child = (FileBrowserNode *)(item->data); + + if (model_node_inserted (model, child)) + { + if (num == indices[i]) + break; + + num++; + } + } + + if (item == NULL) + return FALSE; + + node = (FileBrowserNode *)(item->data); + } + + iter->user_data = node; + iter->user_data2 = NULL; + iter->user_data3 = NULL; + + return node != NULL; +} + +static GtkTreePath * +gedit_file_browser_store_get_path_real (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + GtkTreePath *path = gtk_tree_path_new (); + gint num = 0; + + while (node != model->priv->virtual_root) + { + if (node->parent == NULL) + { + gtk_tree_path_free (path); + return NULL; + } + + num = 0; + + for (GSList *item = FILE_BROWSER_NODE_DIR (node->parent)->children; item; item = item->next) + { + FileBrowserNode *check = (FileBrowserNode *)(item->data); + + if (model_node_visibility (model, check) && (check == node || check->inserted)) + { + if (check == node) + { + gtk_tree_path_prepend_index (path, num); + break; + } + + ++num; + } + else if (check == node) + { + if (NODE_IS_DUMMY (node)) + g_warning ("Dummy not visible???"); + + gtk_tree_path_free (path); + return NULL; + } + } + + node = node->parent; + } + + return path; +} + +static GtkTreePath * +gedit_file_browser_store_get_path (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), NULL); + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (iter->user_data != NULL, NULL); + + return gedit_file_browser_store_get_path_real (GEDIT_FILE_BROWSER_STORE (tree_model), + (FileBrowserNode *)(iter->user_data)); +} + +static void +gedit_file_browser_store_get_value (GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value) +{ + FileBrowserNode *node; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + node = (FileBrowserNode *)(iter->user_data); + + g_value_init (value, GEDIT_FILE_BROWSER_STORE (tree_model)->priv->column_types[column]); + + switch (column) + { + case GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION: + set_gvalue_from_node (value, node); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP: + g_value_set_string (value, node->markup); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS: + g_value_set_uint (value, node->flags); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_ICON: + g_value_set_object (value, node->icon); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_ICON_NAME: + g_value_set_string (value, node->icon_name); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_NAME: + g_value_set_string (value, node->name); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM: + g_value_set_object (value, node->emblem); + break; + default: + g_return_if_reached (); + } +} + +static gboolean +gedit_file_browser_store_iter_next (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + GeditFileBrowserStore *model; + FileBrowserNode *node; + GSList *first; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (iter->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + node = (FileBrowserNode *)(iter->user_data); + + if (node->parent == NULL) + return FALSE; + + first = g_slist_next (g_slist_find (FILE_BROWSER_NODE_DIR (node->parent)->children, node)); + + for (GSList *item = first; item; item = item->next) + { + if (model_node_inserted (model, (FileBrowserNode *)(item->data))) + { + iter->user_data = item->data; + return TRUE; + } + } + + return FALSE; +} + +static gboolean +gedit_file_browser_store_iter_children (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE); + g_return_val_if_fail (parent == NULL || parent->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (parent == NULL) + node = model->priv->virtual_root; + else + node = (FileBrowserNode *)(parent->user_data); + + if (node == NULL) + return FALSE; + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + { + if (model_node_inserted (model, (FileBrowserNode *)(item->data))) + { + iter->user_data = item->data; + return TRUE; + } + } + + return FALSE; +} + +static gboolean +filter_tree_model_iter_has_child_real (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + if (!NODE_IS_DIR (node)) + return FALSE; + + for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + { + if (model_node_inserted (model, (FileBrowserNode *)(item->data))) + return TRUE; + } + + return FALSE; +} + +static gboolean +gedit_file_browser_store_iter_has_child (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE); + g_return_val_if_fail (iter == NULL || iter->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (iter == NULL) + node = model->priv->virtual_root; + else + node = (FileBrowserNode *)(iter->user_data); + + return filter_tree_model_iter_has_child_real (model, node); +} + +static gint +gedit_file_browser_store_iter_n_children (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + gint num = 0; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE); + g_return_val_if_fail (iter == NULL || iter->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (iter == NULL) + node = model->priv->virtual_root; + else + node = (FileBrowserNode *)(iter->user_data); + + if (!NODE_IS_DIR (node)) + return 0; + + for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + { + if (model_node_inserted (model, (FileBrowserNode *)(item->data))) + ++num; + } + + return num; +} + +static gboolean +gedit_file_browser_store_iter_nth_child (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + gint num = 0; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE); + g_return_val_if_fail (parent == NULL || parent->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (parent == NULL) + node = model->priv->virtual_root; + else + node = (FileBrowserNode *)(parent->user_data); + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + { + if (model_node_inserted (model, (FileBrowserNode *)(item->data))) + { + if (num == n) + { + iter->user_data = item->data; + return TRUE; + } + + ++num; + } + } + + return FALSE; +} + +static gboolean +gedit_file_browser_store_iter_parent (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE); + g_return_val_if_fail (child != NULL, FALSE); + g_return_val_if_fail (child->user_data != NULL, FALSE); + + node = (FileBrowserNode *)(child->user_data); + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (!node_in_tree (model, node)) + return FALSE; + + if (node->parent == NULL) + return FALSE; + + iter->user_data = node->parent; + return TRUE; +} + +static void +gedit_file_browser_store_row_inserted (GtkTreeModel *tree_model, + GtkTreePath *path, + GtkTreeIter *iter) +{ + FileBrowserNode *node = (FileBrowserNode *)(iter->user_data); + + node->inserted = TRUE; +} + +static gboolean +gedit_file_browser_store_row_draggable (GtkTreeDragSource *drag_source, + GtkTreePath *path) +{ + GtkTreeIter iter; + GeditFileBrowserStoreFlag flags; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path)) + return FALSE; + + gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + return !FILE_IS_DUMMY (flags); +} + +static gboolean +gedit_file_browser_store_drag_data_delete (GtkTreeDragSource *drag_source, + GtkTreePath *path) +{ + return FALSE; +} + +static gboolean +gedit_file_browser_store_drag_data_get (GtkTreeDragSource *drag_source, + GtkTreePath *path, + GtkSelectionData *selection_data) +{ + GtkTreeIter iter; + GFile *location; + gchar *uris[2] = {0, }; + gboolean ret; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path)) + return FALSE; + + gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + g_assert (location); + + uris[0] = g_file_get_uri (location); + ret = gtk_selection_data_set_uris (selection_data, uris); + + g_free (uris[0]); + g_object_unref (location); + + return ret; +} + +#define FILTER_HIDDEN(mode) (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN) +#define FILTER_BINARY(mode) (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY) + +/* Private */ +static void +model_begin_loading (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + GtkTreeIter iter; + + iter.user_data = node; + g_signal_emit (model, model_signals[BEGIN_LOADING], 0, &iter); +} + +static void +model_end_loading (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + GtkTreeIter iter; + + iter.user_data = node; + g_signal_emit (model, model_signals[END_LOADING], 0, &iter); +} + +static void +model_node_update_visibility (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + GtkTreeIter iter; + + node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + + if (FILTER_HIDDEN (model->priv->filter_mode) && + NODE_IS_HIDDEN (node)) + { + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + return; + } + + if (FILTER_BINARY (model->priv->filter_mode) && !NODE_IS_DIR (node)) + { + if (!NODE_IS_TEXT (node)) + { + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + return; + } + else if (model->priv->binary_patterns != NULL) + { + gssize name_length = strlen (node->name); + gchar *name_reversed = g_utf8_strreverse (node->name, name_length); + + for (guint i = 0; i < model->priv->binary_pattern_specs->len; ++i) + { + GPatternSpec *spec = g_ptr_array_index (model->priv->binary_pattern_specs, i); + + if (g_pattern_spec_match (spec, name_length, node->name, name_reversed)) + { + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + g_free (name_reversed); + return; + } + } + + g_free (name_reversed); + } + } + + if (model->priv->filter_func) + { + iter.user_data = node; + + if (!model->priv->filter_func (model, &iter, model->priv->filter_user_data)) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + } +} + +static gint +collate_nodes (FileBrowserNode *node1, + FileBrowserNode *node2) +{ + if (node1->name == NULL) + { + return -1; + } + else if (node2->name == NULL) + { + return 1; + } + else + { + gchar *k1 = g_utf8_collate_key_for_filename (node1->name, -1); + gchar *k2 = g_utf8_collate_key_for_filename (node2->name, -1); + gint result = strcmp (k1, k2); + + g_free (k1); + g_free (k2); + + return result; + } +} + +static gint +model_sort_default (FileBrowserNode *node1, + FileBrowserNode *node2) +{ + gint f1 = NODE_IS_DUMMY (node1); + gint f2 = NODE_IS_DUMMY (node2); + + if (f1 && f2) + return 0; + else if (f1 || f2) + return f1 ? -1 : 1; + + f1 = NODE_IS_DIR (node1); + f2 = NODE_IS_DIR (node2); + + if (f1 != f2) + return f1 ? -1 : 1; + + return collate_nodes (node1, node2); +} + +static void +model_resort_node (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (node->parent); + + if (!model_node_visibility (model, node->parent)) + { + /* Just sort the children of the parent */ + dir->children = g_slist_sort (dir->children, (GCompareFunc)(model->priv->sort_func)); + } + else + { + GtkTreeIter iter; + GtkTreePath *path; + gint *neworder; + gint pos = 0; + + /* Store current positions */ + for (GSList *item = dir->children; item; item = item->next) + { + FileBrowserNode *child = (FileBrowserNode *)(item->data); + + if (model_node_visibility (model, child)) + child->pos = pos++; + } + + dir->children = g_slist_sort (dir->children, (GCompareFunc)(model->priv->sort_func)); + neworder = g_new (gint, pos); + pos = 0; + + /* Store the new positions */ + for (GSList *item = dir->children; item; item = item->next) + { + FileBrowserNode *child = (FileBrowserNode *)(item->data); + + if (model_node_visibility (model, child)) + neworder[pos++] = child->pos; + } + + iter.user_data = node->parent; + path = gedit_file_browser_store_get_path_real (model, node->parent); + + gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), path, &iter, neworder); + + g_free (neworder); + gtk_tree_path_free (path); + } +} + +static void +row_changed (GeditFileBrowserStore *model, + GtkTreePath **path, + GtkTreeIter *iter) +{ + GtkTreeRowReference *ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), *path); + + /* Insert a copy of the actual path here because the row-inserted + signal may alter the path */ + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), *path, iter); + gtk_tree_path_free (*path); + + *path = gtk_tree_row_reference_get_path (ref); + gtk_tree_row_reference_free (ref); +} + +static void +row_inserted (GeditFileBrowserStore *model, + GtkTreePath **path, + GtkTreeIter *iter) +{ + /* This function creates a row reference for the path because it's + uncertain what might change the actual model/view when we insert + a node, maybe another directory load is triggered for example. + Because functions that use this function rely on the notion that + the path remains pointed towards the inserted node, we use the + reference to keep track. */ + GtkTreeRowReference *ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), *path); + GtkTreePath *copy = gtk_tree_path_copy (*path); + + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), copy, iter); + gtk_tree_path_free (copy); + + if (ref) + { + gtk_tree_path_free (*path); + + /* To restore the path, we get the path from the reference. But, since + we inserted a row, the path will be one index further than the + actual path of our node. We therefore call gtk_tree_path_prev */ + *path = gtk_tree_row_reference_get_path (ref); + gtk_tree_path_prev (*path); + } + + gtk_tree_row_reference_free (ref); +} + +static void +row_deleted (GeditFileBrowserStore *model, + FileBrowserNode *node, + const GtkTreePath *path) +{ + gboolean hidden; + GtkTreePath *copy; + + /* We should always be called when the row is still inserted */ + g_return_if_fail (node->inserted == TRUE || NODE_IS_DUMMY (node)); + + hidden = FILE_IS_HIDDEN (node->flags); + node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + /* Create temporary copies of the path as the signals may alter it */ + + copy = gtk_tree_path_copy (path); + g_signal_emit (model, model_signals[BEFORE_ROW_DELETED], 0, copy); + gtk_tree_path_free (copy); + + node->inserted = FALSE; + + if (hidden) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + copy = gtk_tree_path_copy (path); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), copy); + gtk_tree_path_free (copy); +} + +static void +model_refilter_node (GeditFileBrowserStore *model, + FileBrowserNode *node, + GtkTreePath **path) +{ + gboolean old_visible; + gboolean new_visible; + FileBrowserNodeDir *dir; + GSList *item; + GtkTreeIter iter; + GtkTreePath *tmppath = NULL; + gboolean in_tree; + + if (node == NULL) + return; + + old_visible = model_node_visibility (model, node); + model_node_update_visibility (model, node); + + in_tree = node_in_tree (model, node); + + if (path == NULL) + { + if (in_tree) + tmppath = gedit_file_browser_store_get_path_real (model, node); + else + tmppath = gtk_tree_path_new_first (); + + path = &tmppath; + } + + if (NODE_IS_DIR (node)) + { + if (in_tree) + gtk_tree_path_down (*path); + + dir = FILE_BROWSER_NODE_DIR (node); + + for (item = dir->children; item; item = item->next) + model_refilter_node (model, (FileBrowserNode *)(item->data), path); + + if (in_tree) + gtk_tree_path_up (*path); + } + + if (in_tree) + { + new_visible = model_node_visibility (model, node); + + if (old_visible != new_visible) + { + if (old_visible) + { + row_deleted (model, node, *path); + } + else + { + iter.user_data = node; + row_inserted (model, path, &iter); + gtk_tree_path_next (*path); + } + } + else if (old_visible) + { + gtk_tree_path_next (*path); + } + } + + model_check_dummy (model, node); + + if (tmppath) + gtk_tree_path_free (tmppath); +} + +static void +model_refilter (GeditFileBrowserStore *model) +{ + model_refilter_node (model, model->priv->root, NULL); +} + +static void +file_browser_node_set_name (FileBrowserNode *node) +{ + g_free (node->name); + g_free (node->markup); + + if (node->file) + node->name = gedit_file_browser_utils_file_basename (node->file); + else + node->name = NULL; + + if (node->name) + node->markup = g_markup_escape_text (node->name, -1); + else + node->markup = NULL; +} + +static void +file_browser_node_init (FileBrowserNode *node, + GFile *file, + FileBrowserNode *parent) +{ + if (file != NULL) + { + node->file = g_object_ref (file); + file_browser_node_set_name (node); + } + + node->parent = parent; +} + +static FileBrowserNode * +file_browser_node_new (GFile *file, + FileBrowserNode *parent) +{ + FileBrowserNode *node = g_slice_new0 (FileBrowserNode); + + file_browser_node_init (node, file, parent); + return node; +} + +static FileBrowserNode * +file_browser_node_dir_new (GeditFileBrowserStore *model, + GFile *file, + FileBrowserNode *parent) +{ + FileBrowserNode *node = (FileBrowserNode *)g_slice_new0 (FileBrowserNodeDir); + + file_browser_node_init (node, file, parent); + + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY; + + FILE_BROWSER_NODE_DIR (node)->model = model; + + return node; +} + +static void +file_browser_node_free_children (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + if (node == NULL || !NODE_IS_DIR (node)) + return; + + for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + file_browser_node_free (model, (FileBrowserNode *)(item->data)); + + g_slist_free (FILE_BROWSER_NODE_DIR (node)->children); + FILE_BROWSER_NODE_DIR (node)->children = NULL; + + /* This node is no longer loaded */ + node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_LOADED; +} + +static void +file_browser_node_free (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + if (node == NULL) + return; + + if (NODE_IS_DIR (node)) + { + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (node); + + if (dir->cancellable) + { + g_cancellable_cancel (dir->cancellable); + g_object_unref (dir->cancellable); + + model_end_loading (model, node); + } + + file_browser_node_free_children (model, node); + + if (dir->monitor) + { + g_file_monitor_cancel (dir->monitor); + g_object_unref (dir->monitor); + } + } + + if (node->file) + { + g_signal_emit (model, model_signals[UNLOAD], 0, node->file); + g_object_unref (node->file); + } + + if (node->icon) + g_object_unref (node->icon); + + if (node->emblem) + g_object_unref (node->emblem); + + g_free (node->icon_name); + g_free (node->name); + g_free (node->markup); + + if (NODE_IS_DIR (node)) + g_slice_free (FileBrowserNodeDir, (FileBrowserNodeDir *)node); + else + g_slice_free (FileBrowserNode, (FileBrowserNode *)node); +} + +/** + * model_remove_node_children: + * @model: the #GeditFileBrowserStore + * @node: the FileBrowserNode to remove + * @path: the path of the node, or NULL to let the path be calculated + * @free_nodes: whether to also remove the nodes from memory + * + * Removes all the children of node from the model. This function is used + * to remove the child nodes from the _model_. Don't use it to just free + * a node. + */ +static void +model_remove_node_children (GeditFileBrowserStore *model, + FileBrowserNode *node, + GtkTreePath *path, + gboolean free_nodes) +{ + FileBrowserNodeDir *dir; + GtkTreePath *path_child; + GSList *list; + + if (node == NULL || !NODE_IS_DIR (node)) + return; + + dir = FILE_BROWSER_NODE_DIR (node); + + if (dir->children == NULL) + return; + + if (!model_node_visibility (model, node)) + { + /* Node is invisible and therefore the children can just be freed */ + if (free_nodes) + file_browser_node_free_children (model, node); + + return; + } + + if (path == NULL) + path_child = gedit_file_browser_store_get_path_real (model, node); + else + path_child = gtk_tree_path_copy (path); + + gtk_tree_path_down (path_child); + + list = g_slist_copy (dir->children); + + for (GSList *item = list; item; item = item->next) + model_remove_node (model, (FileBrowserNode *)(item->data), path_child, free_nodes); + + g_slist_free (list); + gtk_tree_path_free (path_child); +} + +/** + * model_remove_node: + * @model: the #GeditFileBrowserStore + * @node: the FileBrowserNode to remove + * @path: the path to use to remove this node, or NULL to use the path + * calculated from the node itself + * @free_nodes: whether to also remove the nodes from memory + * + * Removes this node and all its children from the model. This function is used + * to remove the node from the _model_. Don't use it to just free + * a node. + */ +static void +model_remove_node (GeditFileBrowserStore *model, + FileBrowserNode *node, + GtkTreePath *path, + gboolean free_nodes) +{ + gboolean free_path = FALSE; + FileBrowserNode *parent; + + if (path == NULL) + { + path = gedit_file_browser_store_get_path_real (model, node); + free_path = TRUE; + } + + model_remove_node_children (model, node, path, free_nodes); + + /* Only delete if the node is visible in the tree (but only when it's not the virtual root) */ + if (model_node_visibility (model, node) && node != model->priv->virtual_root) + row_deleted (model, node, path); + + if (free_path) + gtk_tree_path_free (path); + + parent = node->parent; + + /* Remove the node from the parents children list */ + if (free_nodes && parent) + FILE_BROWSER_NODE_DIR (node->parent)->children = + g_slist_remove (FILE_BROWSER_NODE_DIR (node->parent)->children, node); + + /* If this is the virtual root, than set the parent as the virtual root */ + if (node == model->priv->virtual_root) + set_virtual_root_from_node (model, parent); + else if (parent && model_node_visibility (model, parent) && !(free_nodes && NODE_IS_DUMMY(node))) + model_check_dummy (model, parent); + + /* Now free the node if necessary */ + if (free_nodes) + file_browser_node_free (model, node); +} + +/** + * model_clear: + * @model: the #GeditFileBrowserStore + * @free_nodes: whether to also remove the nodes from memory + * + * Removes all nodes from the model. This function is used + * to remove all the nodes from the _model_. Don't use it to just free the + * nodes in the model. + */ +static void +model_clear (GeditFileBrowserStore *model, + gboolean free_nodes) +{ + GtkTreePath *path = gtk_tree_path_new (); + + model_remove_node_children (model, model->priv->virtual_root, path, free_nodes); + gtk_tree_path_free (path); + + /* Remove the dummy if there is one */ + if (model->priv->virtual_root) + { + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (model->priv->virtual_root); + + if (dir->children != NULL) + { + FileBrowserNode *dummy = (FileBrowserNode *)(dir->children->data); + + if (NODE_IS_DUMMY (dummy) && model_node_visibility (model, dummy)) + { + path = gtk_tree_path_new_first (); + row_deleted (model, dummy, path); + gtk_tree_path_free (path); + } + } + } +} + +static void +file_browser_node_unload (GeditFileBrowserStore *model, + FileBrowserNode *node, + gboolean remove_children) +{ + FileBrowserNodeDir *dir; + + if (node == NULL) + return; + + if (!NODE_IS_DIR (node) || !NODE_LOADED (node)) + return; + + dir = FILE_BROWSER_NODE_DIR (node); + + if (remove_children) + model_remove_node_children (model, node, NULL, TRUE); + + if (dir->cancellable) + { + g_cancellable_cancel (dir->cancellable); + g_object_unref (dir->cancellable); + + model_end_loading (model, node); + dir->cancellable = NULL; + } + + if (dir->monitor) + { + g_file_monitor_cancel (dir->monitor); + g_object_unref (dir->monitor); + + dir->monitor = NULL; + } + + node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_LOADED; +} + +static void +model_recomposite_icon_real (GeditFileBrowserStore *tree_model, + FileBrowserNode *node, + GFileInfo *info) +{ + GdkPixbuf *icon; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_return_if_fail (node != NULL); + + if (node->file == NULL) + return; + + if (info) + { + GIcon *gicon = g_file_info_get_icon (info); + + if (gicon != NULL) + icon = gedit_file_browser_utils_pixbuf_from_icon (gicon, GTK_ICON_SIZE_MENU); + else + icon = NULL; + } + else + { + icon = gedit_file_browser_utils_pixbuf_from_file (node->file, GTK_ICON_SIZE_MENU, FALSE); + } + + /* Fallback to the same icon as the file browser */ + if (!icon) + icon = gedit_file_browser_utils_pixbuf_from_theme ("text-x-generic", GTK_ICON_SIZE_MENU); + + if (node->icon) + g_object_unref (node->icon); + + if (node->emblem) + { + gint icon_size; + + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, NULL, &icon_size); + + if (icon == NULL) + { + node->icon = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (node->emblem), + gdk_pixbuf_get_has_alpha (node->emblem), + gdk_pixbuf_get_bits_per_sample (node->emblem), + icon_size, + icon_size); + } + else + { + node->icon = gdk_pixbuf_copy (icon); + g_object_unref (icon); + } + + gdk_pixbuf_composite (node->emblem, node->icon, + icon_size - 10, icon_size - 10, 10, + 10, icon_size - 10, icon_size - 10, + 1, 1, GDK_INTERP_NEAREST, 255); + } + else + { + node->icon = icon; + } +} + +static void +model_recomposite_icon (GeditFileBrowserStore *tree_model, + GtkTreeIter *iter) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + model_recomposite_icon_real (tree_model, + (FileBrowserNode *)(iter->user_data), + NULL); +} + +static FileBrowserNode * +model_create_dummy_node (GeditFileBrowserStore *model, + FileBrowserNode *parent) +{ + FileBrowserNode *dummy; + + dummy = file_browser_node_new (NULL, parent); + dummy->name = g_strdup (_("(Empty)")); + dummy->markup = g_markup_escape_text (dummy->name, -1); + + dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY; + dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + return dummy; +} + +static FileBrowserNode * +model_add_dummy_node (GeditFileBrowserStore *model, + FileBrowserNode *parent) +{ + FileBrowserNode *dummy; + + dummy = model_create_dummy_node (model, parent); + + if (model_node_visibility (model, parent)) + dummy->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + model_add_node (model, dummy, parent); + + return dummy; +} + +static void +model_check_dummy (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + /* Hide the dummy child if needed */ + if (NODE_IS_DIR (node)) + { + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (node); + FileBrowserNode *dummy; + GtkTreeIter iter; + GtkTreePath *path; + guint flags; + + if (dir->children == NULL) + { + model_add_dummy_node (model, node); + return; + } + + dummy = (FileBrowserNode *)(dir->children->data); + + if (!NODE_IS_DUMMY (dummy)) + { + dummy = model_create_dummy_node (model, node); + dir->children = g_slist_prepend (dir->children, dummy); + } + + if (!model_node_visibility (model, node)) + { + dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + return; + } + + /* Temporarily set the node to invisible to check + for real children */ + flags = dummy->flags; + dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + if (!filter_tree_model_iter_has_child_real (model, node)) + { + dummy->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + if (FILE_IS_HIDDEN (flags)) + { + /* Was hidden, needs to be inserted */ + iter.user_data = dummy; + path = gedit_file_browser_store_get_path_real (model, dummy); + + row_inserted (model, &path, &iter); + gtk_tree_path_free (path); + } + } + else if (!FILE_IS_HIDDEN (flags)) + { + /* Was shown, needs to be removed */ + + /* To get the path we need to set it to visible temporarily */ + dummy->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + path = gedit_file_browser_store_get_path_real (model, dummy); + dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + row_deleted (model, dummy, path); + gtk_tree_path_free (path); + } + } +} + +static void +insert_node_sorted (GeditFileBrowserStore *model, + FileBrowserNode *child, + FileBrowserNode *parent) +{ + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent); + + if (model->priv->sort_func == NULL) + dir->children = g_slist_append (dir->children, child); + else + dir->children = g_slist_insert_sorted (dir->children, child, (GCompareFunc)(model->priv->sort_func)); +} + +static void +model_add_node (GeditFileBrowserStore *model, + FileBrowserNode *child, + FileBrowserNode *parent) +{ + /* Add child to parents children */ + insert_node_sorted (model, child, parent); + + if (model_node_visibility (model, parent) && + model_node_visibility (model, child)) + { + GtkTreePath *path = gedit_file_browser_store_get_path_real (model, child); + GtkTreeIter iter; + + iter.user_data = child; + + /* Emit row inserted */ + row_inserted (model, &path, &iter); + gtk_tree_path_free (path); + } + + model_check_dummy (model, parent); + model_check_dummy (model, child); +} + +static void +model_add_nodes_batch (GeditFileBrowserStore *model, + GSList *children, + FileBrowserNode *parent) +{ + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent); + GSList *sorted_children = g_slist_sort (children, (GCompareFunc)model->priv->sort_func); + GSList *child = sorted_children; + GSList *prev = NULL; + GSList *l = dir->children; + + model_check_dummy (model, parent); + + while (child) + { + FileBrowserNode *node = child->data; + GtkTreeIter iter; + GtkTreePath *path; + + /* Reached the end of the first list, just append the second */ + if (l == NULL) + { + dir->children = g_slist_concat (dir->children, child); + + for (l = child; l; l = l->next) + { + if (model_node_visibility (model, parent) && + model_node_visibility (model, l->data)) + { + iter.user_data = l->data; + path = gedit_file_browser_store_get_path_real (model, l->data); + + /* Emit row inserted */ + row_inserted (model, &path, &iter); + gtk_tree_path_free (path); + } + + model_check_dummy (model, l->data); + } + + break; + } + + if (model->priv->sort_func (l->data, node) > 0) + { + GSList *next_child; + + if (prev == NULL) + { + /* Prepend to the list */ + dir->children = g_slist_prepend (dir->children, child); + } + else + { + prev->next = child; + } + + next_child = child->next; + prev = child; + child->next = l; + child = next_child; + + if (model_node_visibility (model, parent) && + model_node_visibility (model, node)) + { + iter.user_data = node; + path = gedit_file_browser_store_get_path_real (model, node); + + /* Emit row inserted */ + row_inserted (model, &path, &iter); + gtk_tree_path_free (path); + } + + model_check_dummy (model, node); + + /* Try again at the same l position with the next child */ + } + else + { + /* Move to the next item in the list */ + prev = l; + l = l->next; + } + } +} + +static gchar const * +backup_content_type (GFileInfo *info) +{ + gchar const *content; + + if (!g_file_info_get_is_backup (info)) + return NULL; + + content = g_file_info_get_content_type (info); + + if (!content || g_content_type_equals (content, "application/x-trash")) + return "text/plain"; + + return content; +} + +static gboolean +content_type_is_text (gchar const *content_type) +{ +#ifdef G_OS_WIN32 + gchar *mime; + gboolean ret; +#endif + + if (!content_type || g_content_type_is_unknown (content_type)) + return TRUE; + +#ifndef G_OS_WIN32 + return (g_content_type_is_a (content_type, "text/plain") || + g_content_type_equals (content_type, "application/x-zerosize")); +#else + if (g_content_type_is_a (content_type, "text")) + return TRUE; + + /* This covers a rare case in which on Windows the PerceivedType is + not set to "text" but the Content Type is set to text/plain */ + mime = g_content_type_get_mime_type (content_type); + ret = g_strcmp0 (mime, "text/plain") == 0; + g_free (mime); + + return ret; +#endif +} + +static void +file_browser_node_set_from_info (GeditFileBrowserStore *model, + FileBrowserNode *node, + GFileInfo *info, + gboolean isadded) +{ + gchar const *content; + gboolean free_info = FALSE; + GtkTreePath *path; + gchar *uri; + GError *error = NULL; + + if (info == NULL) + { + info = g_file_query_info (node->file, + STANDARD_ATTRIBUTE_TYPES, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + + if (!info) + { + if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND)) + { + uri = g_file_get_uri (node->file); + g_warning ("Could not get info for %s: %s", uri, error->message); + g_free (uri); + } + + g_error_free (error); + return; + } + + free_info = TRUE; + } + + if (g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info)) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) + { + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY; + } + else + { + if (!(content = backup_content_type (info))) + content = g_file_info_get_content_type (info); + + if (content_type_is_text (content)) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT; + } + + model_recomposite_icon_real (model, node, info); + + if (free_info) + g_object_unref (info); + + if (isadded) + { + path = gedit_file_browser_store_get_path_real (model, node); + model_refilter_node (model, node, &path); + gtk_tree_path_free (path); + + model_check_dummy (model, node->parent); + } + else + { + model_node_update_visibility (model, node); + } +} + +static FileBrowserNode * +node_list_contains_file (GSList *children, + GFile *file) +{ + for (GSList *item = children; item; item = item->next) + { + FileBrowserNode *node = (FileBrowserNode *)(item->data); + + if (node->file != NULL && g_file_equal (node->file, file)) + return node; + } + + return NULL; +} + +static FileBrowserNode * +model_add_node_from_file (GeditFileBrowserStore *model, + FileBrowserNode *parent, + GFile *file, + GFileInfo *info) +{ + FileBrowserNode *node; + gboolean free_info = FALSE; + GError *error = NULL; + + if ((node = node_list_contains_file (FILE_BROWSER_NODE_DIR (parent)->children, file)) == NULL) + { + if (info == NULL) + { + info = g_file_query_info (file, + STANDARD_ATTRIBUTE_TYPES, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + free_info = TRUE; + } + + if (!info) + { + g_warning ("Error querying file info: %s", error->message); + g_error_free (error); + + /* FIXME: What to do now then... */ + node = file_browser_node_new (file, parent); + } + else if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) + { + node = file_browser_node_dir_new (model, file, parent); + } + else + { + node = file_browser_node_new (file, parent); + } + + file_browser_node_set_from_info (model, node, info, FALSE); + model_add_node (model, node, parent); + + if (info && free_info) + g_object_unref (info); + } + + return node; +} + +/* We pass in a copy of the list of parent->children so that we do + * not have to check if a file already exists among the ones we just + * added */ +static void +model_add_nodes_from_files (GeditFileBrowserStore *model, + FileBrowserNode *parent, + GSList *original_children, + GList *files) +{ + GSList *nodes = NULL; + + for (GList *item = files; item; item = item->next) + { + GFileInfo *info = G_FILE_INFO (item->data); + GFileType type = g_file_info_get_file_type (info); + gchar const *name; + GFile *file; + FileBrowserNode *node; + + /* Skip all non regular, non directory files */ + if (type != G_FILE_TYPE_REGULAR && + type != G_FILE_TYPE_DIRECTORY && + type != G_FILE_TYPE_SYMBOLIC_LINK) + { + g_object_unref (info); + continue; + } + + name = g_file_info_get_name (info); + + /* Skip '.' and '..' directories */ + if (type == G_FILE_TYPE_DIRECTORY && + (strcmp (name, ".") == 0 || + strcmp (name, "..") == 0)) + { + g_object_unref (info); + continue; + } + + file = g_file_get_child (parent->file, name); + if (!(node = node_list_contains_file (original_children, file))) + { + if (type == G_FILE_TYPE_DIRECTORY) + node = file_browser_node_dir_new (model, file, parent); + else + node = file_browser_node_new (file, parent); + + file_browser_node_set_from_info (model, node, info, FALSE); + + nodes = g_slist_prepend (nodes, node); + } + + g_object_unref (file); + g_object_unref (info); + } + + if (nodes) + model_add_nodes_batch (model, nodes, parent); +} + +static FileBrowserNode * +model_add_node_from_dir (GeditFileBrowserStore *model, + FileBrowserNode *parent, + GFile *file) +{ + FileBrowserNode *node; + + /* Check if it already exists */ + if ((node = node_list_contains_file (FILE_BROWSER_NODE_DIR (parent)->children, file)) == NULL) + { + node = file_browser_node_dir_new (model, file, parent); + file_browser_node_set_from_info (model, node, NULL, FALSE); + + if (node->name == NULL) + file_browser_node_set_name (node); + + node->icon_name = g_strdup ("folder-symbolic"); + + model_add_node (model, node, parent); + } + + return node; +} + +static void +on_directory_monitor_event (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + FileBrowserNode *parent) +{ + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent); + FileBrowserNode *node; + + switch (event_type) + { + case G_FILE_MONITOR_EVENT_DELETED: + node = node_list_contains_file (dir->children, file); + + if (node != NULL) + model_remove_node (dir->model, node, NULL, TRUE); + break; + case G_FILE_MONITOR_EVENT_CREATED: + if (g_file_query_exists (file, NULL)) + model_add_node_from_file (dir->model, parent, file, NULL); + + break; + default: + break; + } +} + +static void +async_node_free (AsyncNode *async) +{ + g_object_unref (async->cancellable); + g_slist_free (async->original_children); + g_slice_free (AsyncNode, async); +} + +static void +model_iterate_next_files_cb (GFileEnumerator *enumerator, + GAsyncResult *result, + AsyncNode *async) +{ + GError *error = NULL; + GList *files = g_file_enumerator_next_files_finish (enumerator, result, &error); + FileBrowserNodeDir *dir = async->dir; + FileBrowserNode *parent = (FileBrowserNode *)dir; + + if (files == NULL) + { + g_file_enumerator_close (enumerator, NULL, NULL); + g_object_unref (enumerator); + async_node_free (async); + + if (!error) + { + /* We're done loading */ + g_object_unref (dir->cancellable); + dir->cancellable = NULL; + +/* + * FIXME: This is temporarly, it is a bug in gio: + * http://bugzilla.gnome.org/show_bug.cgi?id=565924 + */ +#ifndef G_OS_WIN32 + if (g_file_is_native (parent->file) && dir->monitor == NULL) + { + dir->monitor = g_file_monitor_directory (parent->file, + G_FILE_MONITOR_NONE, + NULL, + NULL); + if (dir->monitor != NULL) + { + g_signal_connect (dir->monitor, + "changed", + G_CALLBACK (on_directory_monitor_event), + parent); + } + } +#endif + + model_check_dummy (dir->model, parent); + model_end_loading (dir->model, parent); + } + else + { + /* Simply return if we were cancelled */ + if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED) + return; + + /* Otherwise handle the error appropriately */ + g_signal_emit (dir->model, + model_signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY, + error->message); + + file_browser_node_unload (dir->model, (FileBrowserNode *)parent, TRUE); + g_error_free (error); + } + } + else if (g_cancellable_is_cancelled (async->cancellable)) + { + /* Check cancel state manually */ + g_file_enumerator_close (enumerator, NULL, NULL); + g_object_unref (enumerator); + async_node_free (async); + } + else + { + model_add_nodes_from_files (dir->model, parent, async->original_children, files); + + g_list_free (files); + next_files_async (enumerator, async); + } +} + +static void +next_files_async (GFileEnumerator *enumerator, + AsyncNode *async) +{ + g_file_enumerator_next_files_async (enumerator, + DIRECTORY_LOAD_ITEMS_PER_CALLBACK, + G_PRIORITY_DEFAULT, + async->cancellable, + (GAsyncReadyCallback)model_iterate_next_files_cb, + async); +} + +static void +model_iterate_children_cb (GFile *file, + GAsyncResult *result, + AsyncNode *async) +{ + GError *error = NULL; + GFileEnumerator *enumerator; + + if (g_cancellable_is_cancelled (async->cancellable)) + { + async_node_free (async); + return; + } + + if (!(enumerator = g_file_enumerate_children_finish (file, result, &error))) + { + /* Simply return if we were cancelled or if the dir is not there */ + FileBrowserNodeDir *dir = async->dir; + + /* Otherwise handle the error appropriately */ + g_signal_emit (dir->model, + model_signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY, + error->message); + + file_browser_node_unload (dir->model, (FileBrowserNode *)dir, TRUE); + g_error_free (error); + async_node_free (async); + } + else + { + next_files_async (enumerator, async); + } +} + +static void +model_load_directory (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + FileBrowserNodeDir *dir; + AsyncNode *async; + + g_return_if_fail (NODE_IS_DIR (node)); + + dir = FILE_BROWSER_NODE_DIR (node); + + /* Cancel a previous load */ + if (dir->cancellable != NULL) + file_browser_node_unload (dir->model, node, TRUE); + + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_LOADED; + model_begin_loading (model, node); + + dir->cancellable = g_cancellable_new (); + + async = g_slice_new (AsyncNode); + async->dir = dir; + async->cancellable = g_object_ref (dir->cancellable); + async->original_children = g_slist_copy (dir->children); + + /* Start loading async */ + g_file_enumerate_children_async (node->file, + STANDARD_ATTRIBUTE_TYPES, + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, + async->cancellable, + (GAsyncReadyCallback)model_iterate_children_cb, + async); +} + +static GList * +get_parent_files (GeditFileBrowserStore *model, + GFile *file) +{ + GList *result = NULL; + + result = g_list_prepend (result, g_object_ref (file)); + + while ((file = g_file_get_parent (file))) + { + if (g_file_equal (file, model->priv->root->file)) + { + g_object_unref (file); + break; + } + + result = g_list_prepend (result, file); + } + + return result; +} + +static void +model_fill (GeditFileBrowserStore *model, + FileBrowserNode *node, + GtkTreePath **path) +{ + gboolean free_path = FALSE; + GtkTreeIter iter = {0,}; + GSList *item; + FileBrowserNode *child; + + if (node == NULL) + { + node = model->priv->virtual_root; + *path = gtk_tree_path_new (); + free_path = TRUE; + } + + if (*path == NULL) + { + *path = gedit_file_browser_store_get_path_real (model, node); + free_path = TRUE; + } + + if (!model_node_visibility (model, node)) + { + if (free_path) + gtk_tree_path_free (*path); + + return; + } + + if (node != model->priv->virtual_root) + { + /* Insert node */ + iter.user_data = node; + row_inserted(model, path, &iter); + } + + if (NODE_IS_DIR (node)) + { + /* Go to the first child */ + gtk_tree_path_down (*path); + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + { + child = (FileBrowserNode *) (item->data); + + if (model_node_visibility (model, child)) + { + model_fill (model, child, path); + + /* Increase path for next child */ + gtk_tree_path_next (*path); + } + } + + /* Move back up to node path */ + gtk_tree_path_up (*path); + } + + model_check_dummy (model, node); + + if (free_path) + gtk_tree_path_free (*path); +} + +static void +set_virtual_root_from_node (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + FileBrowserNode *prev = node; + FileBrowserNode *next = prev->parent; + FileBrowserNode *check; + FileBrowserNodeDir *dir; + GSList *copy; + GtkTreePath *empty = NULL; + + /* Free all the nodes below that we don't need in cache */ + while (prev != model->priv->root) + { + dir = FILE_BROWSER_NODE_DIR (next); + copy = g_slist_copy (dir->children); + + for (GSList *item = copy; item; item = item->next) + { + check = (FileBrowserNode *)(item->data); + + if (prev == node) + { + /* Only free the children, keeping this depth in cache */ + if (check != node) + { + file_browser_node_free_children (model, check); + file_browser_node_unload (model, check, FALSE); + } + } + else if (check != prev) + { + /* Only free when the node is not in the chain */ + dir->children = g_slist_remove (dir->children, check); + file_browser_node_free (model, check); + } + } + + if (prev != node) + file_browser_node_unload (model, next, FALSE); + + g_slist_free (copy); + prev = next; + next = prev->parent; + } + + /* Free all the nodes up that we don't need in cache */ + for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + { + check = (FileBrowserNode *)(item->data); + + if (NODE_IS_DIR (check)) + { + for (copy = FILE_BROWSER_NODE_DIR (check)->children; copy; copy = copy->next) + { + file_browser_node_free_children (model, (FileBrowserNode*) (copy->data)); + file_browser_node_unload (model, (FileBrowserNode*) (copy->data), FALSE); + } + } + else if (NODE_IS_DUMMY (check)) + { + check->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + } + } + + /* Now finally, set the virtual root, and load it up! */ + model->priv->virtual_root = node; + + /* Notify that the virtual-root has changed before loading up new nodes so that the + "root_changed" signal can be emitted before any "inserted" signals */ + g_object_notify (G_OBJECT (model), "virtual-root"); + + model_fill (model, NULL, &empty); + + if (!NODE_LOADED (node)) + model_load_directory (model, node); +} + +static void +set_virtual_root_from_file (GeditFileBrowserStore *model, + GFile *file) +{ + GList *files; + FileBrowserNode *parent; + GFile *check; + + /* Always clear the model before altering the nodes */ + model_clear (model, FALSE); + + /* Create the node path, get all the uri's */ + files = get_parent_files (model, file); + parent = model->priv->root; + + for (GList *item = files; item; item = item->next) + { + check = G_FILE (item->data); + + parent = model_add_node_from_dir (model, parent, check); + g_object_unref (check); + } + + g_list_free (files); + set_virtual_root_from_node (model, parent); +} + +static FileBrowserNode * +model_find_node_children (GeditFileBrowserStore *model, + FileBrowserNode *parent, + GFile *file) +{ + FileBrowserNodeDir *dir; + FileBrowserNode *child; + FileBrowserNode *result; + + if (!NODE_IS_DIR (parent)) + return NULL; + + dir = FILE_BROWSER_NODE_DIR (parent); + + for (GSList *children = dir->children; children; children = children->next) + { + child = (FileBrowserNode *)(children->data); + + result = model_find_node (model, child, file); + + if (result) + return result; + } + + return NULL; +} + +static FileBrowserNode * +model_find_node (GeditFileBrowserStore *model, + FileBrowserNode *node, + GFile *file) +{ + if (node == NULL) + node = model->priv->root; + + if (node->file && g_file_equal (node->file, file)) + return node; + + if (NODE_IS_DIR (node) && g_file_has_prefix (file, node->file)) + return model_find_node_children (model, node, file); + + return NULL; +} + +static GQuark +gedit_file_browser_store_error_quark (void) +{ + static GQuark quark = 0; + + if (G_UNLIKELY (quark == 0)) + quark = g_quark_from_string ("gedit_file_browser_store_error"); + + return quark; +} + +static GFile * +unique_new_name (GFile *directory, + gchar const *name) +{ + GFile *newuri = NULL; + guint num = 0; + gchar *newname; + + while (newuri == NULL || g_file_query_exists (newuri, NULL)) + { + if (newuri != NULL) + g_object_unref (newuri); + + if (num == 0) + newname = g_strdup (name); + else + newname = g_strdup_printf ("%s(%d)", name, num); + + newuri = g_file_get_child (directory, newname); + g_free (newname); + + ++num; + } + + return newuri; +} + +static GeditFileBrowserStoreResult +model_root_mounted (GeditFileBrowserStore *model, + GFile *virtual_root) +{ + model_check_dummy (model, model->priv->root); + g_object_notify (G_OBJECT (model), "root"); + + if (virtual_root != NULL) + { + return gedit_file_browser_store_set_virtual_root_from_location + (model, virtual_root); + } + else + { + set_virtual_root_from_node (model, model->priv->root); + } + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +static void +handle_root_error (GeditFileBrowserStore *model, + GError *error) +{ + FileBrowserNode *root; + + g_signal_emit (model, + model_signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_SET_ROOT, + error->message); + + /* Set the virtual root to the root */ + root = model->priv->root; + model->priv->virtual_root = root; + + /* Set the root to be loaded */ + root->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_LOADED; + + /* Check the dummy */ + model_check_dummy (model, root); + + g_object_notify (G_OBJECT (model), "root"); + g_object_notify (G_OBJECT (model), "virtual-root"); +} + +static void +mount_cb (GFile *file, + GAsyncResult *res, + MountInfo *mount_info) +{ + gboolean mounted; + GError *error = NULL; + GeditFileBrowserStore *model = mount_info->model; + + mounted = g_file_mount_enclosing_volume_finish (file, res, &error); + + if (mount_info->model) + { + model->priv->mount_info = NULL; + model_end_loading (model, model->priv->root); + } + + if (!mount_info->model || g_cancellable_is_cancelled (mount_info->cancellable)) + { + /* Reset because it might be reused? */ + g_cancellable_reset (mount_info->cancellable); + } + else if (mounted) + { + model_root_mounted (model, mount_info->virtual_root); + } + else if (error->code != G_IO_ERROR_CANCELLED) + { + handle_root_error (model, error); + } + + if (error) + g_error_free (error); + + g_object_unref (mount_info->operation); + g_object_unref (mount_info->cancellable); + + if (mount_info->virtual_root) + g_object_unref (mount_info->virtual_root); + + g_slice_free (MountInfo, mount_info); +} + +static GeditFileBrowserStoreResult +model_mount_root (GeditFileBrowserStore *model, + GFile *virtual_root) +{ + GFileInfo *info; + GError *error = NULL; + MountInfo *mount_info; + + info = g_file_query_info (model->priv->root->file, + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + + if (!info) + { + if (error->code == G_IO_ERROR_NOT_MOUNTED) + { + /* Try to mount it */ + FILE_BROWSER_NODE_DIR (model->priv->root)->cancellable = g_cancellable_new (); + + mount_info = g_slice_new (MountInfo); + mount_info->model = model; + mount_info->virtual_root = g_file_dup (virtual_root); + + /* FIXME: we should be setting the correct window */ + mount_info->operation = gtk_mount_operation_new (NULL); + mount_info->cancellable = g_object_ref (FILE_BROWSER_NODE_DIR (model->priv->root)->cancellable); + + model_begin_loading (model, model->priv->root); + g_file_mount_enclosing_volume (model->priv->root->file, + G_MOUNT_MOUNT_NONE, + mount_info->operation, + mount_info->cancellable, + (GAsyncReadyCallback)mount_cb, + mount_info); + + model->priv->mount_info = mount_info; + return GEDIT_FILE_BROWSER_STORE_RESULT_MOUNTING; + } + else + { + handle_root_error (model, error); + } + + g_error_free (error); + } + else + { + g_object_unref (info); + + return model_root_mounted (model, virtual_root); + } + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +/* Public */ +GeditFileBrowserStore * +gedit_file_browser_store_new (GFile *root) +{ + return GEDIT_FILE_BROWSER_STORE (g_object_new (GEDIT_TYPE_FILE_BROWSER_STORE, + "root", root, + NULL)); +} + +void +gedit_file_browser_store_set_value (GeditFileBrowserStore *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value) +{ + gpointer data; + FileBrowserNode *node; + GtkTreePath *path; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + node = (FileBrowserNode *)(iter->user_data); + + if (column == GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP) + { + g_return_if_fail (G_VALUE_HOLDS_STRING (value)); + + data = g_value_dup_string (value); + + if (!data) + data = g_strdup (node->name); + + g_free (node->markup); + node->markup = data; + } + else if (column == GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM) + { + g_return_if_fail (G_VALUE_HOLDS_OBJECT (value)); + + data = g_value_get_object (value); + + g_return_if_fail (GDK_IS_PIXBUF (data) || data == NULL); + + if (node->emblem) + g_object_unref (node->emblem); + + if (data) + node->emblem = g_object_ref (GDK_PIXBUF (data)); + else + node->emblem = NULL; + + model_recomposite_icon (tree_model, iter); + } + else + { + g_return_if_fail (column == GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP || + column == GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM); + } + + if (model_node_visibility (tree_model, node)) + { + path = gedit_file_browser_store_get_path (GTK_TREE_MODEL (tree_model), iter); + row_changed (tree_model, &path, iter); + gtk_tree_path_free (path); + } +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root (GeditFileBrowserStore *model, + GtkTreeIter *iter) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + g_return_val_if_fail (iter != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + g_return_val_if_fail (iter->user_data != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + model_clear (model, FALSE); + set_virtual_root_from_node (model, (FileBrowserNode *)(iter->user_data)); + + return TRUE; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_from_location (GeditFileBrowserStore *model, + GFile *root) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (root == NULL) + { + gchar *uri = g_file_get_uri (root); + + g_warning ("Invalid uri (%s)", uri); + g_free (uri); + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + /* Check if uri is already the virtual root */ + if (model->priv->virtual_root && g_file_equal (model->priv->virtual_root->file, root)) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + /* Check if uri is the root itself */ + if (g_file_equal (model->priv->root->file, root)) + { + /* Always clear the model before altering the nodes */ + model_clear (model, FALSE); + set_virtual_root_from_node (model, model->priv->root); + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; + } + + if (!g_file_has_prefix (root, model->priv->root->file)) + { + gchar *str = g_file_get_parse_name (model->priv->root->file); + gchar *str1 = g_file_get_parse_name (root); + + g_warning ("Virtual root (%s) is not below actual root (%s)", str1, str); + + g_free (str); + g_free (str1); + + return GEDIT_FILE_BROWSER_STORE_RESULT_ERROR; + } + + set_virtual_root_from_file (model, root); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_top (GeditFileBrowserStore *model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (model->priv->virtual_root == model->priv->root) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + model_clear (model, FALSE); + set_virtual_root_from_node (model, model->priv->root); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_up (GeditFileBrowserStore *model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (model->priv->virtual_root == model->priv->root) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + model_clear (model, FALSE); + set_virtual_root_from_node (model, model->priv->virtual_root->parent); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +gboolean +gedit_file_browser_store_get_iter_virtual_root (GeditFileBrowserStore *model, + GtkTreeIter *iter) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + if (model->priv->virtual_root == NULL) + return FALSE; + + iter->user_data = model->priv->virtual_root; + return TRUE; +} + +gboolean +gedit_file_browser_store_get_iter_root (GeditFileBrowserStore *model, + GtkTreeIter *iter) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + if (model->priv->root == NULL) + return FALSE; + + iter->user_data = model->priv->root; + return TRUE; +} + +gboolean +gedit_file_browser_store_iter_equal (GeditFileBrowserStore *model, + GtkTreeIter *iter1, + GtkTreeIter *iter2) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (iter1 != NULL, FALSE); + g_return_val_if_fail (iter2 != NULL, FALSE); + g_return_val_if_fail (iter1->user_data != NULL, FALSE); + g_return_val_if_fail (iter2->user_data != NULL, FALSE); + + return (iter1->user_data == iter2->user_data); +} + +void +gedit_file_browser_store_cancel_mount_operation (GeditFileBrowserStore *store) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (store)); + + cancel_mount_operation (store); +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_root_and_virtual_root (GeditFileBrowserStore *model, + GFile *root, + GFile *virtual_root) +{ + FileBrowserNode *node; + gboolean equal = FALSE; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (root == NULL && model->priv->root == NULL) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + if (root != NULL && model->priv->root != NULL) + { + equal = g_file_equal (root, model->priv->root->file); + + if (equal && virtual_root == NULL) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + if (virtual_root) + { + if (equal && g_file_equal (virtual_root, model->priv->virtual_root->file)) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + /* Make sure to cancel any previous mount operations */ + cancel_mount_operation (model); + + /* Always clear the model before altering the nodes */ + model_clear (model, TRUE); + file_browser_node_free (model, model->priv->root); + + model->priv->root = NULL; + model->priv->virtual_root = NULL; + + if (root != NULL) + { + /* Create the root node */ + node = file_browser_node_dir_new (model, root, NULL); + + model->priv->root = node; + return model_mount_root (model, virtual_root); + } + else + { + g_object_notify (G_OBJECT (model), "root"); + g_object_notify (G_OBJECT (model), "virtual-root"); + } + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_root (GeditFileBrowserStore *model, + GFile *root) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + return gedit_file_browser_store_set_root_and_virtual_root (model, root, NULL); +} + +GFile * +gedit_file_browser_store_get_root (GeditFileBrowserStore *model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), NULL); + + if (model->priv->root == NULL || model->priv->root->file == NULL) + return NULL; + else + return g_file_dup (model->priv->root->file); +} + +GFile * +gedit_file_browser_store_get_virtual_root (GeditFileBrowserStore *model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), NULL); + + if (model->priv->virtual_root == NULL || model->priv->virtual_root->file == NULL) + return NULL; + else + return g_file_dup (model->priv->virtual_root->file); +} + +void +_gedit_file_browser_store_iter_expanded (GeditFileBrowserStore *model, + GtkTreeIter *iter) +{ + FileBrowserNode *node; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + node = (FileBrowserNode *)(iter->user_data); + + if (NODE_IS_DIR (node) && !NODE_LOADED (node)) + { + /* Load it now */ + model_load_directory (model, node); + } +} + +void +_gedit_file_browser_store_iter_collapsed (GeditFileBrowserStore *model, + GtkTreeIter *iter) +{ + FileBrowserNode *node; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + node = (FileBrowserNode *)(iter->user_data); + + if (NODE_IS_DIR (node) && NODE_LOADED (node)) + { + /* Unload children of the children, keeping 1 depth in cache */ + + for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + { + node = (FileBrowserNode *)(item->data); + + if (NODE_IS_DIR (node) && NODE_LOADED (node)) + { + file_browser_node_unload (model, node, TRUE); + model_check_dummy (model, node); + } + } + } +} + +GeditFileBrowserStoreFilterMode +gedit_file_browser_store_get_filter_mode (GeditFileBrowserStore *model) +{ + return model->priv->filter_mode; +} + +void +gedit_file_browser_store_set_filter_mode (GeditFileBrowserStore *model, + GeditFileBrowserStoreFilterMode mode) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + + if (model->priv->filter_mode == mode) + return; + + model->priv->filter_mode = mode; + model_refilter (model); + + g_object_notify (G_OBJECT (model), "filter-mode"); +} + +void +gedit_file_browser_store_set_filter_func (GeditFileBrowserStore *model, + GeditFileBrowserStoreFilterFunc func, + gpointer user_data) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + + model->priv->filter_func = func; + model->priv->filter_user_data = user_data; + model_refilter (model); +} + +const gchar * const * +gedit_file_browser_store_get_binary_patterns (GeditFileBrowserStore *model) +{ + return (const gchar * const *)model->priv->binary_patterns; +} + +void +gedit_file_browser_store_set_binary_patterns (GeditFileBrowserStore *model, + const gchar **binary_patterns) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + + if (model->priv->binary_patterns != NULL) + { + g_strfreev (model->priv->binary_patterns); + g_ptr_array_unref (model->priv->binary_pattern_specs); + } + + model->priv->binary_patterns = g_strdupv ((gchar **)binary_patterns); + + if (binary_patterns == NULL) + { + model->priv->binary_pattern_specs = NULL; + } + else + { + gssize n_patterns = g_strv_length ((gchar **) binary_patterns); + + model->priv->binary_pattern_specs = g_ptr_array_sized_new (n_patterns); + g_ptr_array_set_free_func (model->priv->binary_pattern_specs, (GDestroyNotify) g_pattern_spec_free); + + for (guint i = 0; binary_patterns[i] != NULL; ++i) + g_ptr_array_add (model->priv->binary_pattern_specs, g_pattern_spec_new (binary_patterns[i])); + } + + model_refilter (model); + + g_object_notify (G_OBJECT (model), "binary-patterns"); +} + +void +gedit_file_browser_store_refilter (GeditFileBrowserStore *model) +{ + model_refilter (model); +} + +GeditFileBrowserStoreFilterMode +gedit_file_browser_store_filter_mode_get_default (void) +{ + return GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN; +} + +void +gedit_file_browser_store_refresh (GeditFileBrowserStore *model) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + + if (model->priv->root == NULL || model->priv->virtual_root == NULL) + return; + + /* Clear the model */ + g_signal_emit (model, model_signals[BEGIN_REFRESH], 0); + file_browser_node_unload (model, model->priv->virtual_root, TRUE); + model_load_directory (model, model->priv->virtual_root); + g_signal_emit (model, model_signals[END_REFRESH], 0); +} + +static void +reparent_node (FileBrowserNode *node, + gboolean reparent) +{ + if (!node->file) + return; + + if (reparent) + { + GFile *parent = node->parent->file; + gchar *base = g_file_get_basename (node->file); + + g_object_unref (node->file); + + node->file = g_file_get_child (parent, base); + g_free (base); + } + + if (NODE_IS_DIR (node)) + { + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (node); + + for (GSList *child = dir->children; child; child = child->next) + reparent_node ((FileBrowserNode *)child->data, TRUE); + } +} + +gboolean +gedit_file_browser_store_rename (GeditFileBrowserStore *model, + GtkTreeIter *iter, + const gchar *new_name, + GError **error) +{ + FileBrowserNode *node; + GFile *file; + GFile *parent; + GFile *previous; + GError *err = NULL; + GtkTreePath *path; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (iter->user_data != NULL, FALSE); + + node = (FileBrowserNode *)(iter->user_data); + + parent = g_file_get_parent (node->file); + g_return_val_if_fail (parent != NULL, FALSE); + + file = g_file_get_child (parent, new_name); + g_object_unref (parent); + + if (g_file_equal (node->file, file)) + { + g_object_unref (file); + return TRUE; + } + + if (g_file_move (node->file, file, G_FILE_COPY_NONE, NULL, NULL, NULL, &err)) + { + previous = node->file; + node->file = file; + + /* This makes sure the actual info for the node is requeried */ + file_browser_node_set_name (node); + file_browser_node_set_from_info (model, node, NULL, TRUE); + + reparent_node (node, FALSE); + + if (model_node_visibility (model, node)) + { + path = gedit_file_browser_store_get_path_real (model, node); + row_changed (model, &path, iter); + gtk_tree_path_free (path); + + /* Reorder this item */ + model_resort_node (model, node); + } + else + { + g_object_unref (previous); + + if (error != NULL) + { + *error = g_error_new_literal (gedit_file_browser_store_error_quark (), + GEDIT_FILE_BROWSER_ERROR_RENAME, + _("The renamed file is currently filtered out. " + "You need to adjust your filter settings to " + "make the file visible")); + } + + return FALSE; + } + + g_signal_emit (model, model_signals[RENAME], 0, previous, node->file); + + g_object_unref (previous); + + return TRUE; + } + else + { + g_object_unref (file); + + if (err) + { + if (error != NULL) + { + *error = g_error_new_literal (gedit_file_browser_store_error_quark (), + GEDIT_FILE_BROWSER_ERROR_RENAME, + err->message); + } + + g_error_free (err); + } + + return FALSE; + } +} + +static void +async_data_free (AsyncData *data) +{ + g_object_unref (data->cancellable); + g_list_free_full (data->files, g_object_unref); + + if (!data->removed) + data->model->priv->async_handles = g_slist_remove (data->model->priv->async_handles, data); + + g_slice_free (AsyncData, data); +} + +static gboolean +emit_no_trash (AsyncData *data) +{ + /* Emit the no trash error */ + gboolean ret; + + g_signal_emit (data->model, model_signals[NO_TRASH], 0, data->files, &ret); + + return ret; +} + +static void +delete_file_finished (GFile *file, + GAsyncResult *res, + AsyncData *data) +{ + GError *error = NULL; + gboolean ok; + + if (data->trash) + ok = g_file_trash_finish (file, res, &error); + else + ok = g_file_delete_finish (file, res, &error); + + if (ok) + { + /* Remove the file from the model */ + FileBrowserNode *node = model_find_node (data->model, NULL, file); + + if (node != NULL) + model_remove_node (data->model, node, NULL, TRUE); + + /* Process the next file */ + data->iter = data->iter->next; + } + else if (!ok && error != NULL) + { + gint code = error->code; + g_error_free (error); + + if (data->trash && code == G_IO_ERROR_NOT_SUPPORTED) + { + /* Trash is not supported on this system. Ask the user + * if he wants to delete completely the files instead. + */ + if (emit_no_trash (data)) + { + /* Changes this into a delete job */ + data->trash = FALSE; + data->iter = data->files; + } + else + { + /* End the job */ + async_data_free (data); + return; + } + } + else if (code == G_IO_ERROR_CANCELLED) + { + /* Job has been cancelled, end the job */ + async_data_free (data); + return; + } + } + + /* Continue the job */ + delete_files (data); +} + +static void +delete_files (AsyncData *data) +{ + GFile *file; + + /* Check if our job is done */ + if (data->iter == NULL) + { + async_data_free (data); + return; + } + + file = G_FILE (data->iter->data); + + if (data->trash) + { + g_file_trash_async (file, + G_PRIORITY_DEFAULT, + data->cancellable, + (GAsyncReadyCallback)delete_file_finished, + data); + } + else + { + g_file_delete_async (file, + G_PRIORITY_DEFAULT, + data->cancellable, + (GAsyncReadyCallback)delete_file_finished, + data); + } +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_delete_all (GeditFileBrowserStore *model, + GList *rows, + gboolean trash) +{ + FileBrowserNode *node; + AsyncData *data; + GList *files = NULL; + GList *row; + GtkTreeIter iter; + GtkTreePath *prev = NULL; + GtkTreePath *path; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (rows == NULL) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + /* First we sort the paths so that we can later on remove any + files/directories that are actually subfiles/directories of + a directory that's also deleted */ + rows = g_list_sort (g_list_copy (rows), (GCompareFunc)gtk_tree_path_compare); + + for (row = rows; row; row = row->next) + { + path = (GtkTreePath *)(row->data); + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) + continue; + + /* Skip if the current path is actually a descendant of the + previous path */ + if (prev != NULL && gtk_tree_path_is_descendant (path, prev)) + continue; + + prev = path; + node = (FileBrowserNode *)(iter.user_data); + files = g_list_prepend (files, g_object_ref (node->file)); + } + + data = g_slice_new (AsyncData); + + data->model = model; + data->cancellable = g_cancellable_new (); + data->files = files; + data->trash = trash; + data->iter = files; + data->removed = FALSE; + + model->priv->async_handles = g_slist_prepend (model->priv->async_handles, data); + + delete_files (data); + g_list_free (rows); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_delete (GeditFileBrowserStore *model, + GtkTreeIter *iter, + gboolean trash) +{ + FileBrowserNode *node; + GList *rows = NULL; + GeditFileBrowserStoreResult result; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + g_return_val_if_fail (iter != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + g_return_val_if_fail (iter->user_data != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + node = (FileBrowserNode *)(iter->user_data); + + if (NODE_IS_DUMMY (node)) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + rows = g_list_append(NULL, gedit_file_browser_store_get_path_real (model, node)); + result = gedit_file_browser_store_delete_all (model, rows, trash); + + g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + return result; +} + +gboolean +gedit_file_browser_store_new_file (GeditFileBrowserStore *model, + GtkTreeIter *parent, + GtkTreeIter *iter) +{ + GFile *file; + GFileOutputStream *stream; + FileBrowserNodeDir *parent_node; + gboolean result = FALSE; + FileBrowserNode *node; + GError *error = NULL; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (parent != NULL, FALSE); + g_return_val_if_fail (parent->user_data != NULL, FALSE); + g_return_val_if_fail (NODE_IS_DIR ((FileBrowserNode *) (parent->user_data)), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + parent_node = FILE_BROWSER_NODE_DIR (parent->user_data); + /* Translators: This is the default name of new files created by the file browser pane. */ + file = unique_new_name (((FileBrowserNode *) parent_node)->file, _("Untitled File")); + + stream = g_file_create (file, G_FILE_CREATE_NONE, NULL, &error); + + if (!stream) + { + g_signal_emit (model, model_signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_NEW_FILE, + error->message); + g_error_free (error); + } + else + { + g_object_unref (stream); + node = model_add_node_from_file (model, + (FileBrowserNode *)parent_node, + file, + NULL); + + if (model_node_visibility (model, node)) + { + iter->user_data = node; + result = TRUE; + } + else + { + g_signal_emit (model, model_signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_NEW_FILE, + _("The new file is currently filtered out. " + "You need to adjust your filter " + "settings to make the file visible")); + } + } + + g_object_unref (file); + return result; +} + +gboolean +gedit_file_browser_store_new_directory (GeditFileBrowserStore *model, + GtkTreeIter *parent, + GtkTreeIter *iter) +{ + GFile *file; + FileBrowserNodeDir *parent_node; + GError *error = NULL; + FileBrowserNode *node; + gboolean result = FALSE; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (parent != NULL, FALSE); + g_return_val_if_fail (parent->user_data != NULL, FALSE); + g_return_val_if_fail (NODE_IS_DIR ((FileBrowserNode *)(parent->user_data)), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + parent_node = FILE_BROWSER_NODE_DIR (parent->user_data); + /* Translators: This is the default name of new directories created by the file browser pane. */ + file = unique_new_name (((FileBrowserNode *) parent_node)->file, _("Untitled Folder")); + + if (!g_file_make_directory (file, NULL, &error)) + { + g_signal_emit (model, model_signals[ERROR], 0, GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY, error->message); + g_error_free (error); + } + else + { + node = model_add_node_from_file (model, + (FileBrowserNode *)parent_node, + file, + NULL); + + if (model_node_visibility (model, node)) + { + iter->user_data = node; + result = TRUE; + } + else + { + g_signal_emit (model, model_signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_NEW_FILE, + _("The new directory is currently filtered " + "out. You need to adjust your filter " + "settings to make the directory visible")); + } + } + + g_object_unref (file); + return result; +} + +void +_gedit_file_browser_store_register_type (GTypeModule *type_module) +{ + gedit_file_browser_store_register_type (type_module); +} + +/* ex:set ts=8 noet: */ -- cgit v1.2.3