summaryrefslogtreecommitdiff
path: root/src/file-manager/fm-tree-model.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/file-manager/fm-tree-model.c')
-rw-r--r--src/file-manager/fm-tree-model.c2152
1 files changed, 2152 insertions, 0 deletions
diff --git a/src/file-manager/fm-tree-model.c b/src/file-manager/fm-tree-model.c
new file mode 100644
index 00000000..6356db90
--- /dev/null
+++ b/src/file-manager/fm-tree-model.c
@@ -0,0 +1,2152 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ * Copyright C) 2000, 2001 Eazel, Inc
+ * Copyright (C) 2002 Anders Carlsson
+ * Copyright (C) 2002 Bent Spoon Software
+ *
+ * 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 of the
+ * License, 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Anders Carlsson <[email protected]>
+ * Darin Adler <[email protected]>
+ */
+
+/* fm-tree-model.c - model for the tree view */
+
+#include <config.h>
+#include "fm-tree-model.h"
+
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <glib/gi18n.h>
+#include <libcaja-private/caja-directory.h>
+#include <libcaja-private/caja-file-attributes.h>
+#include <libcaja-private/caja-file.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+enum
+{
+ ROW_LOADED,
+ LAST_SIGNAL
+};
+
+static guint tree_model_signals[LAST_SIGNAL] = { 0 };
+
+typedef gboolean (* FilePredicate) (CajaFile *);
+
+/* The user_data of the GtkTreeIter is the TreeNode pointer.
+ * It's NULL for the dummy node. If it's NULL, then user_data2
+ * is the TreeNode pointer to the parent.
+ */
+
+typedef struct TreeNode TreeNode;
+typedef struct FMTreeModelRoot FMTreeModelRoot;
+
+struct TreeNode
+{
+ /* part of this node for the file itself */
+ int ref_count;
+
+ CajaFile *file;
+ char *display_name;
+ GIcon *icon;
+ GMount *mount;
+ GdkPixbuf *closed_pixbuf;
+ GdkPixbuf *open_pixbuf;
+ GdkPixbuf *emblem_pixbuf;
+
+ FMTreeModelRoot *root;
+
+ TreeNode *parent;
+ TreeNode *next;
+ TreeNode *prev;
+
+ /* part of the node used only for directories */
+ int dummy_child_ref_count;
+ int all_children_ref_count;
+
+ CajaDirectory *directory;
+ guint done_loading_id;
+ guint files_added_id;
+ guint files_changed_id;
+
+ TreeNode *first_child;
+
+ /* misc. flags */
+ guint done_loading : 1;
+ guint force_has_dummy : 1;
+ guint inserted : 1;
+};
+
+struct FMTreeModelDetails
+{
+ int stamp;
+
+ TreeNode *root_node;
+
+ guint monitoring_update_idle_id;
+
+ gboolean show_hidden_files;
+ gboolean show_backup_files;
+ gboolean show_only_directories;
+
+ GList *highlighted_files;
+};
+
+struct FMTreeModelRoot
+{
+ FMTreeModel *model;
+
+ /* separate hash table for each root node needed */
+ GHashTable *file_to_node_map;
+
+ TreeNode *root_node;
+};
+
+typedef struct
+{
+ CajaDirectory *directory;
+ FMTreeModel *model;
+} DoneLoadingParameters;
+
+static void fm_tree_model_tree_model_init (GtkTreeModelIface *iface);
+static void schedule_monitoring_update (FMTreeModel *model);
+static void destroy_node_without_reporting (FMTreeModel *model,
+ TreeNode *node);
+static void report_node_contents_changed (FMTreeModel *model,
+ TreeNode *node);
+
+G_DEFINE_TYPE_WITH_CODE (FMTreeModel, fm_tree_model, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
+ fm_tree_model_tree_model_init));
+
+static GtkTreeModelFlags
+fm_tree_model_get_flags (GtkTreeModel *tree_model)
+{
+ return GTK_TREE_MODEL_ITERS_PERSIST;
+}
+
+static void
+object_unref_if_not_NULL (gpointer object)
+{
+ if (object == NULL)
+ {
+ return;
+ }
+ g_object_unref (object);
+}
+
+static FMTreeModelRoot *
+tree_model_root_new (FMTreeModel *model)
+{
+ FMTreeModelRoot *root;
+
+ root = g_new0 (FMTreeModelRoot, 1);
+ root->model = model;
+ root->file_to_node_map = g_hash_table_new (NULL, NULL);
+
+ return root;
+}
+
+static TreeNode *
+tree_node_new (CajaFile *file, FMTreeModelRoot *root)
+{
+ TreeNode *node;
+
+ node = g_new0 (TreeNode, 1);
+ node->file = caja_file_ref (file);
+ node->root = root;
+ return node;
+}
+
+static void
+tree_node_unparent (FMTreeModel *model, TreeNode *node)
+{
+ TreeNode *parent, *next, *prev;
+
+ parent = node->parent;
+ next = node->next;
+ prev = node->prev;
+
+ if (parent == NULL &&
+ node == model->details->root_node)
+ {
+ /* it's the first root node -> if there is a next then let it be the first root node */
+ model->details->root_node = next;
+ }
+
+ if (next != NULL)
+ {
+ next->prev = prev;
+ }
+ if (prev == NULL && parent != NULL)
+ {
+ g_assert (parent->first_child == node);
+ parent->first_child = next;
+ }
+ else if (prev != NULL)
+ {
+ prev->next = next;
+ }
+
+ node->parent = NULL;
+ node->next = NULL;
+ node->prev = NULL;
+ node->root = NULL;
+}
+
+static void
+tree_node_destroy (FMTreeModel *model, TreeNode *node)
+{
+ g_assert (node->first_child == NULL);
+ g_assert (node->ref_count == 0);
+
+ tree_node_unparent (model, node);
+
+ g_object_unref (node->file);
+ g_free (node->display_name);
+ object_unref_if_not_NULL (node->icon);
+ object_unref_if_not_NULL (node->closed_pixbuf);
+ object_unref_if_not_NULL (node->open_pixbuf);
+ object_unref_if_not_NULL (node->emblem_pixbuf);
+
+ g_assert (node->done_loading_id == 0);
+ g_assert (node->files_added_id == 0);
+ g_assert (node->files_changed_id == 0);
+ caja_directory_unref (node->directory);
+
+ g_free (node);
+}
+
+static void
+tree_node_parent (TreeNode *node, TreeNode *parent)
+{
+ TreeNode *first_child;
+
+ g_assert (parent != NULL);
+ g_assert (node->parent == NULL);
+ g_assert (node->prev == NULL);
+ g_assert (node->next == NULL);
+
+ first_child = parent->first_child;
+
+ node->parent = parent;
+ node->root = parent->root;
+ node->next = first_child;
+
+ if (first_child != NULL)
+ {
+ g_assert (first_child->prev == NULL);
+ first_child->prev = node;
+ }
+
+ parent->first_child = node;
+}
+
+static GdkPixbuf *
+get_menu_icon (GIcon *icon)
+{
+ CajaIconInfo *info;
+ GdkPixbuf *pixbuf;
+ int size;
+
+ size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
+
+ info = caja_icon_info_lookup (icon, size);
+ pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (info, size);
+ g_object_unref (info);
+
+ return pixbuf;
+}
+
+static GdkPixbuf *
+get_menu_icon_for_file (TreeNode *node,
+ CajaFile *file,
+ CajaFileIconFlags flags)
+{
+ CajaIconInfo *info;
+ GdkPixbuf *pixbuf, *retval;
+ gboolean highlight;
+ int size;
+ FMTreeModel *model;
+
+ size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU);
+
+ info = caja_file_get_icon (file, size, flags);
+ retval = caja_icon_info_get_pixbuf_nodefault_at_size (info, size);
+ model = node->root->model;
+
+ highlight = (g_list_find_custom (model->details->highlighted_files,
+ file, (GCompareFunc) caja_file_compare_location) != NULL);
+
+ if (highlight)
+ {
+ pixbuf = eel_gdk_pixbuf_render (retval, 1, 255, 255, 0, 0);
+
+ if (pixbuf != NULL)
+ {
+ g_object_unref (retval);
+ retval = pixbuf;
+ }
+ }
+
+ g_object_unref (info);
+
+ return retval;
+}
+
+static GdkPixbuf *
+tree_node_get_pixbuf (TreeNode *node,
+ CajaFileIconFlags flags)
+{
+ if (node->parent == NULL)
+ {
+ return get_menu_icon (node->icon);
+ }
+ return get_menu_icon_for_file (node, node->file, flags);
+}
+
+static gboolean
+tree_node_update_pixbuf (TreeNode *node,
+ GdkPixbuf **pixbuf_storage,
+ CajaFileIconFlags flags)
+{
+ GdkPixbuf *pixbuf;
+
+ if (*pixbuf_storage == NULL)
+ {
+ return FALSE;
+ }
+ pixbuf = tree_node_get_pixbuf (node, flags);
+ if (pixbuf == *pixbuf_storage)
+ {
+ g_object_unref (pixbuf);
+ return FALSE;
+ }
+ g_object_unref (*pixbuf_storage);
+ *pixbuf_storage = pixbuf;
+ return TRUE;
+}
+
+static gboolean
+tree_node_update_closed_pixbuf (TreeNode *node)
+{
+ return tree_node_update_pixbuf (node, &node->closed_pixbuf, 0);
+}
+
+static gboolean
+tree_node_update_open_pixbuf (TreeNode *node)
+{
+ return tree_node_update_pixbuf (node, &node->open_pixbuf, CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER);
+}
+
+static GdkPixbuf *
+tree_node_get_emblem_pixbuf_internal (TreeNode *node)
+{
+ GdkPixbuf *pixbuf;
+ GList *emblem_pixbufs;
+ char *emblems_to_ignore[3];
+ int i;
+
+ i = 0;
+ emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_TRASH;
+
+ if (node->parent && node->parent->file)
+ {
+ if (!caja_file_can_write (node->parent->file))
+ {
+ emblems_to_ignore[i++] = CAJA_FILE_EMBLEM_NAME_CANT_WRITE;
+ }
+ }
+
+ emblems_to_ignore[i++] = NULL;
+
+ emblem_pixbufs = caja_file_get_emblem_pixbufs (node->file,
+ caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU),
+ TRUE,
+ emblems_to_ignore);
+
+
+ if (emblem_pixbufs != NULL)
+ {
+ pixbuf = g_object_ref (emblem_pixbufs->data);
+ }
+ else
+ {
+ pixbuf = NULL;
+ }
+
+ eel_gdk_pixbuf_list_free (emblem_pixbufs);
+
+ return pixbuf;
+}
+
+static gboolean
+tree_node_update_emblem_pixbuf (TreeNode *node)
+{
+ GdkPixbuf *pixbuf;
+
+ if (node->emblem_pixbuf == NULL)
+ {
+ return FALSE;
+ }
+ pixbuf = tree_node_get_emblem_pixbuf_internal (node);
+ if (pixbuf == node->emblem_pixbuf)
+ {
+ g_object_unref (pixbuf);
+ return FALSE;
+ }
+ g_object_unref (node->emblem_pixbuf);
+ node->emblem_pixbuf = pixbuf;
+ return TRUE;
+}
+
+static gboolean
+tree_node_update_display_name (TreeNode *node)
+{
+ char *display_name;
+
+ if (node->display_name == NULL)
+ {
+ return FALSE;
+ }
+ /* don't update root node display names */
+ if (node->parent == NULL)
+ {
+ return FALSE;
+ }
+ display_name = caja_file_get_display_name (node->file);
+ if (strcmp (display_name, node->display_name) == 0)
+ {
+ g_free (display_name);
+ return FALSE;
+ }
+ g_free (node->display_name);
+ node->display_name = NULL;
+ return TRUE;
+}
+
+static GdkPixbuf *
+tree_node_get_closed_pixbuf (TreeNode *node)
+{
+ if (node->closed_pixbuf == NULL)
+ {
+ node->closed_pixbuf = tree_node_get_pixbuf (node, 0);
+ }
+ return node->closed_pixbuf;
+}
+
+static GdkPixbuf *
+tree_node_get_open_pixbuf (TreeNode *node)
+{
+ if (node->open_pixbuf == NULL)
+ {
+ node->open_pixbuf = tree_node_get_pixbuf (node, CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER);
+ }
+ return node->open_pixbuf;
+}
+
+static GdkPixbuf *
+tree_node_get_emblem_pixbuf (TreeNode *node)
+{
+ if (node->emblem_pixbuf == NULL)
+ {
+ node->emblem_pixbuf = tree_node_get_emblem_pixbuf_internal (node);
+ }
+ return node->emblem_pixbuf;
+}
+
+static const char *
+tree_node_get_display_name (TreeNode *node)
+{
+ if (node->display_name == NULL)
+ {
+ node->display_name = caja_file_get_display_name (node->file);
+ }
+ return node->display_name;
+}
+
+static gboolean
+tree_node_has_dummy_child (TreeNode *node)
+{
+ return (node->directory != NULL
+ && (!node->done_loading
+ || node->first_child == NULL
+ || node->force_has_dummy)) ||
+ /* Roots always have dummy nodes if directory isn't loaded yet */
+ (node->directory == NULL && node->parent == NULL);
+}
+
+static int
+tree_node_get_child_index (TreeNode *parent, TreeNode *child)
+{
+ int i;
+ TreeNode *node;
+
+ if (child == NULL)
+ {
+ g_assert (tree_node_has_dummy_child (parent));
+ return 0;
+ }
+
+ i = tree_node_has_dummy_child (parent) ? 1 : 0;
+ for (node = parent->first_child; node != NULL; node = node->next, i++)
+ {
+ if (child == node)
+ {
+ return i;
+ }
+ }
+
+ g_assert_not_reached ();
+ return 0;
+}
+
+static gboolean
+make_iter_invalid (GtkTreeIter *iter)
+{
+ iter->stamp = 0;
+ iter->user_data = NULL;
+ iter->user_data2 = NULL;
+ iter->user_data3 = NULL;
+ return FALSE;
+}
+
+static gboolean
+make_iter_for_node (TreeNode *node, GtkTreeIter *iter, int stamp)
+{
+ if (node == NULL)
+ {
+ return make_iter_invalid (iter);
+ }
+ iter->stamp = stamp;
+ iter->user_data = node;
+ iter->user_data2 = NULL;
+ iter->user_data3 = NULL;
+ return TRUE;
+}
+
+static gboolean
+make_iter_for_dummy_row (TreeNode *parent, GtkTreeIter *iter, int stamp)
+{
+ g_assert (tree_node_has_dummy_child (parent));
+ g_assert (parent != NULL);
+ iter->stamp = stamp;
+ iter->user_data = NULL;
+ iter->user_data2 = parent;
+ iter->user_data3 = NULL;
+ return TRUE;
+}
+
+static TreeNode *
+get_node_from_file (FMTreeModelRoot *root, CajaFile *file)
+{
+ return g_hash_table_lookup (root->file_to_node_map, file);
+}
+
+static TreeNode *
+get_parent_node_from_file (FMTreeModelRoot *root, CajaFile *file)
+{
+ CajaFile *parent_file;
+ TreeNode *parent_node;
+
+ parent_file = caja_file_get_parent (file);
+ parent_node = get_node_from_file (root, parent_file);
+ caja_file_unref (parent_file);
+ return parent_node;
+}
+
+static TreeNode *
+create_node_for_file (FMTreeModelRoot *root, CajaFile *file)
+{
+ TreeNode *node;
+
+ g_assert (get_node_from_file (root, file) == NULL);
+ node = tree_node_new (file, root);
+ g_hash_table_insert (root->file_to_node_map, node->file, node);
+ return node;
+}
+
+#ifdef LOG_REF_COUNTS
+
+static char *
+get_node_uri (GtkTreeIter *iter)
+{
+ TreeNode *node, *parent;
+ char *parent_uri, *node_uri;
+
+ node = iter->user_data;
+ if (node != NULL)
+ {
+ return caja_file_get_uri (node->file);
+ }
+
+ parent = iter->user_data2;
+ parent_uri = caja_file_get_uri (parent->file);
+ node_uri = g_strconcat (parent_uri, " -- DUMMY", NULL);
+ g_free (parent_uri);
+ return node_uri;
+}
+
+#endif
+
+static void
+decrement_ref_count (FMTreeModel *model, TreeNode *node, int count)
+{
+ node->all_children_ref_count -= count;
+ if (node->all_children_ref_count == 0)
+ {
+ schedule_monitoring_update (model);
+ }
+}
+
+static void
+abandon_node_ref_count (FMTreeModel *model, TreeNode *node)
+{
+ if (node->parent != NULL)
+ {
+ decrement_ref_count (model, node->parent, node->ref_count);
+#ifdef LOG_REF_COUNTS
+ if (node->ref_count != 0)
+ {
+ char *uri;
+
+ uri = caja_file_get_uri (node->file);
+ g_message ("abandoning %d ref of %s, count is now %d",
+ node->ref_count, uri, node->parent->all_children_ref_count);
+ g_free (uri);
+ }
+#endif
+ }
+ node->ref_count = 0;
+}
+
+static void
+abandon_dummy_row_ref_count (FMTreeModel *model, TreeNode *node)
+{
+ decrement_ref_count (model, node, node->dummy_child_ref_count);
+ if (node->dummy_child_ref_count != 0)
+ {
+#ifdef LOG_REF_COUNTS
+ char *uri;
+
+ uri = caja_file_get_uri (node->file);
+ g_message ("abandoning %d ref of %s -- DUMMY, count is now %d",
+ node->dummy_child_ref_count, uri, node->all_children_ref_count);
+ g_free (uri);
+#endif
+ }
+ node->dummy_child_ref_count = 0;
+}
+
+static void
+report_row_inserted (FMTreeModel *model, GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+
+ 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);
+}
+
+static void
+report_row_contents_changed (FMTreeModel *model, GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, iter);
+ gtk_tree_path_free (path);
+}
+
+static void
+report_row_has_child_toggled (FMTreeModel *model, GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
+ gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), path, iter);
+ gtk_tree_path_free (path);
+}
+
+static GtkTreePath *
+get_node_path (FMTreeModel *model, TreeNode *node)
+{
+ GtkTreeIter iter;
+
+ make_iter_for_node (node, &iter, model->details->stamp);
+ return gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+}
+
+static void
+report_dummy_row_inserted (FMTreeModel *model, TreeNode *parent)
+{
+ GtkTreeIter iter;
+
+ if (!parent->inserted)
+ {
+ return;
+ }
+ make_iter_for_dummy_row (parent, &iter, model->details->stamp);
+ report_row_inserted (model, &iter);
+}
+
+static void
+report_dummy_row_deleted (FMTreeModel *model, TreeNode *parent)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ if (parent->inserted)
+ {
+ make_iter_for_node (parent, &iter, model->details->stamp);
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ gtk_tree_path_append_index (path, 0);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+ gtk_tree_path_free (path);
+ }
+ abandon_dummy_row_ref_count (model, parent);
+}
+
+static void
+report_node_inserted (FMTreeModel *model, TreeNode *node)
+{
+ GtkTreeIter iter;
+
+ make_iter_for_node (node, &iter, model->details->stamp);
+ report_row_inserted (model, &iter);
+ node->inserted = TRUE;
+
+ if (tree_node_has_dummy_child (node))
+ {
+ report_dummy_row_inserted (model, node);
+ }
+
+ if (node->directory != NULL ||
+ node->parent == NULL)
+ {
+ report_row_has_child_toggled (model, &iter);
+ }
+}
+
+static void
+report_node_contents_changed (FMTreeModel *model, TreeNode *node)
+{
+ GtkTreeIter iter;
+
+ if (!node->inserted)
+ {
+ return;
+ }
+ make_iter_for_node (node, &iter, model->details->stamp);
+ report_row_contents_changed (model, &iter);
+}
+
+static void
+report_node_has_child_toggled (FMTreeModel *model, TreeNode *node)
+{
+ GtkTreeIter iter;
+
+ if (!node->inserted)
+ {
+ return;
+ }
+ make_iter_for_node (node, &iter, model->details->stamp);
+ report_row_has_child_toggled (model, &iter);
+}
+
+static void
+report_dummy_row_contents_changed (FMTreeModel *model, TreeNode *parent)
+{
+ GtkTreeIter iter;
+
+ if (!parent->inserted)
+ {
+ return;
+ }
+ make_iter_for_dummy_row (parent, &iter, model->details->stamp);
+ report_row_contents_changed (model, &iter);
+}
+
+static void
+stop_monitoring_directory (FMTreeModel *model, TreeNode *node)
+{
+ CajaDirectory *directory;
+
+ if (node->done_loading_id == 0)
+ {
+ g_assert (node->files_added_id == 0);
+ g_assert (node->files_changed_id == 0);
+ return;
+ }
+
+ directory = node->directory;
+
+ g_signal_handler_disconnect (node->directory, node->done_loading_id);
+ g_signal_handler_disconnect (node->directory, node->files_added_id);
+ g_signal_handler_disconnect (node->directory, node->files_changed_id);
+
+ node->done_loading_id = 0;
+ node->files_added_id = 0;
+ node->files_changed_id = 0;
+
+ caja_directory_file_monitor_remove (node->directory, model);
+}
+
+static void
+destroy_children_without_reporting (FMTreeModel *model, TreeNode *parent)
+{
+ while (parent->first_child != NULL)
+ {
+ destroy_node_without_reporting (model, parent->first_child);
+ }
+}
+
+static void
+destroy_node_without_reporting (FMTreeModel *model, TreeNode *node)
+{
+ abandon_node_ref_count (model, node);
+ stop_monitoring_directory (model, node);
+ node->inserted = FALSE;
+ destroy_children_without_reporting (model, node);
+ g_hash_table_remove (node->root->file_to_node_map, node->file);
+ tree_node_destroy (model, node);
+}
+
+static void
+destroy_node (FMTreeModel *model, TreeNode *node)
+{
+ TreeNode *parent;
+ gboolean parent_had_dummy_child;
+ GtkTreePath *path;
+
+ parent = node->parent;
+ parent_had_dummy_child = tree_node_has_dummy_child (parent);
+
+ path = get_node_path (model, node);
+
+ /* Report row_deleted before actually deleting */
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+ gtk_tree_path_free (path);
+
+ destroy_node_without_reporting (model, node);
+
+ if (tree_node_has_dummy_child (parent))
+ {
+ if (!parent_had_dummy_child)
+ {
+ report_dummy_row_inserted (model, parent);
+ }
+ }
+ else
+ {
+ g_assert (!parent_had_dummy_child);
+ }
+}
+
+static void
+destroy_children (FMTreeModel *model, TreeNode *parent)
+{
+ while (parent->first_child != NULL)
+ {
+ destroy_node (model, parent->first_child);
+ }
+}
+
+static void
+destroy_children_by_function (FMTreeModel *model, TreeNode *parent, FilePredicate f)
+{
+ TreeNode *child, *next;
+
+ for (child = parent->first_child; child != NULL; child = next)
+ {
+ next = child->next;
+ if (f (child->file))
+ {
+ destroy_node (model, child);
+ }
+ else
+ {
+ destroy_children_by_function (model, child, f);
+ }
+ }
+}
+
+static void
+destroy_by_function (FMTreeModel *model, FilePredicate f)
+{
+ TreeNode *node;
+ for (node = model->details->root_node; node != NULL; node = node->next)
+ {
+ destroy_children_by_function (model, node, f);
+ }
+}
+
+static gboolean
+update_node_without_reporting (FMTreeModel *model, TreeNode *node)
+{
+ gboolean changed;
+
+ changed = FALSE;
+
+ if (node->directory == NULL &&
+ (caja_file_is_directory (node->file) || node->parent == NULL))
+ {
+ node->directory = caja_directory_get_for_file (node->file);
+ }
+ else if (node->directory != NULL &&
+ !(caja_file_is_directory (node->file) || node->parent == NULL))
+ {
+ stop_monitoring_directory (model, node);
+ destroy_children (model, node);
+ caja_directory_unref (node->directory);
+ node->directory = NULL;
+ }
+
+ changed |= tree_node_update_display_name (node);
+ changed |= tree_node_update_closed_pixbuf (node);
+ changed |= tree_node_update_open_pixbuf (node);
+ changed |= tree_node_update_emblem_pixbuf (node);
+
+ return changed;
+}
+
+static void
+insert_node (FMTreeModel *model, TreeNode *parent, TreeNode *node)
+{
+ gboolean parent_empty;
+
+ parent_empty = parent->first_child == NULL;
+ if (parent_empty)
+ {
+ /* Make sure the dummy lives as we insert the new row */
+ parent->force_has_dummy = TRUE;
+ }
+
+ tree_node_parent (node, parent);
+
+ update_node_without_reporting (model, node);
+ report_node_inserted (model, node);
+
+ if (parent_empty)
+ {
+ parent->force_has_dummy = FALSE;
+ if (!tree_node_has_dummy_child (parent))
+ {
+ /* Temporarily set this back so that row_deleted is
+ * sent before actually removing the dummy child */
+ parent->force_has_dummy = TRUE;
+ report_dummy_row_deleted (model, parent);
+ parent->force_has_dummy = FALSE;
+ }
+ }
+}
+
+static void
+reparent_node (FMTreeModel *model, TreeNode *node)
+{
+ GtkTreePath *path;
+ TreeNode *new_parent;
+
+ new_parent = get_parent_node_from_file (node->root, node->file);
+ if (new_parent == NULL || new_parent->directory == NULL)
+ {
+ destroy_node (model, node);
+ return;
+ }
+
+ path = get_node_path (model, node);
+
+ /* Report row_deleted before actually deleting */
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+ gtk_tree_path_free (path);
+
+ abandon_node_ref_count (model, node);
+ tree_node_unparent (model, node);
+
+ insert_node (model, new_parent, node);
+}
+
+static gboolean
+should_show_file (FMTreeModel *model, CajaFile *file)
+{
+ gboolean should;
+ TreeNode *node;
+
+ should = caja_file_should_show (file,
+ model->details->show_hidden_files,
+ model->details->show_backup_files,
+ TRUE);
+
+ if (should
+ && model->details->show_only_directories
+ &&! caja_file_is_directory (file))
+ {
+ should = FALSE;
+ }
+
+ if (should && caja_file_is_gone (file))
+ {
+ should = FALSE;
+ }
+
+ for (node = model->details->root_node; node != NULL; node = node->next)
+ {
+ if (!should && node != NULL && file == node->file)
+ {
+ should = TRUE;
+ }
+ }
+
+ return should;
+}
+
+static void
+update_node (FMTreeModel *model, TreeNode *node)
+{
+ gboolean had_dummy_child, has_dummy_child;
+ gboolean had_directory, has_directory;
+ gboolean changed;
+
+ if (!should_show_file (model, node->file))
+ {
+ destroy_node (model, node);
+ return;
+ }
+
+ if (node->parent != NULL && node->parent->directory != NULL
+ && !caja_directory_contains_file (node->parent->directory, node->file))
+ {
+ reparent_node (model, node);
+ return;
+ }
+
+ had_dummy_child = tree_node_has_dummy_child (node);
+ had_directory = node->directory != NULL;
+
+ changed = update_node_without_reporting (model, node);
+
+ has_dummy_child = tree_node_has_dummy_child (node);
+ has_directory = node->directory != NULL;
+
+ if (had_dummy_child != has_dummy_child)
+ {
+ if (has_dummy_child)
+ {
+ report_dummy_row_inserted (model, node);
+ }
+ else
+ {
+ /* Temporarily set this back so that row_deleted is
+ * sent before actually removing the dummy child */
+ node->force_has_dummy = TRUE;
+ report_dummy_row_deleted (model, node);
+ node->force_has_dummy = FALSE;
+ }
+ }
+ if (had_directory != has_directory)
+ {
+ report_node_has_child_toggled (model, node);
+ }
+
+ if (changed)
+ {
+ report_node_contents_changed (model, node);
+ }
+}
+
+static void
+process_file_change (FMTreeModelRoot *root,
+ CajaFile *file)
+{
+ TreeNode *node, *parent;
+
+ node = get_node_from_file (root, file);
+ if (node != NULL)
+ {
+ update_node (root->model, node);
+ return;
+ }
+
+ if (!should_show_file (root->model, file))
+ {
+ return;
+ }
+
+ parent = get_parent_node_from_file (root, file);
+ if (parent == NULL)
+ {
+ return;
+ }
+
+ insert_node (root->model, parent, create_node_for_file (root, file));
+}
+
+static void
+files_changed_callback (CajaDirectory *directory,
+ GList *changed_files,
+ gpointer callback_data)
+{
+ FMTreeModelRoot *root;
+ GList *node;
+
+ root = (FMTreeModelRoot *) (callback_data);
+
+ for (node = changed_files; node != NULL; node = node->next)
+ {
+ process_file_change (root, CAJA_FILE (node->data));
+ }
+}
+
+static void
+set_done_loading (FMTreeModel *model, TreeNode *node, gboolean done_loading)
+{
+ gboolean had_dummy;
+
+ if (node == NULL || node->done_loading == done_loading)
+ {
+ return;
+ }
+
+ had_dummy = tree_node_has_dummy_child (node);
+
+ node->done_loading = done_loading;
+
+ if (tree_node_has_dummy_child (node))
+ {
+ if (had_dummy)
+ {
+ report_dummy_row_contents_changed (model, node);
+ }
+ else
+ {
+ report_dummy_row_inserted (model, node);
+ }
+ }
+ else
+ {
+ if (had_dummy)
+ {
+ /* Temporarily set this back so that row_deleted is
+ * sent before actually removing the dummy child */
+ node->force_has_dummy = TRUE;
+ report_dummy_row_deleted (model, node);
+ node->force_has_dummy = FALSE;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ }
+}
+
+static void
+done_loading_callback (CajaDirectory *directory,
+ FMTreeModelRoot *root)
+{
+ CajaFile *file;
+ TreeNode *node;
+ GtkTreeIter iter;
+
+ file = caja_directory_get_corresponding_file (directory);
+ node = get_node_from_file (root, file);
+ if (node == NULL)
+ {
+ /* This can happen for non-existing files as tree roots,
+ * since the directory <-> file object relation gets
+ * broken due to caja_directory_remove_file()
+ * getting called when i/o fails.
+ */
+ return;
+ }
+ set_done_loading (root->model, node, TRUE);
+ caja_file_unref (file);
+
+ make_iter_for_node (node, &iter, root->model->details->stamp);
+ g_signal_emit (root->model,
+ tree_model_signals[ROW_LOADED], 0,
+ &iter);
+}
+
+static CajaFileAttributes
+get_tree_monitor_attributes (void)
+{
+ CajaFileAttributes attributes;
+
+ attributes =
+ CAJA_FILE_ATTRIBUTES_FOR_ICON |
+ CAJA_FILE_ATTRIBUTE_INFO |
+ CAJA_FILE_ATTRIBUTE_LINK_INFO;
+
+ return attributes;
+}
+
+static void
+start_monitoring_directory (FMTreeModel *model, TreeNode *node)
+{
+ CajaDirectory *directory;
+ CajaFileAttributes attributes;
+
+ if (node->done_loading_id != 0)
+ {
+ return;
+ }
+
+ g_assert (node->files_added_id == 0);
+ g_assert (node->files_changed_id == 0);
+
+ directory = node->directory;
+
+ node->done_loading_id = g_signal_connect
+ (directory, "done_loading",
+ G_CALLBACK (done_loading_callback), node->root);
+ node->files_added_id = g_signal_connect
+ (directory, "files_added",
+ G_CALLBACK (files_changed_callback), node->root);
+ node->files_changed_id = g_signal_connect
+ (directory, "files_changed",
+ G_CALLBACK (files_changed_callback), node->root);
+
+ set_done_loading (model, node, caja_directory_are_all_files_seen (directory));
+
+ attributes = get_tree_monitor_attributes ();
+ caja_directory_file_monitor_add (directory, model,
+ model->details->show_hidden_files,
+ model->details->show_backup_files,
+ attributes, files_changed_callback, node->root);
+}
+
+static int
+fm_tree_model_get_n_columns (GtkTreeModel *model)
+{
+ return FM_TREE_MODEL_NUM_COLUMNS;
+}
+
+static GType
+fm_tree_model_get_column_type (GtkTreeModel *model, int index)
+{
+ switch (index)
+ {
+ case FM_TREE_MODEL_DISPLAY_NAME_COLUMN:
+ return G_TYPE_STRING;
+ case FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN:
+ return GDK_TYPE_PIXBUF;
+ case FM_TREE_MODEL_OPEN_PIXBUF_COLUMN:
+ return GDK_TYPE_PIXBUF;
+ case FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN:
+ return GDK_TYPE_PIXBUF;
+ case FM_TREE_MODEL_FONT_STYLE_COLUMN:
+ return PANGO_TYPE_STYLE;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return G_TYPE_INVALID;
+}
+
+static gboolean
+iter_is_valid (FMTreeModel *model, const GtkTreeIter *iter)
+{
+ TreeNode *node, *parent;
+
+ if (iter->stamp != model->details->stamp)
+ {
+ return FALSE;
+ }
+
+ node = iter->user_data;
+ parent = iter->user_data2;
+ if (node == NULL)
+ {
+ if (parent != NULL)
+ {
+ if (!CAJA_IS_FILE (parent->file))
+ {
+ return FALSE;
+ }
+ if (!tree_node_has_dummy_child (parent))
+ {
+ return FALSE;
+ }
+ }
+ }
+ else
+ {
+ if (!CAJA_IS_FILE (node->file))
+ {
+ return FALSE;
+ }
+ if (parent != NULL)
+ {
+ return FALSE;
+ }
+ }
+ if (iter->user_data3 != NULL)
+ {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+fm_tree_model_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path)
+{
+ int *indices;
+ GtkTreeIter parent;
+ int depth, i;
+
+ indices = gtk_tree_path_get_indices (path);
+ depth = gtk_tree_path_get_depth (path);
+
+ if (! gtk_tree_model_iter_nth_child (model, iter, NULL, indices[0]))
+ {
+ return FALSE;
+ }
+
+ for (i = 1; i < depth; i++)
+ {
+ parent = *iter;
+
+ if (! gtk_tree_model_iter_nth_child (model, iter, &parent, indices[i]))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static GtkTreePath *
+fm_tree_model_get_path (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ FMTreeModel *tree_model;
+ TreeNode *node, *parent, *cnode;
+ GtkTreePath *path;
+ GtkTreeIter parent_iter;
+ int i;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), NULL);
+ tree_model = FM_TREE_MODEL (model);
+ g_return_val_if_fail (iter_is_valid (tree_model, iter), NULL);
+
+ node = iter->user_data;
+ if (node == NULL)
+ {
+ parent = iter->user_data2;
+ if (parent == NULL)
+ {
+ return gtk_tree_path_new ();
+ }
+ }
+ else
+ {
+ parent = node->parent;
+ if (parent == NULL)
+ {
+ i = 0;
+ for (cnode = tree_model->details->root_node; cnode != node; cnode = cnode->next)
+ {
+ i++;
+ }
+ path = gtk_tree_path_new ();
+ gtk_tree_path_append_index (path, i);
+ return path;
+ }
+ }
+
+ parent_iter.stamp = iter->stamp;
+ parent_iter.user_data = parent;
+ parent_iter.user_data2 = NULL;
+ parent_iter.user_data3 = NULL;
+
+ path = fm_tree_model_get_path (model, &parent_iter);
+
+ gtk_tree_path_append_index (path, tree_node_get_child_index (parent, node));
+
+ return path;
+}
+
+static void
+fm_tree_model_get_value (GtkTreeModel *model, GtkTreeIter *iter, int column, GValue *value)
+{
+ TreeNode *node, *parent;
+ FMTreeModel *fm_model;
+
+ g_return_if_fail (FM_IS_TREE_MODEL (model));
+ g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter));
+
+ fm_model = FM_TREE_MODEL (model);
+ node = iter->user_data;
+
+ switch (column)
+ {
+ case FM_TREE_MODEL_DISPLAY_NAME_COLUMN:
+ g_value_init (value, G_TYPE_STRING);
+ if (node == NULL)
+ {
+ parent = iter->user_data2;
+ g_value_set_static_string (value, parent->done_loading
+ ? _("(Empty)") : _("Loading..."));
+ }
+ else
+ {
+ g_value_set_string (value, tree_node_get_display_name (node));
+ }
+ break;
+ case FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN:
+ g_value_init (value, GDK_TYPE_PIXBUF);
+ g_value_set_object (value, node == NULL ? NULL : tree_node_get_closed_pixbuf (node));
+ break;
+ case FM_TREE_MODEL_OPEN_PIXBUF_COLUMN:
+ g_value_init (value, GDK_TYPE_PIXBUF);
+ g_value_set_object (value, node == NULL ? NULL : tree_node_get_open_pixbuf (node));
+ break;
+ case FM_TREE_MODEL_EMBLEM_PIXBUF_COLUMN:
+ g_value_init (value, GDK_TYPE_PIXBUF);
+ g_value_set_object (value, node == NULL ? NULL : tree_node_get_emblem_pixbuf (node));
+ break;
+ case FM_TREE_MODEL_FONT_STYLE_COLUMN:
+ g_value_init (value, PANGO_TYPE_STYLE);
+ if (node == NULL)
+ {
+ g_value_set_enum (value, PANGO_STYLE_ITALIC);
+ }
+ else
+ {
+ g_value_set_enum (value, PANGO_STYLE_NORMAL);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+fm_tree_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ TreeNode *node, *parent, *next;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), FALSE);
+
+ node = iter->user_data;
+
+ if (node == NULL)
+ {
+ parent = iter->user_data2;
+ next = parent->first_child;
+ }
+ else
+ {
+ next = node->next;
+ }
+
+ return make_iter_for_node (next, iter, iter->stamp);
+}
+
+static gboolean
+fm_tree_model_iter_children (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent_iter)
+{
+ TreeNode *parent;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), parent_iter), FALSE);
+
+ parent = parent_iter->user_data;
+ if (parent == NULL)
+ {
+ return make_iter_invalid (iter);
+ }
+
+ if (tree_node_has_dummy_child (parent))
+ {
+ return make_iter_for_dummy_row (parent, iter, parent_iter->stamp);
+ }
+ return make_iter_for_node (parent->first_child, iter, parent_iter->stamp);
+}
+
+static gboolean
+fm_tree_model_iter_parent (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *child_iter)
+{
+ TreeNode *child, *parent;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), child_iter), FALSE);
+
+ child = child_iter->user_data;
+
+ if (child == NULL)
+ {
+ parent = child_iter->user_data2;
+ }
+ else
+ {
+ parent = child->parent;
+ }
+
+ return make_iter_for_node (parent, iter, child_iter->stamp);
+}
+
+static gboolean
+fm_tree_model_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ gboolean has_child;
+ TreeNode *node;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), FALSE);
+
+ node = iter->user_data;
+
+ has_child = node != NULL && (node->directory != NULL || node->parent == NULL);
+
+#if 0
+ g_warning ("Node '%s' %s",
+ node && node->file ? caja_file_get_uri (node->file) : "no name",
+ has_child ? "has child" : "no child");
+#endif
+
+ return has_child;
+}
+
+static int
+fm_tree_model_iter_n_children (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ FMTreeModel *tree_model;
+ TreeNode *parent, *node;
+ int n;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (iter == NULL || iter_is_valid (FM_TREE_MODEL (model), iter), FALSE);
+
+ tree_model = FM_TREE_MODEL (model);
+
+ if (iter == NULL)
+ {
+ return 1;
+ }
+
+ parent = iter->user_data;
+ if (parent == NULL)
+ {
+ return 0;
+ }
+
+ n = tree_node_has_dummy_child (parent) ? 1 : 0;
+ for (node = parent->first_child; node != NULL; node = node->next)
+ {
+ n++;
+ }
+
+ return n;
+}
+
+static gboolean
+fm_tree_model_iter_nth_child (GtkTreeModel *model, GtkTreeIter *iter,
+ GtkTreeIter *parent_iter, int n)
+{
+ FMTreeModel *tree_model;
+ TreeNode *parent, *node;
+ int i;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (parent_iter == NULL
+ || iter_is_valid (FM_TREE_MODEL (model), parent_iter), FALSE);
+
+ tree_model = FM_TREE_MODEL (model);
+
+ if (parent_iter == NULL)
+ {
+ node = tree_model->details->root_node;
+ for (i = 0; i < n && node != NULL; i++, node = node->next);
+ return make_iter_for_node (node, iter,
+ tree_model->details->stamp);
+ }
+
+ parent = parent_iter->user_data;
+ if (parent == NULL)
+ {
+ return make_iter_invalid (iter);
+ }
+
+ i = tree_node_has_dummy_child (parent) ? 1 : 0;
+ if (n == 0 && i == 1)
+ {
+ return make_iter_for_dummy_row (parent, iter, parent_iter->stamp);
+ }
+ for (node = parent->first_child; i != n; i++, node = node->next)
+ {
+ if (node == NULL)
+ {
+ return make_iter_invalid (iter);
+ }
+ }
+
+ return make_iter_for_node (node, iter, parent_iter->stamp);
+}
+
+static void
+update_monitoring (FMTreeModel *model, TreeNode *node)
+{
+ TreeNode *child;
+
+ if (node->all_children_ref_count == 0)
+ {
+ stop_monitoring_directory (model, node);
+ destroy_children (model, node);
+ }
+ else
+ {
+ for (child = node->first_child; child != NULL; child = child->next)
+ {
+ update_monitoring (model, child);
+ }
+ start_monitoring_directory (model, node);
+ }
+}
+
+static gboolean
+update_monitoring_idle_callback (gpointer callback_data)
+{
+ FMTreeModel *model;
+ TreeNode *node;
+
+ model = FM_TREE_MODEL (callback_data);
+ model->details->monitoring_update_idle_id = 0;
+ for (node = model->details->root_node; node != NULL; node = node->next)
+ {
+ update_monitoring (model, node);
+ }
+ return FALSE;
+}
+
+static void
+schedule_monitoring_update (FMTreeModel *model)
+{
+ if (model->details->monitoring_update_idle_id == 0)
+ {
+ model->details->monitoring_update_idle_id =
+ g_idle_add (update_monitoring_idle_callback, model);
+ }
+}
+
+static void
+stop_monitoring_directory_and_children (FMTreeModel *model, TreeNode *node)
+{
+ TreeNode *child;
+
+ stop_monitoring_directory (model, node);
+ for (child = node->first_child; child != NULL; child = child->next)
+ {
+ stop_monitoring_directory_and_children (model, child);
+ }
+}
+
+static void
+stop_monitoring (FMTreeModel *model)
+{
+ TreeNode *node;
+
+ for (node = model->details->root_node; node != NULL; node = node->next)
+ {
+ stop_monitoring_directory_and_children (model, node);
+ }
+}
+
+static void
+fm_tree_model_ref_node (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ TreeNode *node, *parent;
+#ifdef LOG_REF_COUNTS
+ char *uri;
+#endif
+
+ g_return_if_fail (FM_IS_TREE_MODEL (model));
+ g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter));
+
+ node = iter->user_data;
+ if (node == NULL)
+ {
+ parent = iter->user_data2;
+ g_assert (parent->dummy_child_ref_count >= 0);
+ ++parent->dummy_child_ref_count;
+ }
+ else
+ {
+ parent = node->parent;
+ g_assert (node->ref_count >= 0);
+ ++node->ref_count;
+ }
+
+ if (parent != NULL)
+ {
+ g_assert (parent->all_children_ref_count >= 0);
+ if (++parent->all_children_ref_count == 1)
+ {
+ if (parent->first_child == NULL)
+ {
+ parent->done_loading = FALSE;
+ }
+ schedule_monitoring_update (FM_TREE_MODEL (model));
+ }
+#ifdef LOG_REF_COUNTS
+ uri = get_node_uri (iter);
+ g_message ("ref of %s, count is now %d",
+ uri, parent->all_children_ref_count);
+ g_free (uri);
+#endif
+ }
+}
+
+static void
+fm_tree_model_unref_node (GtkTreeModel *model, GtkTreeIter *iter)
+{
+ TreeNode *node, *parent;
+#ifdef LOG_REF_COUNTS
+ char *uri;
+#endif
+
+ g_return_if_fail (FM_IS_TREE_MODEL (model));
+ g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter));
+
+ node = iter->user_data;
+ if (node == NULL)
+ {
+ parent = iter->user_data2;
+ g_assert (parent->dummy_child_ref_count > 0);
+ --parent->dummy_child_ref_count;
+ }
+ else
+ {
+ parent = node->parent;
+ g_assert (node->ref_count > 0);
+ --node->ref_count;
+ }
+
+ if (parent != NULL)
+ {
+ g_assert (parent->all_children_ref_count > 0);
+#ifdef LOG_REF_COUNTS
+ uri = get_node_uri (iter);
+ g_message ("unref of %s, count is now %d",
+ uri, parent->all_children_ref_count - 1);
+ g_free (uri);
+#endif
+ if (--parent->all_children_ref_count == 0)
+ {
+ schedule_monitoring_update (FM_TREE_MODEL (model));
+ }
+ }
+}
+
+void
+fm_tree_model_add_root_uri (FMTreeModel *model, const char *root_uri, const char *display_name, GIcon *icon, GMount *mount)
+{
+ CajaFile *file;
+ TreeNode *node, *cnode;
+ FMTreeModelRoot *newroot;
+
+ file = caja_file_get_by_uri (root_uri);
+
+ newroot = tree_model_root_new (model);
+ node = create_node_for_file (newroot, file);
+ node->display_name = g_strdup (display_name);
+ node->icon = g_object_ref (icon);
+ if (mount)
+ {
+ node->mount = g_object_ref (mount);
+ }
+ newroot->root_node = node;
+ node->parent = NULL;
+ if (model->details->root_node == NULL)
+ {
+ model->details->root_node = node;
+ }
+ else
+ {
+ /* append it */
+ for (cnode = model->details->root_node; cnode->next != NULL; cnode = cnode->next);
+ cnode->next = node;
+ node->prev = cnode;
+ }
+
+ caja_file_unref (file);
+
+ update_node_without_reporting (model, node);
+ report_node_inserted (model, node);
+}
+
+GMount *
+fm_tree_model_get_mount_for_root_node_file (FMTreeModel *model, CajaFile *file)
+{
+ TreeNode *node;
+
+ for (node = model->details->root_node; node != NULL; node = node->next)
+ {
+ if (file == node->file)
+ {
+ break;
+ }
+ }
+
+ if (node)
+ {
+ return node->mount;
+ }
+
+ return NULL;
+}
+
+void
+fm_tree_model_remove_root_uri (FMTreeModel *model, const char *uri)
+{
+ TreeNode *node;
+ GtkTreePath *path;
+ FMTreeModelRoot *root;
+ CajaFile *file;
+
+ file = caja_file_get_by_uri (uri);
+ for (node = model->details->root_node; node != NULL; node = node->next)
+ {
+ if (file == node->file)
+ {
+ break;
+ }
+ }
+ caja_file_unref (file);
+
+ if (node)
+ {
+ /* remove the node */
+
+ if (node->mount)
+ {
+ g_object_unref (node->mount);
+ node->mount = NULL;
+ }
+
+ caja_file_monitor_remove (node->file, model);
+ path = get_node_path (model, node);
+
+ /* Report row_deleted before actually deleting */
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+ gtk_tree_path_free (path);
+
+ if (node->prev)
+ {
+ node->prev->next = node->next;
+ }
+ if (node->next)
+ {
+ node->next->prev = node->prev;
+ }
+ if (node == model->details->root_node)
+ {
+ model->details->root_node = node->next;
+ }
+
+ /* destroy the root identifier */
+ root = node->root;
+ destroy_node_without_reporting (model, node);
+ g_hash_table_destroy (root->file_to_node_map);
+ g_free (root);
+ }
+}
+
+FMTreeModel *
+fm_tree_model_new (void)
+{
+ FMTreeModel *model;
+
+ model = g_object_new (FM_TYPE_TREE_MODEL, NULL);
+
+ return model;
+}
+
+void
+fm_tree_model_set_show_hidden_files (FMTreeModel *model,
+ gboolean show_hidden_files)
+{
+ g_return_if_fail (FM_IS_TREE_MODEL (model));
+ g_return_if_fail (show_hidden_files == FALSE || show_hidden_files == TRUE);
+
+ show_hidden_files = show_hidden_files != FALSE;
+ if (model->details->show_hidden_files == show_hidden_files)
+ {
+ return;
+ }
+ model->details->show_hidden_files = show_hidden_files;
+ model->details->show_backup_files = show_hidden_files;
+ stop_monitoring (model);
+ if (!show_hidden_files)
+ {
+ destroy_by_function (model, caja_file_is_hidden_file);
+ }
+ schedule_monitoring_update (model);
+}
+
+static gboolean
+file_is_not_directory (CajaFile *file)
+{
+ return !caja_file_is_directory (file);
+}
+
+void
+fm_tree_model_set_show_only_directories (FMTreeModel *model,
+ gboolean show_only_directories)
+{
+ g_return_if_fail (FM_IS_TREE_MODEL (model));
+ g_return_if_fail (show_only_directories == FALSE || show_only_directories == TRUE);
+
+ show_only_directories = show_only_directories != FALSE;
+ if (model->details->show_only_directories == show_only_directories)
+ {
+ return;
+ }
+ model->details->show_only_directories = show_only_directories;
+ stop_monitoring (model);
+ if (show_only_directories)
+ {
+ destroy_by_function (model, file_is_not_directory);
+ }
+ schedule_monitoring_update (model);
+}
+
+CajaFile *
+fm_tree_model_iter_get_file (FMTreeModel *model, GtkTreeIter *iter)
+{
+ TreeNode *node;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), NULL);
+ g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), NULL);
+
+ node = iter->user_data;
+ return node == NULL ? NULL : caja_file_ref (node->file);
+}
+
+/* This is used to work around some sort order stability problems
+ with gtktreemodelsort */
+int
+fm_tree_model_iter_compare_roots (FMTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b)
+{
+ TreeNode *a, *b, *n;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), 0);
+ g_return_val_if_fail (iter_is_valid (model, iter_a), 0);
+ g_return_val_if_fail (iter_is_valid (model, iter_b), 0);
+
+ a = iter_a->user_data;
+ b = iter_b->user_data;
+
+ g_assert (a != NULL && a->parent == NULL);
+ g_assert (b != NULL && b->parent == NULL);
+
+ if (a == b)
+ {
+ return 0;
+ }
+
+ for (n = model->details->root_node; n != NULL; n = n->next)
+ {
+ if (n == a)
+ {
+ return -1;
+ }
+ if (n == b)
+ {
+ return 1;
+ }
+ }
+ g_assert_not_reached ();
+}
+
+gboolean
+fm_tree_model_iter_is_root (FMTreeModel *model, GtkTreeIter *iter)
+{
+ TreeNode *node;
+
+ g_return_val_if_fail (FM_IS_TREE_MODEL (model), 0);
+ g_return_val_if_fail (iter_is_valid (model, iter), 0);
+ node = iter->user_data;
+ if (node == NULL)
+ {
+ return FALSE;
+ }
+ else
+ {
+ return (node->parent == NULL);
+ }
+}
+
+gboolean
+fm_tree_model_file_get_iter (FMTreeModel *model,
+ GtkTreeIter *iter,
+ CajaFile *file,
+ GtkTreeIter *current_iter)
+{
+ TreeNode *node, *root_node;
+
+ if (current_iter != NULL && current_iter->user_data != NULL)
+ {
+ node = get_node_from_file (((TreeNode *) current_iter->user_data)->root, file);
+ return make_iter_for_node (node, iter, model->details->stamp);
+ }
+
+ for (root_node = model->details->root_node; root_node != NULL; root_node = root_node->next)
+ {
+ node = get_node_from_file (root_node->root, file);
+ if (node != NULL)
+ {
+ return make_iter_for_node (node, iter, model->details->stamp);
+ }
+ }
+ return FALSE;
+}
+
+static void
+do_update_node (CajaFile *file,
+ FMTreeModel *model)
+{
+ TreeNode *root, *node = NULL;
+
+ for (root = model->details->root_node; root != NULL; root = root->next)
+ {
+ node = get_node_from_file (root->root, file);
+
+ if (node != NULL)
+ {
+ break;
+ }
+ }
+
+ if (node == NULL)
+ {
+ return;
+ }
+
+ update_node (model, node);
+}
+
+void
+fm_tree_model_set_highlight_for_files (FMTreeModel *model,
+ GList *files)
+{
+ GList *old_files;
+
+ if (model->details->highlighted_files != NULL)
+ {
+ old_files = model->details->highlighted_files;
+ model->details->highlighted_files = NULL;
+
+ g_list_foreach (old_files,
+ (GFunc) do_update_node, model);
+
+ caja_file_list_free (old_files);
+ }
+
+ if (files != NULL)
+ {
+ model->details->highlighted_files =
+ caja_file_list_copy (files);
+ g_list_foreach (model->details->highlighted_files,
+ (GFunc) do_update_node, model);
+ }
+}
+
+static void
+fm_tree_model_init (FMTreeModel *model)
+{
+ model->details = g_new0 (FMTreeModelDetails, 1);
+
+ do
+ {
+ model->details->stamp = g_random_int ();
+ }
+ while (model->details->stamp == 0);
+}
+
+static void
+fm_tree_model_finalize (GObject *object)
+{
+ FMTreeModel *model;
+ TreeNode *root_node, *next_root;
+ FMTreeModelRoot *root;
+
+ model = FM_TREE_MODEL (object);
+
+ for (root_node = model->details->root_node; root_node != NULL; root_node = next_root)
+ {
+ next_root = root_node->next;
+ root = root_node->root;
+ destroy_node_without_reporting (model, root_node);
+ g_hash_table_destroy (root->file_to_node_map);
+ g_free (root);
+ }
+
+ if (model->details->monitoring_update_idle_id != 0)
+ {
+ g_source_remove (model->details->monitoring_update_idle_id);
+ }
+
+ if (model->details->highlighted_files != NULL)
+ {
+ caja_file_list_free (model->details->highlighted_files);
+ }
+
+ g_free (model->details);
+
+ G_OBJECT_CLASS (fm_tree_model_parent_class)->finalize (object);
+}
+
+static void
+fm_tree_model_class_init (FMTreeModelClass *class)
+{
+ G_OBJECT_CLASS (class)->finalize = fm_tree_model_finalize;
+
+ tree_model_signals[ROW_LOADED] =
+ g_signal_new ("row_loaded",
+ FM_TYPE_TREE_MODEL,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (FMTreeModelClass, row_loaded),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1,
+ GTK_TYPE_TREE_ITER);
+}
+
+static void
+fm_tree_model_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = fm_tree_model_get_flags;
+ iface->get_n_columns = fm_tree_model_get_n_columns;
+ iface->get_column_type = fm_tree_model_get_column_type;
+ iface->get_iter = fm_tree_model_get_iter;
+ iface->get_path = fm_tree_model_get_path;
+ iface->get_value = fm_tree_model_get_value;
+ iface->iter_next = fm_tree_model_iter_next;
+ iface->iter_children = fm_tree_model_iter_children;
+ iface->iter_has_child = fm_tree_model_iter_has_child;
+ iface->iter_n_children = fm_tree_model_iter_n_children;
+ iface->iter_nth_child = fm_tree_model_iter_nth_child;
+ iface->iter_parent = fm_tree_model_iter_parent;
+ iface->ref_node = fm_tree_model_ref_node;
+ iface->unref_node = fm_tree_model_unref_node;
+}
+
+