/* -*- 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 <andersca@gnu.org>
 *          Darin Adler <darin@bentspoon.com>
 */

/* 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;
}


