/* vi:set et ai sw=2 sts=2 ts=2: */
/*-
 * Copyright (c) 2023 Amrit Borah <elessar1802@gmail.com>
 *
 * 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
 */



/*
 * The rough working of the Model -
 *
 * Each file/subdir/dir is a #_Node.
 * There are 2 types of #_Node - a file #_Node & a dummy Node.
 * A file #_Node is one which could also be a subdir. But the key
 * differentiating factor is that a file #_Node is always initialized with a file
 * while a dummy Node is initialed with NULL.
 * The dummy node's main purpose is to act as a child for a subdir
 * which has not been expanded in the view yet.
 * Whenever a #_Node is loaded, the node's file's children will be added as
 * child nodes. If any of these child nodes are non-empty directories then
 * they will additionally be given a dummy child node. When these subdirs
 * will be loaded (expanded in the view), the dummy child node will be replaced
 * by the actual children of the node.
 *
 * A Node's children are stored in sorted manner in a GSequence and additionally
 * a HashTable is used to quickly query and retrieve the required child.
 */



#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include "thunar/thunar-application.h"
#include "thunar/thunar-file.h"
#include "thunar/thunar-gio-extensions.h"
#include "thunar/thunar-gobject-extensions.h"
#include "thunar/thunar-io-jobs.h"
#include "thunar/thunar-preferences.h"
#include "thunar/thunar-private.h"
#include "thunar/thunar-simple-job.h"
#include "thunar/thunar-tree-view-model.h"
#include "thunar/thunar-user.h"
#include "thunar/thunar-util.h"

#include <libxfce4util/libxfce4util.h>



/* A delay after which the children of the collapsed subdir will be
 * freed and replaced by a dummy node (cleanup); if the subdir is again
 * expanded before the delay elapses the scheduled cleanup will be cancelled */
#define CLEANUP_AFTER_COLLAPSE_DELAY 5000 /* in ms */

/* used in order to model expand arrows on folders */
typedef enum
{
  can_expand_unknown,
  can_expand_loading,
  can_expand_yes,
  can_expand_no,
} CanExpand;


/* Defintions & typedefs */
typedef struct _Node Node;



/* Property identifiers */
enum
{
  PROP_0,
  PROP_CASE_SENSITIVE,
  PROP_DATE_STYLE,
  PROP_DATE_CUSTOM_STYLE,
  PROP_FOLDER,
  PROP_FOLDERS_FIRST,
  PROP_HIDDEN_LAST,
  PROP_NUM_FILES,
  PROP_SHOW_HIDDEN,
  PROP_FOLDER_ITEM_COUNT,
  PROP_FILE_SIZE_BINARY,
  PROP_LOADING,
  PROP_EXPANDABLE_FOLDERS,
  N_PROPERTIES
};



static void
thunar_tree_view_model_drag_dest_init (GtkTreeDragDestIface *iface);
static void
thunar_tree_view_model_sortable_init (GtkTreeSortableIface *iface);
static void
thunar_tree_view_model_tree_model_init (GtkTreeModelIface *iface);


/*************************************************
 * GObject Virtual Methods
 *************************************************/
static void
thunar_tree_view_model_dispose (GObject *object);
static void
thunar_tree_view_model_finalize (GObject *object);
static void
thunar_tree_view_model_get_property (GObject    *object,
                                     guint       prop_id,
                                     GValue     *value,
                                     GParamSpec *pspec);
static void
thunar_tree_view_model_set_property (GObject      *object,
                                     guint         prop_id,
                                     const GValue *value,
                                     GParamSpec   *pspec);


/*************************************************
 * GtkTreeDragDest Virtual Methods
 *************************************************/
static gboolean
thunar_tree_view_model_drag_data_received (GtkTreeDragDest  *dest,
                                           GtkTreePath      *path,
                                           GtkSelectionData *data);
static gboolean
thunar_tree_view_model_row_drop_possible (GtkTreeDragDest  *dest,
                                          GtkTreePath      *path,
                                          GtkSelectionData *data);


/*************************************************
 * GtkSortable Virtual Methods
 *************************************************/
static gboolean
thunar_tree_view_model_get_sort_column_id (GtkTreeSortable *sortable,
                                           gint            *sort_column_id,
                                           GtkSortType     *order);
static void
thunar_tree_view_model_set_sort_column_id (GtkTreeSortable *sortable,
                                           gint             sort_column_id,
                                           GtkSortType      order);
static void
thunar_tree_view_model_set_default_sort_func (GtkTreeSortable       *sortable,
                                              GtkTreeIterCompareFunc func,
                                              gpointer               data,
                                              GDestroyNotify         destroy);
static void
thunar_tree_view_model_set_sort_func (GtkTreeSortable       *sortable,
                                      gint                   sort_column_id,
                                      GtkTreeIterCompareFunc func,
                                      gpointer               data,
                                      GDestroyNotify         destroy);
static gboolean
thunar_tree_view_model_has_default_sort_func (GtkTreeSortable *sortable);


/*************************************************
 * GtkTreeModel Virtual Methods
 *************************************************/
static GtkTreeModelFlags
thunar_tree_view_model_get_flags (GtkTreeModel *model);
static gint
thunar_tree_view_model_get_n_columns (GtkTreeModel *model);
static GType
thunar_tree_view_model_get_column_type (GtkTreeModel *model,
                                        gint          idx);
static gboolean
thunar_tree_view_model_get_iter (GtkTreeModel *model,
                                 GtkTreeIter  *iter,
                                 GtkTreePath  *path);
static GtkTreePath *
thunar_tree_view_model_get_path (GtkTreeModel *model,
                                 GtkTreeIter  *iter);
static void
thunar_tree_view_model_get_value (GtkTreeModel *model,
                                  GtkTreeIter  *iter,
                                  gint          column,
                                  GValue       *value);
static gboolean
thunar_tree_view_model_iter_next (GtkTreeModel *model,
                                  GtkTreeIter  *iter);
static gboolean
thunar_tree_view_model_iter_previous (GtkTreeModel *model,
                                      GtkTreeIter  *iter);
static gboolean
thunar_tree_view_model_iter_children (GtkTreeModel *model,
                                      GtkTreeIter  *iter,
                                      GtkTreeIter  *parent);
static gboolean
thunar_tree_view_model_iter_has_child (GtkTreeModel *model,
                                       GtkTreeIter  *iter);
static gint
thunar_tree_view_model_iter_n_children (GtkTreeModel *model,
                                        GtkTreeIter  *iter);
static gboolean
thunar_tree_view_model_iter_nth_child (GtkTreeModel *model,
                                       GtkTreeIter  *iter,
                                       GtkTreeIter  *parent,
                                       gint          n);
static gboolean
thunar_tree_view_model_iter_parent (GtkTreeModel *model,
                                    GtkTreeIter  *iter,
                                    GtkTreeIter  *child);


/*************************************************
 * Private Methods
 *************************************************/
/* Property Getters */
static gboolean
thunar_tree_view_model_get_case_sensitive (ThunarTreeViewModel *model);
static ThunarDateStyle
thunar_tree_view_model_get_date_style (ThunarTreeViewModel *model);
static const char *
thunar_tree_view_model_get_date_custom_style (ThunarTreeViewModel *model);
static gint
thunar_tree_view_model_get_num_files (ThunarTreeViewModel *model);
static gboolean
thunar_tree_view_model_get_folders_first (ThunarTreeViewModel *model);
static gboolean
thunar_tree_view_model_get_hidden_last (ThunarTreeViewModel *model);
static gint
thunar_tree_view_model_get_folder_item_count (ThunarTreeViewModel *model);
static gboolean
thunar_tree_view_model_get_loading (ThunarTreeViewModel *model);

/* Property Setters */
static void
thunar_tree_view_model_set_case_sensitive (ThunarTreeViewModel *model,
                                           gboolean             case_sensitive);
static void
thunar_tree_view_model_set_date_style (ThunarTreeViewModel *model,
                                       ThunarDateStyle      date_style);
static void
thunar_tree_view_model_set_date_custom_style (ThunarTreeViewModel *model,
                                              const char          *date_custom_style);
static void
thunar_tree_view_model_set_folder_item_count (ThunarTreeViewModel  *model,
                                              ThunarFolderItemCount count_as_dir_size);
static void
thunar_tree_view_model_set_expandable_folders (ThunarTreeViewModel *model,
                                               gboolean             expandable_folders);

/* Internal helper funcs */
static Node *
thunar_tree_view_model_new_node (ThunarFile *file);
static Node *
thunar_tree_view_model_new_dummy_node (void);
static gboolean
thunar_tree_view_model_node_has_dummy_child (Node *node);
static void
thunar_tree_view_model_node_add_child (Node *node,
                                       Node *child);
static void
thunar_tree_view_model_node_add_dummy_child (Node *node);
static void
thunar_tree_view_model_node_drop_dummy_child (Node *node);
static Node *
thunar_tree_view_model_dir_add_file (Node       *node,
                                     ThunarFile *file);
static void
thunar_tree_view_model_dir_remove_file (Node       *node,
                                        ThunarFile *file);
static Node *
thunar_tree_view_model_locate_file (ThunarTreeViewModel *model,
                                    ThunarFile          *file);
static gint
thunar_tree_view_model_cmp_nodes (gconstpointer a,
                                  gconstpointer b,
                                  gpointer      data);
static void
thunar_tree_view_model_sort (ThunarTreeViewModel *model);
static void
thunar_tree_view_model_load_dir (Node *node);
static void
thunar_tree_view_model_cleanup_model (ThunarTreeViewModel *model);
static void
thunar_tree_view_model_file_count_callback (ThunarJob           *job,
                                            ThunarTreeViewModel *model);
static void
thunar_tree_view_model_node_destroy (Node *node);
static void
thunar_tree_view_model_dir_files_changed (Node       *node,
                                          GHashTable *files);
static void
thunar_tree_view_model_set_loading (ThunarTreeViewModel *model,
                                    gboolean             loading);
static gboolean
thunar_tree_view_model_update_search_files (ThunarTreeViewModel *model);


/*************************************************
 *  Struct Definitions
 *************************************************/
struct _ThunarTreeViewModelClass
{
  GObjectClass __parent__;

  /* signals */
  void (*error) (ThunarTreeViewModel *model,
                 const GError        *error);
  void (*search_done) (void);
};



struct _ThunarTreeViewModel
{
  GObject __parent__;

  /* the model stamp is only used when debugging is
   * enabled, to make sure we don't accept iterators
   * generated by another model.
   */
#ifndef NDEBUG
  gint stamp;
#endif

  Node         *root;
  ThunarFolder *dir;

  /* Hashtable for quick access to all directories and sub-directories which are managed by this view
   * The key is the corresponding #ThunarFile of the directory, and the value is the #_Node of the directory */
  GHashTable *subdirs;

  gboolean       sort_case_sensitive : 1;
  gboolean       sort_folders_first : 1;
  gboolean       sort_hidden_last : 1;
  gint           sort_sign; /* 1 = ascending, -1 descending */
  ThunarSortFunc sort_func;

  /* Options */
  ThunarFolderItemCount folder_item_count;
  gboolean              file_size_binary : 1;
  ThunarDateStyle       date_style;
  char                 *date_custom_style;
  gboolean              show_hidden;
  gboolean              expandable_folders;

  gint n_visible_files;
  gint loading;

  gchar **search_terms;

  ThunarJob *search_job;
  GList     *search_files;
  GMutex     mutex_add_search_files;

  guint update_search_results_timeout_id;

  /* Separate ThunarJob to do the empty-checks, since doing so involves file IO */
  ThunarJob *check_empty_job;

  /* type: [ThunarFile*, gboolean* (is_empty)] */
  /* The 'is_empty' values of this GHashtable are filled by the 'check_empty_job' */
  /* As such, do not access the GHashTable while the job is running */
  GHashTable *files_for_empty_check;
};



struct _Node
{
  ThunarFile   *file;
  ThunarFolder *dir;

  Node          *parent;
  GSequenceIter *ptr; /* self ref */

  gint     depth;
  gint     n_children;
  gboolean loaded;
  CanExpand can_expand;
  gboolean file_watch_active;

  /* set of all the children gfiles;
   * contains mappings of (gfile -> GSequenceIter *(_child_node->ptr)) */
  GHashTable *set;
  GHashTable *hidden_files;

  GSequence           *children; /* Nodes */
  ThunarTreeViewModel *model;

  guint scheduled_unload_id;
};

struct _MatchForeach
{
  GList        *paths;
  GPatternSpec *pspec;
  gboolean      match_diacritics;
  gboolean      case_sensitive;
};



/*************************************************
 *  GType Definitions
 *************************************************/
G_DEFINE_TYPE_WITH_CODE (ThunarTreeViewModel, thunar_tree_view_model, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, thunar_tree_view_model_tree_model_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_DEST, thunar_tree_view_model_drag_dest_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE, thunar_tree_view_model_sortable_init))



/* Reference to GParamSpec(s) of all the N_PROPERTIES */
static GParamSpec *tree_model_props[N_PROPERTIES] = {
  NULL,
};

static guint model_signals[THUNAR_TREE_VIEW_MODEL_LAST_SIGNAL];



static void
thunar_tree_view_model_class_init (ThunarTreeViewModelClass *klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->dispose = thunar_tree_view_model_dispose;
  gobject_class->finalize = thunar_tree_view_model_finalize;
  gobject_class->get_property = thunar_tree_view_model_get_property;
  gobject_class->set_property = thunar_tree_view_model_set_property;



  tree_model_props[PROP_CASE_SENSITIVE] = g_param_spec_boolean ("case-sensitive",
                                                                "case-sensitive",
                                                                "case-sensitive",
                                                                TRUE,
                                                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  tree_model_props[PROP_DATE_STYLE] = g_param_spec_enum ("date-style",
                                                         "date-style",
                                                         "date-style",
                                                         THUNAR_TYPE_DATE_STYLE,
                                                         THUNAR_DATE_STYLE_SIMPLE,
                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  tree_model_props[PROP_DATE_CUSTOM_STYLE] = g_param_spec_string ("date-custom-style",
                                                                  "DateCustomStyle",
                                                                  NULL,
                                                                  "%Y-%m-%d %H:%M:%S",
                                                                  G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  tree_model_props[PROP_FOLDER] = g_param_spec_object ("folder",
                                                       "folder",
                                                       "folder",
                                                       THUNAR_TYPE_FOLDER,
                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  tree_model_props[PROP_FOLDERS_FIRST] = g_param_spec_boolean ("folders-first",
                                                               "folders-first",
                                                               "folders-first",
                                                               TRUE,
                                                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  tree_model_props[PROP_HIDDEN_LAST] = g_param_spec_boolean ("hidden-last",
                                                             "hidden-last",
                                                             "hidden-last",
                                                             FALSE,
                                                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  tree_model_props[PROP_NUM_FILES] = g_param_spec_uint ("num-files",
                                                        "num-files",
                                                        "num-files",
                                                        0, G_MAXUINT, 0,
                                                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

  tree_model_props[PROP_SHOW_HIDDEN] = g_param_spec_boolean ("show-hidden",
                                                             "show-hidden",
                                                             "show-hidden",
                                                             FALSE,
                                                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  tree_model_props[PROP_FILE_SIZE_BINARY] = g_param_spec_boolean ("file-size-binary",
                                                                  "file-size-binary",
                                                                  "file-size-binary",
                                                                  TRUE,
                                                                  G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  tree_model_props[PROP_FOLDER_ITEM_COUNT] = g_param_spec_enum ("folder-item-count",
                                                                "folder-item-count",
                                                                "folder-item-count",
                                                                THUNAR_TYPE_FOLDER_ITEM_COUNT,
                                                                TRUE,
                                                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  tree_model_props[PROP_LOADING] = g_param_spec_boolean ("loading",
                                                         "loading",
                                                         "loading",
                                                         FALSE,
                                                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

  tree_model_props[PROP_EXPANDABLE_FOLDERS] = g_param_spec_boolean ("expandable-folders",
                                                                    "expandable-folders",
                                                                    "expandable-folders",
                                                                    FALSE,
                                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);


  /**
   * ThunarTreeViewModel:case-sensitive:
   *
   * Tells whether the sorting should be case sensitive.
   **/
  g_object_class_install_property (gobject_class, PROP_CASE_SENSITIVE,
                                   tree_model_props[PROP_CASE_SENSITIVE]);

  /**
   * ThunarTreeViewModel:date-style:
   *
   * The style used to format dates.
   **/
  g_object_class_install_property (gobject_class, PROP_DATE_STYLE,
                                   tree_model_props[PROP_DATE_STYLE]);

  /**
   * ThunarTreeViewModel:date-custom-style:
   *
   * The style used for custom format of dates.
   **/
  g_object_class_install_property (gobject_class, PROP_DATE_CUSTOM_STYLE,
                                   tree_model_props[PROP_DATE_CUSTOM_STYLE]);

  /**
   * ThunarTreeViewModel:folder:
   *
   * The folder presented by this #ThunarTreeViewModel.
   **/
  g_object_class_install_property (gobject_class, PROP_FOLDER,
                                   tree_model_props[PROP_FOLDER]);

  /**
   * ThunarTreeViewModel::folders-first:
   *
   * Tells whether to always sort folders before other files.
   **/
  g_object_class_install_property (gobject_class, PROP_FOLDERS_FIRST,
                                   tree_model_props[PROP_FOLDERS_FIRST]);
  /**
   * ThunarTreeViewModel::hidden-last:
   *
   * Tells whether to always sort hidden files after other files.
   **/
  g_object_class_install_property (gobject_class, PROP_HIDDEN_LAST,
                                   tree_model_props[PROP_HIDDEN_LAST]);

  /**
   * ThunarTreeViewModel::num-files:
   *
   * The number of files in the folder presented by this #ThunarTreeViewModel.
   **/
  g_object_class_install_property (gobject_class, PROP_NUM_FILES,
                                   tree_model_props[PROP_NUM_FILES]);

  /**
   * ThunarTreeViewModel::show-hidden:
   *
   * Tells whether to include hidden (and backup) files.
   **/
  g_object_class_install_property (gobject_class, PROP_SHOW_HIDDEN,
                                   tree_model_props[PROP_SHOW_HIDDEN]);

  /**
   * ThunarTreeViewModel::file-size-binary:
   *
   * Tells whether to format file size in binary.
   **/
  g_object_class_install_property (gobject_class, PROP_FILE_SIZE_BINARY,
                                   tree_model_props[PROP_FILE_SIZE_BINARY]);

  /**
   * ThunarTreeViewModel:folder-item-count:
   *
   * Tells when the size column of folders should show the number of containing files
   **/
  g_object_class_install_property (gobject_class, PROP_FOLDER_ITEM_COUNT,
                                   tree_model_props[PROP_FOLDER_ITEM_COUNT]);

  /**
   * ThunarTreeViewModel:loading:
   *
   * Tells if the model is yet loading a folder
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_LOADING,
                                   tree_model_props[PROP_LOADING]);

  /**
   * ThunarTreeViewModel:expandable-folders
   *
   * %TRUE to enable expandable folders
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_EXPANDABLE_FOLDERS,
                                   tree_model_props[PROP_EXPANDABLE_FOLDERS]);

  /**
   * ThunarTreeViewModel::error:
   * @store : a #ThunarTreeViewModel.
   * @error : a #GError that describes the problem.
   *
   * Emitted when an error occurs while loading the
   * @store content.
   **/
  model_signals[THUNAR_TREE_VIEW_MODEL_ERROR] =
  g_signal_new (I_ ("error"),
                G_TYPE_FROM_CLASS (gobject_class),
                G_SIGNAL_RUN_LAST,
                G_STRUCT_OFFSET (ThunarTreeViewModelClass, error),
                NULL, NULL,
                g_cclosure_marshal_VOID__POINTER,
                G_TYPE_NONE, 1, G_TYPE_POINTER);

  /**
   * ThunarTreeViewModel::search-done:
   * @store : a #ThunarTreeViewModel.
   * @error : a #GError that describes the problem.
   *
   * Emitted when a recursive search finishes.
   **/
  model_signals[THUNAR_TREE_VIEW_MODEL_SEARCH_DONE] =
  g_signal_new (I_ ("search-done"),
                G_TYPE_FROM_CLASS (gobject_class),
                G_SIGNAL_RUN_LAST,
                G_STRUCT_OFFSET (ThunarTreeViewModelClass, search_done),
                NULL, NULL,
                NULL,
                G_TYPE_NONE, 0);
}



static void
thunar_tree_view_model_init (ThunarTreeViewModel *model)
{
  /* generate a unique stamp if we're in debug mode */
#ifndef NDEBUG
  model->stamp = g_random_int ();
#endif

  model->search_job = NULL;
  model->update_search_results_timeout_id = 0;

  model->search_terms = NULL;
  model->search_files = NULL;
  g_mutex_init (&model->mutex_add_search_files);

  model->sort_func = thunar_file_compare_by_name;
  model->loading = 0;

  model->subdirs = g_hash_table_new (g_direct_hash, g_direct_equal);

  /* Hashtable data type: [ThunarFile*, gboolean*] */
  model->files_for_empty_check = g_hash_table_new_full (g_direct_hash,
                                                        g_direct_equal,
                                                        (GDestroyNotify) g_object_unref,
                                                        g_free);
}



static void
thunar_tree_view_model_drag_dest_init (GtkTreeDragDestIface *iface)
{
  iface->drag_data_received = thunar_tree_view_model_drag_data_received;
  iface->row_drop_possible = thunar_tree_view_model_row_drop_possible;
}



static void
thunar_tree_view_model_sortable_init (GtkTreeSortableIface *iface)
{
  iface->get_sort_column_id = thunar_tree_view_model_get_sort_column_id;
  iface->set_sort_column_id = thunar_tree_view_model_set_sort_column_id;
  iface->set_sort_func = thunar_tree_view_model_set_sort_func;
  iface->set_default_sort_func = thunar_tree_view_model_set_default_sort_func;
  iface->has_default_sort_func = thunar_tree_view_model_has_default_sort_func;
}


static void
thunar_tree_view_model_tree_model_init (GtkTreeModelIface *iface)
{
  iface->get_flags = thunar_tree_view_model_get_flags;
  iface->get_n_columns = thunar_tree_view_model_get_n_columns;
  iface->get_column_type = thunar_tree_view_model_get_column_type;
  iface->get_iter = thunar_tree_view_model_get_iter;
  iface->get_path = thunar_tree_view_model_get_path;
  iface->get_value = thunar_tree_view_model_get_value;
  iface->iter_next = thunar_tree_view_model_iter_next;
  iface->iter_previous = thunar_tree_view_model_iter_previous;
  iface->iter_children = thunar_tree_view_model_iter_children;
  iface->iter_has_child = thunar_tree_view_model_iter_has_child;
  iface->iter_n_children = thunar_tree_view_model_iter_n_children;
  iface->iter_nth_child = thunar_tree_view_model_iter_nth_child;
  iface->iter_parent = thunar_tree_view_model_iter_parent;
}



/*************************************************
 *  GObject Virtual Func definitions
 *************************************************/
static void
thunar_tree_view_model_dispose (GObject *object)
{
  /* unlink from the folder (if any) */
  thunar_tree_view_model_set_folder (THUNAR_TREE_VIEW_MODEL (object), NULL, NULL);

  (*G_OBJECT_CLASS (thunar_tree_view_model_parent_class)->dispose) (object);
}



static void
thunar_tree_view_model_finalize (GObject *object)
{
  ThunarTreeViewModel *model = THUNAR_TREE_VIEW_MODEL (object);

  /* this will cancel the search job if any*/
  thunar_tree_view_model_set_folder (THUNAR_TREE_VIEW_MODEL (object),
                                     NULL, NULL);

  g_mutex_clear (&model->mutex_add_search_files);

  /* stop check empty job and clear job data */
  if (G_UNLIKELY (model->check_empty_job != NULL))
    {
      thunar_job_cancel (THUNAR_JOB (model->check_empty_job));
      g_object_unref (model->check_empty_job);
      model->check_empty_job = NULL;
    }

  g_hash_table_destroy (model->files_for_empty_check);

  g_free (model->date_custom_style);
  g_strfreev (model->search_terms);

  g_hash_table_destroy (model->subdirs);

  (*G_OBJECT_CLASS (thunar_tree_view_model_parent_class)->finalize) (object);
}



static void
thunar_tree_view_model_get_property (GObject    *object,
                                     guint       prop_id,
                                     GValue     *value,
                                     GParamSpec *pspec)
{
  ThunarTreeViewModel *model = THUNAR_TREE_VIEW_MODEL (object);

  switch (prop_id)
    {
    case PROP_CASE_SENSITIVE:
      g_value_set_boolean (value, thunar_tree_view_model_get_case_sensitive (model));
      break;

    case PROP_DATE_STYLE:
      g_value_set_enum (value, thunar_tree_view_model_get_date_style (model));
      break;

    case PROP_DATE_CUSTOM_STYLE:
      g_value_set_string (value, thunar_tree_view_model_get_date_custom_style (model));
      break;

    case PROP_FOLDER:
      g_value_set_object (value, thunar_tree_view_model_get_folder (THUNAR_TREE_VIEW_MODEL (model)));
      break;

    case PROP_FOLDERS_FIRST:
      g_value_set_boolean (value, thunar_tree_view_model_get_folders_first (model));
      break;

    case PROP_HIDDEN_LAST:
      g_value_set_boolean (value, thunar_tree_view_model_get_hidden_last (model));
      break;

    case PROP_NUM_FILES:
      g_value_set_uint (value, thunar_tree_view_model_get_num_files (model));
      break;

    case PROP_SHOW_HIDDEN:
      g_value_set_boolean (value, thunar_tree_view_model_get_show_hidden (THUNAR_TREE_VIEW_MODEL (model)));
      break;

    case PROP_FILE_SIZE_BINARY:
      g_value_set_boolean (value, thunar_tree_view_model_get_file_size_binary (THUNAR_TREE_VIEW_MODEL (model)));
      break;

    case PROP_FOLDER_ITEM_COUNT:
      g_value_set_enum (value, thunar_tree_view_model_get_folder_item_count (model));
      break;

    case PROP_LOADING:
      g_value_set_boolean (value, thunar_tree_view_model_get_loading (model));
      break;

    case PROP_EXPANDABLE_FOLDERS:
      g_value_set_boolean (value, model->expandable_folders);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}



static void
thunar_tree_view_model_set_property (GObject      *object,
                                     guint         prop_id,
                                     const GValue *value,
                                     GParamSpec   *pspec)
{
  ThunarTreeViewModel *model = THUNAR_TREE_VIEW_MODEL (object);

  switch (prop_id)
    {
    case PROP_CASE_SENSITIVE:
      thunar_tree_view_model_set_case_sensitive (model, g_value_get_boolean (value));
      break;

    case PROP_DATE_STYLE:
      thunar_tree_view_model_set_date_style (model, g_value_get_enum (value));
      break;

    case PROP_DATE_CUSTOM_STYLE:
      thunar_tree_view_model_set_date_custom_style (model, g_value_get_string (value));
      break;

    case PROP_FOLDER:
      thunar_tree_view_model_set_folder (THUNAR_TREE_VIEW_MODEL (model), g_value_get_object (value), NULL);
      break;

    case PROP_FOLDERS_FIRST:
      thunar_tree_view_model_set_folders_first (THUNAR_TREE_VIEW_MODEL (model), g_value_get_boolean (value));
      break;

    case PROP_HIDDEN_LAST:
      thunar_tree_view_model_set_hidden_last (THUNAR_TREE_VIEW_MODEL (model), g_value_get_boolean (value));
      break;

    case PROP_SHOW_HIDDEN:
      thunar_tree_view_model_set_show_hidden (THUNAR_TREE_VIEW_MODEL (model), g_value_get_boolean (value));
      break;

    case PROP_FILE_SIZE_BINARY:
      thunar_tree_view_model_set_file_size_binary (THUNAR_TREE_VIEW_MODEL (model), g_value_get_boolean (value));
      break;

    case PROP_FOLDER_ITEM_COUNT:
      thunar_tree_view_model_set_folder_item_count (model, g_value_get_enum (value));
      break;

    case PROP_EXPANDABLE_FOLDERS:
      thunar_tree_view_model_set_expandable_folders (THUNAR_TREE_VIEW_MODEL (model), g_value_get_boolean (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}



/*************************************************
 *  GtkTreeDragDest Virtual Func definitions
 *************************************************/
static gboolean
thunar_tree_view_model_drag_data_received (GtkTreeDragDest  *dest,
                                           GtkTreePath      *path,
                                           GtkSelectionData *data)
{
  return FALSE;
}



static gboolean
thunar_tree_view_model_row_drop_possible (GtkTreeDragDest  *dest,
                                          GtkTreePath      *path,
                                          GtkSelectionData *data)
{
  return FALSE;
}



/*************************************************
 *  GtkSortable Virtual Func definitions
 *************************************************/
static gboolean
thunar_tree_view_model_get_sort_column_id (GtkTreeSortable *sortable,
                                           gint            *sort_column_id,
                                           GtkSortType     *order)
{
  ThunarTreeViewModel *model = THUNAR_TREE_VIEW_MODEL (sortable);

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);

  if (model->sort_func == thunar_cmp_files_by_mime_type)
    *sort_column_id = THUNAR_COLUMN_MIME_TYPE;
  else if (model->sort_func == thunar_file_compare_by_name)
    *sort_column_id = THUNAR_COLUMN_NAME;
  else if (model->sort_func == thunar_cmp_files_by_permissions)
    *sort_column_id = THUNAR_COLUMN_PERMISSIONS;
  else if (model->sort_func == thunar_cmp_files_by_size || model->sort_func == (ThunarSortFunc) thunar_cmp_files_by_size_and_items_count)
    *sort_column_id = THUNAR_COLUMN_SIZE;
  else if (model->sort_func == thunar_cmp_files_by_size_in_bytes)
    *sort_column_id = THUNAR_COLUMN_SIZE_IN_BYTES;
  else if (model->sort_func == thunar_cmp_files_by_date_created)
    *sort_column_id = THUNAR_COLUMN_DATE_CREATED;
  else if (model->sort_func == thunar_cmp_files_by_date_accessed)
    *sort_column_id = THUNAR_COLUMN_DATE_ACCESSED;
  else if (model->sort_func == thunar_cmp_files_by_date_modified)
    *sort_column_id = THUNAR_COLUMN_DATE_MODIFIED;
  else if (model->sort_func == thunar_cmp_files_by_date_deleted)
    *sort_column_id = THUNAR_COLUMN_DATE_DELETED;
  else if (model->sort_func == thunar_cmp_files_by_recency)
    *sort_column_id = THUNAR_COLUMN_RECENCY;
  else if (model->sort_func == thunar_cmp_files_by_location)
    *sort_column_id = THUNAR_COLUMN_LOCATION;
  else if (model->sort_func == thunar_cmp_files_by_type)
    *sort_column_id = THUNAR_COLUMN_TYPE;
  else if (model->sort_func == thunar_cmp_files_by_owner)
    *sort_column_id = THUNAR_COLUMN_OWNER;
  else if (model->sort_func == thunar_cmp_files_by_group)
    *sort_column_id = THUNAR_COLUMN_GROUP;
  else
    _thunar_assert_not_reached ();

  if (order != NULL)
    {
      if (model->sort_sign > 0)
        *order = GTK_SORT_ASCENDING;
      else
        *order = GTK_SORT_DESCENDING;
    }

  return TRUE;
}



static void
thunar_tree_view_model_set_sort_column_id (GtkTreeSortable *sortable,
                                           gint             sort_column_id,
                                           GtkSortType      order)
{
  ThunarTreeViewModel *model = THUNAR_TREE_VIEW_MODEL (sortable);

  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  switch (sort_column_id)
    {
    case THUNAR_COLUMN_DATE_CREATED:
      model->sort_func = thunar_cmp_files_by_date_created;
      break;

    case THUNAR_COLUMN_DATE_ACCESSED:
      model->sort_func = thunar_cmp_files_by_date_accessed;
      break;

    case THUNAR_COLUMN_DATE_MODIFIED:
      model->sort_func = thunar_cmp_files_by_date_modified;
      break;

    case THUNAR_COLUMN_DATE_DELETED:
      model->sort_func = thunar_cmp_files_by_date_deleted;
      break;

    case THUNAR_COLUMN_RECENCY:
      model->sort_func = thunar_cmp_files_by_recency;
      break;

    case THUNAR_COLUMN_LOCATION:
      model->sort_func = thunar_cmp_files_by_location;
      break;

    case THUNAR_COLUMN_GROUP:
      model->sort_func = thunar_cmp_files_by_group;
      break;

    case THUNAR_COLUMN_MIME_TYPE:
      model->sort_func = thunar_cmp_files_by_mime_type;
      break;

    case THUNAR_COLUMN_FILE_NAME:
    case THUNAR_COLUMN_NAME:
      model->sort_func = thunar_file_compare_by_name;
      break;

    case THUNAR_COLUMN_OWNER:
      model->sort_func = thunar_cmp_files_by_owner;
      break;

    case THUNAR_COLUMN_PERMISSIONS:
      model->sort_func = thunar_cmp_files_by_permissions;
      break;

    case THUNAR_COLUMN_SIZE:
      model->sort_func = (model->folder_item_count != THUNAR_FOLDER_ITEM_COUNT_NEVER) ? (ThunarSortFunc) thunar_cmp_files_by_size_and_items_count : thunar_cmp_files_by_size;
      break;

    case THUNAR_COLUMN_SIZE_IN_BYTES:
      model->sort_func = thunar_cmp_files_by_size_in_bytes;
      break;

    case THUNAR_COLUMN_TYPE:
      model->sort_func = thunar_cmp_files_by_type;
      break;

    default:
      _thunar_assert_not_reached ();
    }

  /* new sort sign */
  model->sort_sign = (order == GTK_SORT_ASCENDING) ? 1 : -1;

  /* re-sort the model */
  if (model->root != NULL)
    thunar_tree_view_model_sort (model);

  /* notify listining parties */
  gtk_tree_sortable_sort_column_changed (sortable);
}



static void
thunar_tree_view_model_set_default_sort_func (GtkTreeSortable       *sortable,
                                              GtkTreeIterCompareFunc func,
                                              gpointer               data,
                                              GDestroyNotify         destroy)
{
  g_critical ("ThunarTreeViewModel has sorting facilities built-in!");
}



static void
thunar_tree_view_model_set_sort_func (GtkTreeSortable       *sortable,
                                      gint                   sort_column_id,
                                      GtkTreeIterCompareFunc func,
                                      gpointer               data,
                                      GDestroyNotify         destroy)
{
  g_critical ("ThunarTreeViewModel has sorting facilities built-in!");
}



static gboolean
thunar_tree_view_model_has_default_sort_func (GtkTreeSortable *sortable)
{
  return FALSE;
}



/*************************************************
 *  GtkTreeModel Virtual Func definitions
 *************************************************/
static GtkTreeModelFlags
thunar_tree_view_model_get_flags (GtkTreeModel *model)
{
  return GTK_TREE_MODEL_ITERS_PERSIST;
}



static gint
thunar_tree_view_model_get_n_columns (GtkTreeModel *model)
{
  return THUNAR_N_COLUMNS;
}



static GType
thunar_tree_view_model_get_column_type (GtkTreeModel *model,
                                        gint          idx)
{
  switch (idx)
    {
    case THUNAR_COLUMN_DATE_CREATED:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_DATE_ACCESSED:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_DATE_MODIFIED:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_DATE_DELETED:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_RECENCY:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_LOCATION:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_GROUP:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_MIME_TYPE:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_NAME:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_OWNER:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_PERMISSIONS:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_SIZE:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_SIZE_IN_BYTES:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_TYPE:
      return G_TYPE_STRING;

    case THUNAR_COLUMN_FILE:
      return THUNAR_TYPE_FILE;

    case THUNAR_COLUMN_FILE_NAME:
      return G_TYPE_STRING;
    }

  _thunar_assert_not_reached ();
  return G_TYPE_INVALID;
}



static gboolean
thunar_tree_view_model_get_iter (GtkTreeModel *model,
                                 GtkTreeIter  *iter,
                                 GtkTreePath  *path)
{
  ThunarTreeViewModel *_model;
  GSequenceIter       *ptr = NULL;
  gint                *indices;
  gint                 depth, loc;
  Node                *node;

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);
  _thunar_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);

  _model = THUNAR_TREE_VIEW_MODEL (model);

  if (_model->root == NULL)
    return FALSE;

  indices = gtk_tree_path_get_indices_with_depth (path, &depth);
  node = _model->root;
  for (gint d = 0; d < depth; d++)
    {
      loc = indices[d];

      if (loc < 0 || loc >= node->n_children)
        return FALSE;

      ptr = g_sequence_get_iter_at_pos (node->children, loc);
      if (g_sequence_iter_is_end (ptr))
        return FALSE;
      node = g_sequence_get (ptr);
    }

  GTK_TREE_ITER_INIT (*iter, _model->stamp, ptr);

  return TRUE;
}



static GtkTreePath *
thunar_tree_view_model_get_path (GtkTreeModel *model,
                                 GtkTreeIter  *iter)
{
  GtkTreePath *path;
  Node        *node;
  gint        *indices;
  gint         depth;

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), NULL);
  _thunar_return_val_if_fail (iter->stamp == THUNAR_TREE_VIEW_MODEL (model)->stamp, NULL);
  _thunar_return_val_if_fail (iter->user_data != NULL, NULL);

  THUNAR_WARN_RETURN_VAL (g_sequence_iter_is_end (iter->user_data), NULL);

  node = g_sequence_get (iter->user_data);

  depth = node->depth;
  indices = g_malloc_n (depth, sizeof (gint));

  for (gint d = depth - 1; d >= 0; --d)
    {
      indices[d] = g_sequence_iter_get_position (node->ptr);
      node = node->parent;
    }

  path = gtk_tree_path_new_from_indicesv (indices, depth);
  g_free (indices);
  return path;
}



static void
thunar_tree_view_model_get_value (GtkTreeModel *model,
                                  GtkTreeIter  *iter,
                                  gint          column,
                                  GValue       *value)
{
  Node         *node;
  ThunarGroup  *group = NULL;
  const gchar  *device_type;
  const gchar  *name;
  const gchar  *real_name;
  ThunarUser   *user = NULL;
  ThunarFolder *folder;
  gint32        item_count;
  GFile        *g_file;
  GFile        *g_file_parent = NULL;
  gchar        *str = NULL;
  ThunarFile   *file = NULL;

  _thunar_return_if_fail (THUNAR_TREE_VIEW_MODEL (model));
  _thunar_return_if_fail (iter->stamp == (THUNAR_TREE_VIEW_MODEL (model))->stamp);

  node = g_sequence_get (iter->user_data);
  if (node != NULL)
    {
      file = node->file;
      if (file != NULL)
        g_object_ref (file);
    }
  else
    {
      /* should not happen, but already caused unreproducible segfault */
      g_warn_if_reached ();
    }

  switch (column)
    {
    case THUNAR_COLUMN_DATE_CREATED:
      g_value_init (value, G_TYPE_STRING);
      if (file == NULL)
        {
          g_value_set_static_string (value, "");
          break;
        }
      str = thunar_file_get_date_string (file, THUNAR_FILE_DATE_CREATED, THUNAR_TREE_VIEW_MODEL (model)->date_style, THUNAR_TREE_VIEW_MODEL (model)->date_custom_style);
      g_value_take_string (value, str); /* take str is nullable */
      break;

    case THUNAR_COLUMN_DATE_ACCESSED:
      g_value_init (value, G_TYPE_STRING);
      if (file == NULL)
        {
          g_value_set_static_string (value, "");
          break;
        }
      str = thunar_file_get_date_string (file, THUNAR_FILE_DATE_ACCESSED, THUNAR_TREE_VIEW_MODEL (model)->date_style, THUNAR_TREE_VIEW_MODEL (model)->date_custom_style);
      g_value_take_string (value, str);
      break;

    case THUNAR_COLUMN_DATE_MODIFIED:
      g_value_init (value, G_TYPE_STRING);
      if (file == NULL)
        {
          g_value_set_static_string (value, "");
          break;
        }
      str = thunar_file_get_date_string (file, THUNAR_FILE_DATE_MODIFIED, THUNAR_TREE_VIEW_MODEL (model)->date_style, THUNAR_TREE_VIEW_MODEL (model)->date_custom_style);
      g_value_take_string (value, str);
      break;

    case THUNAR_COLUMN_DATE_DELETED:
      g_value_init (value, G_TYPE_STRING);
      if (file == NULL)
        {
          g_value_set_static_string (value, "");
          break;
        }
      str = thunar_file_get_date_string (file, THUNAR_FILE_DATE_DELETED, THUNAR_TREE_VIEW_MODEL (model)->date_style, THUNAR_TREE_VIEW_MODEL (model)->date_custom_style);
      g_value_take_string (value, str);
      break;

    case THUNAR_COLUMN_RECENCY:
      g_value_init (value, G_TYPE_STRING);
      if (file == NULL)
        {
          g_value_set_static_string (value, "");
          break;
        }
      str = thunar_file_get_date_string (file, THUNAR_FILE_RECENCY, THUNAR_TREE_VIEW_MODEL (model)->date_style, THUNAR_TREE_VIEW_MODEL (model)->date_custom_style);
      g_value_take_string (value, str);
      break;

    case THUNAR_COLUMN_LOCATION:
      g_value_init (value, G_TYPE_STRING);
      /* NOTE: return val can be null due to file not having been loaded yet.
       * The non visible children are lazily loaded. */
      if (file != NULL)
        g_file_parent = g_file_get_parent (thunar_file_get_file (file));
      str = NULL;

      /* g_file_parent will be NULL if a search returned the root
       * directory somehow, or "file:///" is in recent:/// somehow.
       * These should be quite rare circumstances. */
      if (G_UNLIKELY (g_file_parent == NULL))
        {
          g_value_take_string (value, NULL);
          break;
        }

      /* Try and show a relative path beginning with the current folder's name to the parent folder.
       * Fall thru with str==NULL if that is not possible. */
      folder = THUNAR_TREE_VIEW_MODEL (model)->dir;
      if (G_LIKELY (folder != NULL))
        {
          const gchar *folder_basename = thunar_file_get_basename (thunar_folder_get_corresponding_file (folder));
          GFile       *g_folder = thunar_file_get_file (thunar_folder_get_corresponding_file (folder));
          if (g_file_equal (g_folder, g_file_parent))
            {
              /* commonest non-prefix case: item location is directly inside the search folder */
              str = g_strdup (folder_basename);
            }
          else
            {
              str = g_file_get_relative_path (g_folder, g_file_parent);
              /* str can still be NULL if g_folder is not a prefix of g_file_parent */
              if (str != NULL)
                {
                  gchar *tmp = g_build_path (G_DIR_SEPARATOR_S, folder_basename, str, NULL);
                  g_free (str);
                  str = tmp;
                }
            }
        }

      /* catchall for when model->folder is not an ancestor of the parent (e.g. when searching recent:///).
       * In this case, show a prettified absolute URI or local path. */
      if (str == NULL)
        str = g_file_get_parse_name (g_file_parent);

      g_object_unref (g_file_parent);
      g_value_take_string (value, str);
      break;

    case THUNAR_COLUMN_GROUP:
      g_value_init (value, G_TYPE_STRING);
      if (file != NULL)
        group = thunar_file_get_group (file);
      if (G_LIKELY (group != NULL))
        {
          g_value_set_string (value, thunar_group_get_name (group));
          g_object_unref (G_OBJECT (group));
        }
      else
        {
          g_value_set_static_string (value, "");
        }
      break;

    case THUNAR_COLUMN_MIME_TYPE:
      g_value_init (value, G_TYPE_STRING);
      if (file == NULL)
        {
          g_value_set_static_string (value, "");
          break;
        }
      g_value_set_static_string (value, thunar_file_get_content_type (file));
      break;

    case THUNAR_COLUMN_NAME:
      g_value_init (value, G_TYPE_STRING);
      if (file == NULL)
        {
          g_value_set_static_string (value, _ ("Loading..."));
          break;
        }
      g_value_set_static_string (value, thunar_file_get_display_name (file));
      break;

    case THUNAR_COLUMN_OWNER:
      g_value_init (value, G_TYPE_STRING);
      if (file != NULL)
        user = thunar_file_get_user (file);
      if (G_LIKELY (user != NULL))
        {
          /* determine sane display name for the owner */
          name = thunar_user_get_name (user);
          real_name = thunar_user_get_real_name (user);
          if (G_LIKELY (real_name != NULL))
            {
              if (strcmp (name, real_name) == 0)
                str = g_strdup (name);
              else
                str = g_strdup_printf ("%s (%s)", real_name, name);
            }
          else
            str = g_strdup (name);
          g_value_take_string (value, str);
          g_object_unref (G_OBJECT (user));
        }
      else
        {
          g_value_set_static_string (value, "");
        }
      break;

    case THUNAR_COLUMN_PERMISSIONS:
      g_value_init (value, G_TYPE_STRING);
      if (file == NULL)
        {
          g_value_set_static_string (value, "");
          break;
        }
      g_value_take_string (value, thunar_file_get_mode_string (file));
      break;

    case THUNAR_COLUMN_SIZE:
      g_value_init (value, G_TYPE_STRING);
      if (file == NULL)
        {
          g_value_set_static_string (value, "");
          break;
        }
      if (thunar_file_is_mountable (file))
        {
          ThunarFilesystemSpaceInfo fs_size_info;
          gchar                    *disk_usage;

          g_file = thunar_file_get_target_location (file);
          if (g_file == NULL)
            break;

          thunar_g_file_get_fs_space (g_file, &fs_size_info);
          disk_usage = thunar_g_file_get_free_space_string (&fs_size_info, THUNAR_TREE_VIEW_MODEL (model)->file_size_binary);
          g_value_take_string (value, disk_usage);
          g_object_unref (g_file);
          break;
        }
      else if (thunar_file_is_directory (file))
        {
          if (THUNAR_TREE_VIEW_MODEL (model)->folder_item_count == THUNAR_FOLDER_ITEM_COUNT_ALWAYS)
            {
              item_count = thunar_file_get_file_count (file, G_CALLBACK (thunar_tree_view_model_file_count_callback), model);
              if (item_count < 0)
                g_value_take_string (value, g_strdup (_("unknown")));
              else
                g_value_take_string (value, g_strdup_printf (ngettext ("%u item", "%u items", item_count), item_count));
            }

          /* If the option is set to always show folder sizes as item counts only for local files,
           * check if the files is local or not, and act accordingly */
          else if (THUNAR_TREE_VIEW_MODEL (model)->folder_item_count == THUNAR_FOLDER_ITEM_COUNT_ONLY_LOCAL)
            {
              if (thunar_file_is_local (file))
                {
                  item_count = thunar_file_get_file_count (file, G_CALLBACK (thunar_tree_view_model_file_count_callback), model);
                  if (item_count < 0)
                    g_value_take_string (value, g_strdup (_("unknown")));
                  else
                    g_value_take_string (value, g_strdup_printf (ngettext ("%u item", "%u items", item_count), item_count));
                }
              else
                g_value_take_string (value, thunar_file_get_size_string_formatted (file, THUNAR_TREE_VIEW_MODEL (model)->file_size_binary));
            }
          else if (THUNAR_TREE_VIEW_MODEL (model)->folder_item_count != THUNAR_FOLDER_ITEM_COUNT_NEVER)
            g_warning ("Error, unknown enum value for folder_item_count in the list model");
        }
      else
        {
          g_value_take_string (value, thunar_file_get_size_string_formatted (file, THUNAR_TREE_VIEW_MODEL (model)->file_size_binary));
        }
      break;

    case THUNAR_COLUMN_SIZE_IN_BYTES:
      g_value_init (value, G_TYPE_STRING);
      if (file == NULL)
        {
          g_value_set_static_string (value, "");
          break;
        }
      g_value_take_string (value, thunar_file_get_size_in_bytes_string (file));
      break;

    case THUNAR_COLUMN_TYPE:
      g_value_init (value, G_TYPE_STRING);
      if (file == NULL)
        {
          g_value_set_static_string (value, "");
          break;
        }
      device_type = thunar_file_get_device_type (file);
      if (device_type != NULL)
        {
          g_value_take_string (value, g_strdup (device_type));
          break;
        }
      g_value_take_string (value, thunar_file_get_content_type_desc (file, TRUE));
      break;

    case THUNAR_COLUMN_FILE:
      g_value_init (value, THUNAR_TYPE_FILE);
      g_value_set_object (value, file);
      break;

    case THUNAR_COLUMN_FILE_NAME:
      g_value_init (value, G_TYPE_STRING);
      if (file == NULL)
        {
          g_value_set_static_string (value, _ ("Loading..."));
          break;
        }
      g_value_set_static_string (value, thunar_file_get_display_name (file));
      break;

    default:
      _thunar_assert_not_reached ();
      break;
    }

  if (file != NULL)
    g_object_unref (file);
}



static gboolean
thunar_tree_view_model_iter_next (GtkTreeModel *model,
                                  GtkTreeIter  *iter)
{
  GSequenceIter *ptr;

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);
  _thunar_return_val_if_fail (iter->stamp == THUNAR_TREE_VIEW_MODEL (model)->stamp, FALSE);
  _thunar_return_val_if_fail (iter->user_data != NULL, FALSE);

  THUNAR_WARN_RETURN_VAL (g_sequence_iter_is_end (iter->user_data), FALSE);

  ptr = g_sequence_iter_next (iter->user_data);
  if (g_sequence_iter_is_end (ptr))
    return FALSE;
  iter->user_data = ptr;
  return TRUE;
}



static gboolean
thunar_tree_view_model_iter_previous (GtkTreeModel *model,
                                      GtkTreeIter  *iter)
{
  GSequenceIter *ptr;

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);
  _thunar_return_val_if_fail (iter->stamp == THUNAR_TREE_VIEW_MODEL (model)->stamp, FALSE);
  _thunar_return_val_if_fail (iter->user_data != NULL, FALSE);

  THUNAR_WARN_RETURN_VAL (g_sequence_iter_is_begin (iter->user_data), FALSE);

  ptr = g_sequence_iter_prev (iter->user_data);
  if (g_sequence_iter_is_begin (ptr))
    return FALSE;
  iter->user_data = ptr;
  return TRUE;
}



static gboolean
thunar_tree_view_model_iter_children (GtkTreeModel *model,
                                      GtkTreeIter  *iter,
                                      GtkTreeIter  *parent)
{
  GSequenceIter *ptr;
  Node          *node;

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);
  _thunar_return_val_if_fail (parent->stamp == THUNAR_TREE_VIEW_MODEL (model)->stamp, FALSE);

  if (parent == NULL)
    /* return iter corresponding to path -> "0"; i.e 1st iter */
    return gtk_tree_model_get_iter_first (model, iter);

  node = g_sequence_get (parent->user_data);
  if (node->n_children == 0 || node->children == NULL)
    return FALSE;

  ptr = g_sequence_get_iter_at_pos (node->children, 0);
  THUNAR_WARN_RETURN_VAL (g_sequence_iter_is_end (ptr), FALSE);
  GTK_TREE_ITER_INIT (*iter, THUNAR_TREE_VIEW_MODEL (model)->stamp, ptr);

  return TRUE;
}



static gboolean
thunar_tree_view_model_iter_has_child (GtkTreeModel *model,
                                       GtkTreeIter  *iter)
{
  Node *node;

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);
  _thunar_return_val_if_fail (iter->stamp == THUNAR_TREE_VIEW_MODEL (model)->stamp, FALSE);
  _thunar_return_val_if_fail (iter->user_data != NULL, FALSE);

  node = g_sequence_get (iter->user_data);
  return node->n_children > 0;
}



static gint
thunar_tree_view_model_iter_n_children (GtkTreeModel *model,
                                        GtkTreeIter  *iter)
{
  ThunarTreeViewModel *_model;
  Node                *node;

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);

  _model = THUNAR_TREE_VIEW_MODEL (model);

  /* As a special case if iter == NULL then return number of toplevel children */
  if (iter == NULL)
    return _model->root != NULL ? _model->root->n_children : 0;

  _thunar_return_val_if_fail (iter->stamp == _model->stamp, FALSE);
  _thunar_return_val_if_fail (iter->user_data != NULL, FALSE);

  node = g_sequence_get (iter->user_data);
  return node->n_children;
}



static gboolean
thunar_tree_view_model_iter_nth_child (GtkTreeModel *model,
                                       GtkTreeIter  *iter,
                                       GtkTreeIter  *parent,
                                       gint          n)
{
  GSequenceIter *ptr;
  Node          *node;

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);

  if (parent == NULL)
    node = THUNAR_TREE_VIEW_MODEL (model)->root;
  else
    node = g_sequence_get (parent->user_data);

  if (node == NULL || n >= node->n_children)
    return FALSE;

  ptr = g_sequence_get_iter_at_pos (node->children, n);
  THUNAR_WARN_RETURN_VAL (g_sequence_iter_is_end (ptr), FALSE);
  GTK_TREE_ITER_INIT (*iter, THUNAR_TREE_VIEW_MODEL (model)->stamp, ptr);

  return TRUE;
}



static gboolean
thunar_tree_view_model_iter_parent (GtkTreeModel *model,
                                    GtkTreeIter  *iter,
                                    GtkTreeIter  *child)
{
  GSequenceIter *ptr;
  Node          *node;

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);
  _thunar_return_val_if_fail (child->stamp == THUNAR_TREE_VIEW_MODEL (model)->stamp, FALSE);
  _thunar_return_val_if_fail (child->user_data != NULL, FALSE);

  node = g_sequence_get (child->user_data);
  if (node->depth <= 1)
    return FALSE;

  ptr = node->parent->ptr;
  GTK_TREE_ITER_INIT (*iter, THUNAR_TREE_VIEW_MODEL (model)->stamp, ptr);

  return TRUE;
}



ThunarFolder *
thunar_tree_view_model_get_folder (ThunarTreeViewModel *model)
{
  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), NULL);
  return THUNAR_TREE_VIEW_MODEL (model)->dir;
}



gboolean
thunar_tree_view_model_get_show_hidden (ThunarTreeViewModel *model)
{
  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);
  return THUNAR_TREE_VIEW_MODEL (model)->show_hidden;
}



gboolean
thunar_tree_view_model_get_file_size_binary (ThunarTreeViewModel *model)
{
  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);
  return THUNAR_TREE_VIEW_MODEL (model)->file_size_binary;
}



ThunarFile *
thunar_tree_view_model_get_file (ThunarTreeViewModel *model,
                                 GtkTreeIter         *iter)
{
  ThunarFile *file;

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), NULL);
  _thunar_return_val_if_fail (iter->stamp == THUNAR_TREE_VIEW_MODEL (model)->stamp, NULL);
  _thunar_return_val_if_fail (iter->user_data != NULL, NULL);

  file = ((Node *) g_sequence_get (iter->user_data))->file;
  return file != NULL ? g_object_ref (file) : NULL;
}



GList *
thunar_tree_view_model_get_paths_for_files (ThunarTreeViewModel *model,
                                            GList               *files)
{
  GtkTreePath *path;
  GtkTreeIter  tree_iter;
  Node        *node;
  GList       *paths = NULL;

  for (GList *lp = files; lp != NULL; lp = lp->next)
    {
      node = thunar_tree_view_model_locate_file (THUNAR_TREE_VIEW_MODEL (model), THUNAR_FILE (lp->data));

      if (node == NULL)
        continue;

      GTK_TREE_ITER_INIT (tree_iter, THUNAR_TREE_VIEW_MODEL (model)->stamp, node->ptr);
      path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &tree_iter);
      paths = g_list_prepend (paths, path);
    }

  return paths;
}



ThunarJob *
thunar_tree_view_model_get_job (ThunarTreeViewModel *model)
{
  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), NULL);
  return THUNAR_TREE_VIEW_MODEL (model)->search_job;
}



G_NORETURN
static void
_thunar_tree_view_model_search_error (ThunarJob *job)
{
  g_error ("Error while searching recursively");
}



static void
_thunar_tree_view_model_search_finished (ThunarJob           *job,
                                         ThunarTreeViewModel *model)
{
  if (model->search_job)
    {
      g_signal_handlers_disconnect_by_data (model->search_job, model);
      g_object_unref (model->search_job);
      model->search_job = NULL;
    }

  if (model->update_search_results_timeout_id > 0)
    {
      thunar_tree_view_model_update_search_files (model);
      g_source_remove (model->update_search_results_timeout_id);
      model->update_search_results_timeout_id = 0;
    }

  thunar_g_list_free_full (model->search_files);
  model->search_files = NULL;

  g_signal_emit_by_name (model, "search-done");
}



static void
_thunar_tree_view_model_cancel_search_job (ThunarTreeViewModel *model)
{
  /* cancel the ongoing search if there is one */
  if (model->search_job)
    {
      thunar_job_cancel (THUNAR_JOB (model->search_job));

      g_signal_handlers_disconnect_by_data (model->search_job, model);
      g_object_unref (model->search_job);
      model->search_job = NULL;
    }

  thunar_g_list_free_full (model->search_files);
  model->search_files = NULL;
}



void
thunar_tree_view_model_set_folder (ThunarTreeViewModel *model,
                                   ThunarFolder        *folder,
                                   gchar               *search_query)
{
  ThunarTreeViewModel *_model;
  gchar               *search_query_normalized;

  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  _model = THUNAR_TREE_VIEW_MODEL (model);

  _thunar_tree_view_model_cancel_search_job (_model);

  if (_model->update_search_results_timeout_id != 0)
    {
      g_source_remove (_model->update_search_results_timeout_id);
      _model->update_search_results_timeout_id = 0;
    }
  thunar_g_list_free_full (_model->search_files);
  _model->search_files = NULL;

  thunar_tree_view_model_cleanup_model (_model);
  _model->root = NULL;

  if (g_hash_table_size (_model->subdirs) > 0)
    g_hash_table_remove_all (_model->subdirs);

  if (_model->dir != NULL)
    g_object_unref (_model->dir);
  _model->dir = NULL;

  _model->dir = folder;
  if (_model->dir == NULL)
    return;

  g_object_freeze_notify (G_OBJECT (model));

  g_object_ref (_model->dir);
  _model->root = thunar_tree_view_model_new_node (thunar_folder_get_corresponding_file (_model->dir));

  if (_model->root == NULL)
    return;

  _model->root->model = _model;

  if (search_query == NULL || strlen (g_strstrip (search_query)) == 0)
    thunar_tree_view_model_load_dir (_model->root);
  else
    {
      /* mark the current folder as ready */
      _model->root->loaded = TRUE;

      search_query_normalized = thunar_g_utf8_normalize_for_search (search_query, TRUE, TRUE);
      g_strfreev (_model->search_terms);
      _model->search_terms = thunar_util_split_search_query (search_query_normalized, NULL);
      if (_model->search_terms != NULL)
        {
          /* search the current folder
           * start a new recursive_search_job */
          _model->search_job = thunar_io_jobs_search_directory (THUNAR_TREE_VIEW_MODEL (_model), search_query_normalized, thunar_folder_get_corresponding_file (folder));
          g_signal_connect (_model->search_job, "error", G_CALLBACK (_thunar_tree_view_model_search_error), NULL);
          g_signal_connect (_model->search_job, "finished", G_CALLBACK (_thunar_tree_view_model_search_finished), _model);
          thunar_job_launch (THUNAR_JOB (_model->search_job));

          /* add new results to the model every X ms */
          _model->update_search_results_timeout_id = g_timeout_add (500, G_SOURCE_FUNC (thunar_tree_view_model_update_search_files), _model);
        }
      g_free (search_query_normalized);
    }

  /* notify listeners that we have a new folder */
  g_object_notify_by_pspec (G_OBJECT (model), tree_model_props[PROP_FOLDER]);
  g_object_notify_by_pspec (G_OBJECT (model), tree_model_props[PROP_NUM_FILES]);
  g_object_thaw_notify (G_OBJECT (model));
}



static void
_thunar_tree_view_model_set_show_hidden (Node    *node,
                                         gpointer data)
{
  GHashTableIter iter;
  gpointer       key;

  /* we have a dummy node here! */
  if (node->file == NULL || node->dir == NULL
      || thunar_tree_view_model_node_has_dummy_child (node))
    return;

  g_sequence_foreach (node->children,
                      (GFunc) _thunar_tree_view_model_set_show_hidden, NULL);

  g_hash_table_iter_init (&iter, node->hidden_files);
  if (node->model->show_hidden)
    while (g_hash_table_iter_next (&iter, &key, NULL))
      thunar_tree_view_model_dir_add_file (node, THUNAR_FILE (key));
  else
    while (g_hash_table_iter_next (&iter, &key, NULL))
      thunar_tree_view_model_dir_remove_file (node, THUNAR_FILE (key));
}



void
thunar_tree_view_model_set_show_hidden (ThunarTreeViewModel *model,
                                        gboolean             show_hidden)
{
  ThunarTreeViewModel *_model;

  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  _model = THUNAR_TREE_VIEW_MODEL (model);

  if (_model->show_hidden == show_hidden)
    return;

  THUNAR_TREE_VIEW_MODEL (model)->show_hidden = show_hidden;

  if (_model->root != NULL)
    _thunar_tree_view_model_set_show_hidden (_model->root, NULL);
}



void
thunar_tree_view_model_set_folders_first (ThunarTreeViewModel *model,
                                          gboolean             folders_first)
{
  ThunarTreeViewModel *_model;

  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  _model = THUNAR_TREE_VIEW_MODEL (model);

  /* check if the new setting differs */
  if ((_model->sort_folders_first && folders_first)
      || (!_model->sort_folders_first && !folders_first))
    return;

  /* apply the new setting (re-sorting the _model) */
  _model->sort_folders_first = folders_first;
  g_object_notify_by_pspec (G_OBJECT (_model), tree_model_props[PROP_FOLDERS_FIRST]);
  thunar_tree_view_model_sort (_model);

  /* emit a "changed" signal for each row, so the display is
     reloaded with the new folders first setting */
  gtk_tree_model_foreach (GTK_TREE_MODEL (_model),
                          (GtkTreeModelForeachFunc) (void (*) (void)) gtk_tree_model_row_changed,
                          NULL);
}



void
thunar_tree_view_model_set_hidden_last (ThunarTreeViewModel *model,
                                        gboolean             hidden_last)
{
  ThunarTreeViewModel *_model;

  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  _model = THUNAR_TREE_VIEW_MODEL (model);

  /* check if the setting differs */
  if ((_model->sort_hidden_last && hidden_last)
      || (!_model->sort_hidden_last && !hidden_last))
    return;

  /* apply the new setting (re-sort the _model) */
  _model->sort_hidden_last = hidden_last;
  g_object_notify_by_pspec (G_OBJECT (_model), tree_model_props[PROP_HIDDEN_LAST]);
  thunar_tree_view_model_sort (_model);

  /* emit a "changed" signal for each row, so the display is
     reloaded with the new hidden last setting */
  gtk_tree_model_foreach (GTK_TREE_MODEL (_model),
                          (GtkTreeModelForeachFunc) (void (*) (void)) gtk_tree_model_row_changed,
                          NULL);
}

void
thunar_tree_view_model_set_file_size_binary (ThunarTreeViewModel *model,
                                             gboolean             file_size_binary)
{
  ThunarTreeViewModel *_model;

  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  _model = THUNAR_TREE_VIEW_MODEL (model);

  /* normalize the setting */
  file_size_binary = !!file_size_binary;

  /* check if we have a new setting */
  if (_model->file_size_binary != file_size_binary)
    {
      /* apply the new setting */
      _model->file_size_binary = file_size_binary;

      /* resort the model with the new setting */
      thunar_tree_view_model_sort (_model);

      /* notify listeners */
      g_object_notify_by_pspec (G_OBJECT (_model), tree_model_props[PROP_FILE_SIZE_BINARY]);

      /* emit a "changed" signal for each row, so the display is
         reloaded with the new binary file size setting */
      gtk_tree_model_foreach (GTK_TREE_MODEL (_model),
                              (GtkTreeModelForeachFunc) (void (*) (void)) gtk_tree_model_row_changed,
                              NULL);
    }
}



void
thunar_tree_view_model_set_job (ThunarTreeViewModel *model,
                                ThunarJob           *job)
{
  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));
  THUNAR_TREE_VIEW_MODEL (model)->search_job = job;
}



static gboolean
thunar_tree_view_model_get_case_sensitive (ThunarTreeViewModel *model)
{
  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);
  return model->sort_case_sensitive;
}



static ThunarDateStyle
thunar_tree_view_model_get_date_style (ThunarTreeViewModel *model)
{
  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), THUNAR_DATE_STYLE_SIMPLE);
  return model->date_style;
}



static const char *
thunar_tree_view_model_get_date_custom_style (ThunarTreeViewModel *model)
{
  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), NULL);
  return model->date_custom_style;
}



static gint
thunar_tree_view_model_get_num_files (ThunarTreeViewModel *model)
{
  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), 0);
  return model->root != NULL ? model->root->n_children : 0;
}



static gboolean
thunar_tree_view_model_get_folders_first (ThunarTreeViewModel *model)
{
  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);
  return model->sort_folders_first;
}



static gboolean
thunar_tree_view_model_get_hidden_last (ThunarTreeViewModel *model)
{
  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);
  return model->sort_hidden_last;
}



static gint
thunar_tree_view_model_get_folder_item_count (ThunarTreeViewModel *model)
{
  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), THUNAR_FOLDER_ITEM_COUNT_NEVER);
  return model->folder_item_count;
}



static gboolean
thunar_tree_view_model_get_loading (ThunarTreeViewModel *model)
{
  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE);
  return model->loading > 0;
}



static void
thunar_tree_view_model_set_case_sensitive (ThunarTreeViewModel *model,
                                           gboolean             case_sensitive)
{
  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  /* normalize the setting */
  case_sensitive = !!case_sensitive;

  /* check if we have a new setting */
  if (model->sort_case_sensitive != case_sensitive)
    {
      /* apply the new setting */
      model->sort_case_sensitive = case_sensitive;

      /* resort the model with the new setting */
      thunar_tree_view_model_sort (model);

      /* notify listeners */
      g_object_notify_by_pspec (G_OBJECT (model), tree_model_props[PROP_CASE_SENSITIVE]);

      /* emit a "changed" signal for each row, so the display is
         reloaded with the new case-sensitive setting */
      gtk_tree_model_foreach (GTK_TREE_MODEL (model),
                              (GtkTreeModelForeachFunc) (void (*) (void)) gtk_tree_model_row_changed,
                              NULL);
    }
}



static void
thunar_tree_view_model_set_date_style (ThunarTreeViewModel *model,
                                       ThunarDateStyle      date_style)
{
  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  /* check if we have a new setting */
  if (model->date_style != date_style)
    {
      /* apply the new setting */
      model->date_style = date_style;

      /* notify listeners */
      g_object_notify_by_pspec (G_OBJECT (model), tree_model_props[PROP_DATE_STYLE]);

      /* emit a "changed" signal for each row, so the display is reloaded with the new date style */
      gtk_tree_model_foreach (GTK_TREE_MODEL (model),
                              (GtkTreeModelForeachFunc) (void (*) (void)) gtk_tree_model_row_changed,
                              NULL);
    }
}



static void
thunar_tree_view_model_set_date_custom_style (ThunarTreeViewModel *model,
                                              const char          *date_custom_style)
{
  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  /* check if we have a new setting */
  if (g_strcmp0 (model->date_custom_style, date_custom_style) != 0)
    {
      /* apply the new setting */
      g_free (model->date_custom_style);
      model->date_custom_style = g_strdup (date_custom_style);

      /* notify listeners */
      g_object_notify_by_pspec (G_OBJECT (model), tree_model_props[PROP_DATE_CUSTOM_STYLE]);

      /* emit a "changed" signal for each row, so the display is reloaded with the new date style */
      gtk_tree_model_foreach (GTK_TREE_MODEL (model),
                              (GtkTreeModelForeachFunc) (void (*) (void)) gtk_tree_model_row_changed,
                              NULL);
    }
}



static void
thunar_tree_view_model_set_folder_item_count (ThunarTreeViewModel  *model,
                                              ThunarFolderItemCount count_as_dir_size)
{
  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  /* check if the new setting differs */
  if (model->folder_item_count == count_as_dir_size)
    return;

  model->folder_item_count = count_as_dir_size;
  g_object_notify_by_pspec (G_OBJECT (model), tree_model_props[PROP_FOLDER_ITEM_COUNT]);

  gtk_tree_model_foreach (GTK_TREE_MODEL (model), (GtkTreeModelForeachFunc) (void (*) (void)) gtk_tree_model_row_changed, NULL);

  /* re-sorting the model if needed */
  if (model->sort_func == thunar_cmp_files_by_size || model->sort_func == (ThunarSortFunc) thunar_cmp_files_by_size_and_items_count)
    {
      model->sort_func = (model->folder_item_count != THUNAR_FOLDER_ITEM_COUNT_NEVER) ? (ThunarSortFunc) thunar_cmp_files_by_size_and_items_count : thunar_cmp_files_by_size;
      thunar_tree_view_model_sort (model);
    }
}



static void
_thunar_tree_view_model_node_check_empty_job_finished (ThunarTreeViewModel *model,
                                                       ThunarJob           *job)
{
  gpointer       thunar_file, value;
  GHashTableIter iter_map;
  gboolean      *is_empty;
  Node          *node;

  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));
  _thunar_return_if_fail (THUNAR_IS_JOB (job));

  /* Note: This method will be called whenever the job finsihed. No matter if it finished sucesfully or was aborted in some way */

  if (model->check_empty_job != NULL)
    {
      g_object_unref (model->check_empty_job);
      model->check_empty_job = NULL;
    }

  if (model->expandable_folders)
    {
      g_hash_table_iter_init (&iter_map, model->files_for_empty_check);
      while (g_hash_table_iter_next (&iter_map, &thunar_file, &value))
        {
          is_empty = (gboolean *) value;
          node = thunar_tree_view_model_locate_file (model, thunar_file);

          if (node == NULL)
            continue;

          if (*is_empty)
            {
              node->can_expand = can_expand_no;
              continue;
            }

          node->can_expand = can_expand_yes;

          /* No dummy child and no files added yet */
          if (node->n_children == 0)
            {
              GtkTreeIter  tree_iter;
              GtkTreePath *path;

              thunar_tree_view_model_node_add_dummy_child (node);

              GTK_TREE_ITER_INIT (tree_iter, node->model->stamp, node->ptr);
              path = gtk_tree_model_get_path (GTK_TREE_MODEL (node->model), &tree_iter);
              gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), path, &tree_iter);
              gtk_tree_path_free (path);
            }
        }
    }

  /* cleanup hashtable */
  g_hash_table_remove_all (model->files_for_empty_check);

  /* drop the job reference we kept for the time the job runs */
  g_object_unref (model);
}



/**
 * thunar_tree_view_model_update_expand_arrows:
 * @model : a #ThunarTreeViewModel
 * @force : force an update, even if nodes already have expand states set
 *
 * Updates the expand state for nodes which have an unknown expand state
 *
 * Returns %FALSE if a job is still running, %TRUE otherwise
 **/
gboolean
thunar_tree_view_model_update_expand_arrows (ThunarTreeViewModel *model,
                                             GList               *files,
                                             gboolean             force)
{
  ThunarFile *file;
  Node       *node;

  if (!model->expandable_folders)
    return TRUE;

  /* If the job is still running, inform the caller on that */
  if (G_UNLIKELY (model->check_empty_job != NULL))
    return FALSE;

  for (GList *lp = files; lp != NULL; lp = lp->next)
    {
      file = lp->data;

      /* only readable directories are relevant here */
      if (!thunar_file_is_directory (file) || !thunar_file_is_readable (file))
        continue;

      node = thunar_tree_view_model_locate_file (model, file);

      if (node == NULL)
        continue;

      /* only load 'can_expand' for nodes which do not yet have a value for it */
      if (!force && (node->can_expand == can_expand_yes || node->can_expand == can_expand_no))
        continue;

      node->can_expand = can_expand_loading;

      /* actually it should not be possible that we alreay have that file */
      if (g_hash_table_contains (model->files_for_empty_check, lp->data))
        continue;

      gboolean *is_empty = g_new (gboolean, 1);
      *is_empty = FALSE;
      g_hash_table_insert (model->files_for_empty_check, g_object_ref (lp->data), is_empty);
    }

  /* start a new job */
  model->check_empty_job = thunar_io_jobs_check_empty (model->files_for_empty_check);
  g_signal_connect_swapped (model->check_empty_job, "finished", G_CALLBACK (_thunar_tree_view_model_node_check_empty_job_finished), g_object_ref (model));

  thunar_job_launch (THUNAR_JOB (model->check_empty_job));

  return TRUE;
}



static void
thunar_tree_view_model_set_expandable_folders (ThunarTreeViewModel *model,
                                               gboolean             expandable_folders)
{
  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  model->expandable_folders = expandable_folders;
}



static Node *
thunar_tree_view_model_new_node (ThunarFile *file)
{
  THUNAR_WARN_RETURN_VAL (file == NULL, NULL);

  Node *_node = g_malloc (sizeof (Node));

  _node->file = g_object_ref (file);

  _node->dir = NULL;

  /* Not been added to the model yet */
  _node->parent = NULL;
  _node->ptr = NULL;

  _node->depth = 0;
  _node->n_children = 0;
  /* if dir is NULL then no need to load; already loaded */
  _node->loaded = FALSE;

  _node->set = g_hash_table_new (g_direct_hash, g_direct_equal);
  _node->hidden_files = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
  _node->children = g_sequence_new (NULL);

  _node->scheduled_unload_id = 0;

  _node->file_watch_active = FALSE;

  _node->can_expand = can_expand_unknown;

  return _node;
}



static Node *
thunar_tree_view_model_new_dummy_node (void)
{
  Node *_node = g_malloc (sizeof (Node));

  _node->file = NULL;

  _node->dir = NULL;

  /* Not been added to the model yet */
  _node->parent = NULL;
  _node->ptr = NULL;

  _node->depth = 0;
  _node->n_children = 0;
  /* if dir is NULL then no need to load; already loaded */
  _node->loaded = FALSE;

  _node->set = NULL;
  _node->hidden_files = NULL;
  _node->children = NULL;

  _node->scheduled_unload_id = 0;

  return _node;
}



static gboolean
thunar_tree_view_model_node_has_dummy_child (Node *node)
{
  GSequenceIter *iter;
  Node          *child;

  if (node->n_children != 1)
    return FALSE;
  iter = g_sequence_get_iter_at_pos (node->children, 0);
  child = g_sequence_get (iter);
  return child->file == NULL;
}



static void
thunar_tree_view_model_node_add_child (Node *node,
                                       Node *child)
{
  GtkTreeIter    tree_iter;
  GtkTreePath   *path;
  GSequenceIter *iter;
  Node          *_child;

  THUNAR_WARN_VOID_RETURN (child == NULL || child->file == NULL);

  child->depth = node->depth + 1;
  child->parent = node;
  child->model = node->model;

  if (thunar_tree_view_model_node_has_dummy_child (node))
    {
      /* replace the dummy node with the new child node */
      iter = g_sequence_get_iter_at_pos (node->children, 0);

      THUNAR_WARN_VOID_RETURN (iter == NULL);

      _child = g_sequence_get (iter);
      g_sequence_set (iter, child);
      child->ptr = iter;

      THUNAR_WARN_VOID_RETURN (iter == NULL);

      g_free (_child); /* free the dummy node */

      /* notify the view */
      GTK_TREE_ITER_INIT (tree_iter, node->model->stamp, child->ptr);
      path = gtk_tree_model_get_path (GTK_TREE_MODEL (node->model), &tree_iter);
      gtk_tree_model_row_changed (GTK_TREE_MODEL (node->model), path, &tree_iter);
      gtk_tree_path_free (path);
    }
  else
    {
      node->n_children++;
      child->ptr = g_sequence_insert_sorted (node->children, child,
                                             thunar_tree_view_model_cmp_nodes, node->model);

      /* notify the view */
      GTK_TREE_ITER_INIT (tree_iter, node->model->stamp, child->ptr);
      path = gtk_tree_model_get_path (GTK_TREE_MODEL (node->model), &tree_iter);
      gtk_tree_model_row_inserted (GTK_TREE_MODEL (node->model), path, &tree_iter);
      gtk_tree_path_free (path);
    }

  g_hash_table_insert (node->set, child->file, child->ptr);
}



static void
thunar_tree_view_model_node_add_dummy_child (Node *node)
{
  GtkTreeIter  tree_iter;
  GtkTreePath *path;
  Node        *dummy;

  THUNAR_WARN_VOID_RETURN (node == NULL);

  dummy = thunar_tree_view_model_new_dummy_node ();
  dummy->depth = node->depth + 1;
  dummy->parent = node;
  node->n_children++;

  dummy->ptr = g_sequence_prepend (node->children, dummy);

  GTK_TREE_ITER_INIT (tree_iter, node->model->stamp, dummy->ptr);
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (node->model), &tree_iter);
  gtk_tree_model_row_inserted (GTK_TREE_MODEL (node->model), path, &tree_iter);
  gtk_tree_path_free (path);
}



static void
thunar_tree_view_model_node_drop_dummy_child (Node *node)
{
  Node          *dummy;
  GtkTreeIter    tree_iter;
  GtkTreePath   *path;
  GSequenceIter *iter;

  _thunar_return_if_fail (thunar_tree_view_model_node_has_dummy_child (node));


  THUNAR_WARN_VOID_RETURN (node->n_children != 1);

  iter = g_sequence_get_iter_at_pos (node->children, 0);

  THUNAR_WARN_VOID_RETURN (g_sequence_iter_is_end (iter));

  dummy = g_sequence_get (iter);
  THUNAR_WARN_VOID_RETURN (iter == NULL || dummy->file != NULL);

  GTK_TREE_ITER_INIT (tree_iter, node->model->stamp, dummy->ptr);
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (node->model), &tree_iter);
  gtk_tree_model_row_deleted (GTK_TREE_MODEL (node->model), path);
  gtk_tree_path_free (path);

  node->n_children--;

  THUNAR_WARN_VOID_RETURN (node->n_children != 0);

  g_sequence_remove (dummy->ptr);
  g_free (dummy);

  if (node->ptr != NULL)
    {
      GTK_TREE_ITER_INIT (tree_iter, node->model->stamp, node->ptr);
      path = gtk_tree_model_get_path (GTK_TREE_MODEL (node->model), &tree_iter);
      gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (node->model), path, &tree_iter);
      gtk_tree_path_free (path);
    }
}



static Node *
thunar_tree_view_model_dir_add_file (Node       *node,
                                     ThunarFile *file)
{
  GtkTreeIter  tree_iter;
  GtkTreePath *path;
  Node        *child;

  if (g_hash_table_contains (node->set, file))
    {
      GSequenceIter *iter = g_hash_table_lookup (node->set, file);
      return g_sequence_get (iter);
    }

  child = thunar_tree_view_model_new_node (file);
  thunar_tree_view_model_node_add_child (node, child);

  /* notify the model if a child has been added to previously empty folder */
  if (node->ptr != NULL && node->n_children == 1)
    {
      node->can_expand = can_expand_yes;
      GTK_TREE_ITER_INIT (tree_iter, node->model->stamp, node->ptr);
      path = gtk_tree_model_get_path (GTK_TREE_MODEL (node->model), &tree_iter);
      gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (node->model), path, &tree_iter);
      gtk_tree_path_free (path);
    }

  return child;
}



static void
thunar_tree_view_model_dir_remove_file (Node       *node,
                                        ThunarFile *file)
{
  GtkTreeIter    tree_iter;
  GtkTreePath   *path;
  GSequenceIter *iter;

  iter = g_hash_table_lookup (node->set, file);

  THUNAR_WARN_VOID_RETURN (iter == NULL);

  GTK_TREE_ITER_INIT (tree_iter, node->model->stamp, iter);
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (node->model), &tree_iter);

  thunar_tree_view_model_node_destroy (g_sequence_get (iter));

  g_sequence_remove (iter);
  g_hash_table_remove (node->set, file);
  node->n_children--;

  /* row deletion will trigger selection change if the file is selected;
   * thus it is important to free the node before calling row_deleted */
  gtk_tree_model_row_deleted (GTK_TREE_MODEL (node->model), path);
  gtk_tree_path_free (path);

  THUNAR_WARN_VOID_RETURN (node->n_children < 0);

  /* notify the model if all children have been deleted */
  if (node->ptr != NULL && node->n_children == 0)
    {
      GTK_TREE_ITER_INIT (tree_iter, node->model->stamp, node->ptr);
      path = gtk_tree_model_get_path (GTK_TREE_MODEL (node->model), &tree_iter);
      gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (node->model), path, &tree_iter);
      gtk_tree_path_free (path);
    }
}



static Node *
thunar_tree_view_model_locate_file (ThunarTreeViewModel *model,
                                    ThunarFile          *file)
{
  ThunarFile    *parent;
  GSequenceIter *iter;
  Node          *parent_node;

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), NULL);
  _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);

  if (!thunar_file_has_parent (file))
    return NULL;

  parent = thunar_file_get_parent (file, NULL);

  if (parent == NULL)
    return NULL;

  if (!g_hash_table_contains (model->subdirs, parent))
    {
      g_object_unref (parent);
      return NULL;
    }

  parent_node = g_hash_table_lookup (model->subdirs, parent);
  g_object_unref (parent);
  THUNAR_WARN_RETURN_VAL (parent_node == NULL, NULL);

  if (!g_hash_table_contains (parent_node->set, file))
    return NULL;

  iter = g_hash_table_lookup (parent_node->set, file);
  THUNAR_WARN_RETURN_VAL (iter == NULL, NULL);

  return g_sequence_get (iter);
}



static gint
thunar_tree_view_model_cmp_nodes (gconstpointer a,
                                  gconstpointer b,
                                  gpointer      data)
{
  ThunarTreeViewModel *model = THUNAR_TREE_VIEW_MODEL (data);
  gboolean             isdir_a;
  gboolean             isdir_b;
  gboolean             is_hidden_a;
  gboolean             is_hidden_b;

  a = ((Node *) a)->file;
  b = ((Node *) b)->file;

  _thunar_return_val_if_fail (THUNAR_IS_FILE (a), 0);
  _thunar_return_val_if_fail (THUNAR_IS_FILE (b), 0);

  if (G_LIKELY (model->sort_folders_first))
    {
      isdir_a = thunar_file_is_directory (a);
      isdir_b = thunar_file_is_directory (b);
      if (isdir_a != isdir_b)
        return isdir_a ? -1 : 1;
    }

  if (model->sort_hidden_last)
    {
      is_hidden_a = thunar_file_is_hidden (a);
      is_hidden_b = thunar_file_is_hidden (b);
      if (is_hidden_a != is_hidden_b)
        return is_hidden_a ? 1 : -1;
    }

  return (*model->sort_func) (a, b, model->sort_case_sensitive) * model->sort_sign;
}



static void
_thunar_tree_view_model_sort (Node    *node,
                              gpointer data)
{
  GtkTreePath    *path;
  GtkTreeIter     iter;
  GSequenceIter **old_order;
  gint           *new_order;
  gint            n;
  gint            length;
  GSequenceIter  *row;

  if (!node->loaded || node->children == NULL)
    return;

  /* recursively sort */
  if (node->children != NULL)
    g_sequence_foreach (node->children, (GFunc) _thunar_tree_view_model_sort, NULL);

  length = node->n_children;
  if (G_UNLIKELY (length <= 1))
    return;

  /* be sure to not overuse the stack */
  if (G_LIKELY (length < STACK_ALLOC_LIMIT))
    {
      old_order = g_newa (GSequenceIter *, length);
      new_order = g_newa (gint, length);
    }
  else
    {
      old_order = g_new (GSequenceIter *, length);
      new_order = g_new (gint, length);
    }

  /* store old order */
  row = g_sequence_get_begin_iter (node->children);
  for (n = 0; n < length; ++n)
    {
      old_order[n] = row;
      row = g_sequence_iter_next (row);
    }

  /* sort */
  g_sequence_sort (node->children, thunar_tree_view_model_cmp_nodes, node->model);

  /* new_order[newpos] = oldpos */
  for (n = 0; n < length; ++n)
    new_order[g_sequence_iter_get_position (old_order[n])] = n;

  /* tell the view about the new item order */
  if (node->ptr != NULL)
    {
      GTK_TREE_ITER_INIT (iter, node->model->stamp, node->ptr);
      path = gtk_tree_model_get_path (GTK_TREE_MODEL (node->model), &iter);
      gtk_tree_model_rows_reordered (GTK_TREE_MODEL (node->model), path, &iter, new_order);
      gtk_tree_path_free (path);
    }
  else
    {
      path = gtk_tree_path_new ();
      gtk_tree_model_rows_reordered (GTK_TREE_MODEL (node->model), path, NULL, new_order);
      gtk_tree_path_free (path);
    }

  /* clean up if we used the heap */
  if (G_UNLIKELY (length >= STACK_ALLOC_LIMIT))
    {
      g_free (old_order);
      g_free (new_order);
    }
}



static void
thunar_tree_view_model_sort (ThunarTreeViewModel *model)
{
  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  if (model->root == NULL)
    return;

  /* sort the entire model */
  _thunar_tree_view_model_sort (model->root, NULL);
}



static void
_thunar_tree_view_model_folder_destroy (Node *node)
{
  /* if a subdir gets destroyed, it's handled internally, but if the current_folder
   * gets destroyed, then we simply set_folder to NULL & cleanup things */
  if (node->parent == NULL)
    {
      if (node->n_children > 0)
        {
          /* set_folder func does not emit row-deleted signal, but instead relies on
           * ThunarStandardView to disconnect & reconnect the view to quickly update
           * changes in current_directory. */
          GList *keys = g_hash_table_get_keys (node->set);

          for (GList *lp = keys; lp != NULL; lp = lp->next)
            thunar_tree_view_model_dir_remove_file (node, THUNAR_FILE (lp->data));

          g_list_free (keys);
        }

      /* reset the model */
      thunar_tree_view_model_set_folder (THUNAR_TREE_VIEW_MODEL (node->model), NULL, NULL);
    }
}



static void
_thunar_tree_view_model_folder_error (Node         *node,
                                      const GError *error)
{
  _thunar_return_if_fail (error != NULL);

  ThunarTreeViewModel *model = node->model;

  if (node->parent == NULL)
    _thunar_tree_view_model_folder_destroy (node);
  else
    thunar_tree_view_model_dir_remove_file (node->parent, node->file);

  /* forward the error signal */
  g_signal_emit_by_name (G_OBJECT (model), "error", error);
}



static void
_thunar_tree_view_model_dir_files_added (Node       *node,
                                         GHashTable *files)
{
  ThunarFile    *file;
  GHashTableIter iter;
  gpointer       key;

  g_hash_table_iter_init (&iter, files);
  while (g_hash_table_iter_next (&iter, &key, NULL))
    {
      file = THUNAR_FILE (key);

      if (thunar_file_is_hidden (file) && !g_hash_table_contains (node->hidden_files, file))
        {
          g_hash_table_add (node->hidden_files, g_object_ref (file));

          if (!node->model->show_hidden)
            continue;
        }

      if (!g_hash_table_contains (node->set, file))
        thunar_tree_view_model_dir_add_file (node, file);
    }

  g_object_notify_by_pspec (G_OBJECT (node->model), tree_model_props[PROP_NUM_FILES]);
}



static void
_thunar_tree_view_model_dir_files_removed (Node       *node,
                                           GHashTable *files)
{
  ThunarFile    *file;
  GHashTableIter iter;
  gpointer       key;

  g_hash_table_iter_init (&iter, files);
  while (g_hash_table_iter_next (&iter, &key, NULL))
    {
      file = THUNAR_FILE (key);

      /* we cannot trust thunar_file_is_hidden here;
       * don't know why. Maybe the file has gone through dispose */
      if (g_hash_table_contains (node->hidden_files, file))
        {
          g_hash_table_remove (node->hidden_files, file);

          if (!node->model->show_hidden)
            continue;
        }

      thunar_tree_view_model_dir_remove_file (node, file);
    }

  g_object_notify_by_pspec (G_OBJECT (node->model), tree_model_props[PROP_NUM_FILES]);
}



static void
_thunar_tree_view_model_dir_notify_loading (Node         *node,
                                            GParamSpec   *spec,
                                            ThunarFolder *dir)
{
  if (!thunar_folder_get_loading (dir) && !node->loaded)
    {
      node->loaded = TRUE;

      if (thunar_tree_view_model_node_has_dummy_child (node))
        thunar_tree_view_model_node_drop_dummy_child (node);

      if (node->n_children == 0)
        node->can_expand = can_expand_no;
      else
        node->can_expand = can_expand_yes;

      /* signal model that this dir is done loading */
      thunar_tree_view_model_set_loading (node->model, FALSE);
    }
}



static void
thunar_tree_view_model_load_dir (Node *node)
{
  GHashTable *files;

  _thunar_return_if_fail (node != NULL);

  if (G_UNLIKELY (node->file == NULL || !thunar_file_is_directory (node->file)))
    return;

  if (node->loaded)
    return;

  thunar_tree_view_model_set_loading (node->model, TRUE);

  node->dir = thunar_folder_get_for_file (node->file);
  THUNAR_WARN_VOID_RETURN (node->dir == NULL);

  g_signal_connect_swapped (G_OBJECT (node->dir), "destroy", G_CALLBACK (_thunar_tree_view_model_folder_destroy), node);
  g_signal_connect_swapped (G_OBJECT (node->dir), "error", G_CALLBACK (_thunar_tree_view_model_folder_error), node);
  g_signal_connect_swapped (G_OBJECT (node->dir), "files-added", G_CALLBACK (_thunar_tree_view_model_dir_files_added), node);
  g_signal_connect_swapped (G_OBJECT (node->dir), "files-removed", G_CALLBACK (_thunar_tree_view_model_dir_files_removed), node);
  g_signal_connect_swapped (G_OBJECT (node->dir), "files-changed", G_CALLBACK (thunar_tree_view_model_dir_files_changed), node);
  g_signal_connect_swapped (G_OBJECT (node->dir), "notify::loading", G_CALLBACK (_thunar_tree_view_model_dir_notify_loading), node);

  files = thunar_folder_get_files (node->dir);
  if (files != NULL)
    _thunar_tree_view_model_dir_files_added (node, files);

  /* If the folder is already loaded, directly update the loading state */
  if (!thunar_folder_get_loading (node->dir))
    _thunar_tree_view_model_dir_notify_loading (node, NULL, node->dir);

  g_hash_table_insert (node->model->subdirs, node->file, node);
}



static gboolean
_thunar_tree_view_model_cleanup_idle (Node *node)
{
  thunar_tree_view_model_node_destroy (node);

  return G_SOURCE_REMOVE;
}



static void
thunar_tree_view_model_cleanup_model (ThunarTreeViewModel *model)
{
  Node  *_node;
  GList *values;

  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  if (model->root == NULL)
    /* we have nothing to cleanup */
    return;

  /* since we are doing a lazy cleanup we should make sure
   to disconnect from the signal handlers of the folders */
  values = g_hash_table_get_values (model->subdirs);
  for (GList *dirs = values; dirs != NULL; dirs = dirs->next)
    {
      _node = dirs->data;

      THUNAR_WARN_VOID_RETURN (_node == NULL || _node->dir == NULL);

      g_signal_handlers_disconnect_by_data (G_OBJECT (_node->dir), _node);
      g_object_unref (_node->dir);
      _node->dir = NULL;
    }
  g_list_free (values);

  /* clean up the nodes in the background*/
  g_idle_add ((GSourceFunc) _thunar_tree_view_model_cleanup_idle, model->root);
  model->root = NULL;
}



static void
thunar_tree_view_model_file_count_callback (ThunarJob           *job,
                                            ThunarTreeViewModel *model)
{
  GArray     *param_values;
  ThunarFile *file;
  ThunarFile *parent;
  GHashTable *files;
  Node       *parent_node;

  if (job == NULL)
    return;

  param_values = thunar_simple_job_get_param_values (THUNAR_SIMPLE_JOB (job));
  file = THUNAR_FILE (g_value_get_object (&g_array_index (param_values, GValue, 0)));

  if (file == NULL)
    return;

  parent = thunar_file_get_parent (file, NULL);
  if (parent == NULL)
    return;

  parent_node = thunar_tree_view_model_locate_file (model, parent);
  g_object_unref (parent);

  if (parent_node == NULL)
    return;

  files = g_hash_table_new (g_direct_hash, NULL);
  g_hash_table_add (files, file);
  thunar_tree_view_model_dir_files_changed (parent_node, files);
  g_hash_table_destroy (files);
}



static void
thunar_tree_view_model_node_destroy (Node *node)
{
  GSequenceIter *iter;

  /* stop possible file-watch */
  if (node->file != NULL && node->file_watch_active)
    {
      g_signal_handlers_disconnect_by_data (node->file, node);
      thunar_file_unwatch (node->file);
    }

  if (node->scheduled_unload_id != 0)
    {
      g_source_remove (node->scheduled_unload_id);
      node->scheduled_unload_id = 0;
    }

  if (node->file == NULL)
    {
      /* we have found a dummy node */
      g_free (node);
      return;
    }

  THUNAR_WARN_VOID_RETURN (node->file == NULL);

  if (node->dir != NULL)
    {
      g_signal_handlers_disconnect_by_data (G_OBJECT (node->dir), node);

      THUNAR_WARN_VOID_RETURN (!g_hash_table_contains (node->model->subdirs, node->file));

      g_hash_table_remove (node->model->subdirs, node->file);
      g_object_unref (node->dir);
    }

  while (node->n_children > 0)
    {
      iter = g_sequence_get_iter_at_pos (node->children, 0);
      thunar_tree_view_model_node_destroy (g_sequence_get (iter));
      g_sequence_remove (iter);
      node->n_children--;
    }

  g_sequence_free (node->children);
  g_hash_table_destroy (node->set);
  g_hash_table_destroy (node->hidden_files);

  g_object_unref (node->file);
  g_free (node);
}



static void
thunar_tree_view_model_dir_files_changed (Node       *node_parent,
                                          GHashTable *files)
{
  ThunarTreeViewModel *model = node_parent->model;
  GSequenceIter       *iter;
  GtkTreeIter          tree_iter;
  GHashTableIter       files_iter;
  GtkTreePath         *path;
  Node                *node;
  gint                 pos_after, pos_before;
  gint                *new_order;
  gint                 length;
  ThunarFile          *file;
  gpointer             key;

  g_hash_table_iter_init (&files_iter, files);
  while (g_hash_table_iter_next (&files_iter, &key, NULL))
    {
      file = THUNAR_FILE (key);

      /* two cases - file is hidden or not
       * 1. if it is in hidden list but not hidden anymore then add the new file
       * 2. if it is not hidden but has turned hidden hide it */
      if (g_hash_table_contains (node_parent->hidden_files, file) && !thunar_file_is_hidden (file))
        {
          g_hash_table_remove (node_parent->hidden_files, file);
          thunar_tree_view_model_dir_add_file (node_parent, file);
          continue;
        }

      /* file is now hidden but still in the visible list */
      if (g_hash_table_contains (node_parent->set, file) && thunar_file_is_hidden (file))
        {
          if (!model->show_hidden)
            thunar_tree_view_model_dir_remove_file (node_parent, file);

          g_hash_table_add (node_parent->hidden_files, g_object_ref (file));
        }

      node = thunar_tree_view_model_locate_file (model, file);
      /* we dont have the file */
      if (node == NULL)
        continue;

      iter = node->ptr;

      pos_before = g_sequence_iter_get_position (iter);
      g_sequence_sort_changed (iter, thunar_tree_view_model_cmp_nodes, model);
      pos_after = g_sequence_iter_get_position (iter);

      if (pos_before != pos_after)
        {
          length = node_parent->n_children;
          new_order = g_malloc_n (length, sizeof (gint));

          /* new_order[newpos] = oldpos */
          for (gint i = 0, j = 0; i < length; ++i)
            {
              if (G_UNLIKELY (i == pos_after))
                {
                  new_order[i] = pos_before;
                }
              else
                {
                  if (G_UNLIKELY (j == pos_before))
                    j++;
                  new_order[i] = j++;
                }
            }

          if (node_parent->ptr != NULL)
            {
              GTK_TREE_ITER_INIT (tree_iter, model->stamp, node_parent->ptr);
              path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &tree_iter);
              gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), path, &tree_iter, new_order);
              gtk_tree_path_free (path);
            }
          else
            {
              path = gtk_tree_path_new_first ();
              gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), path, NULL, new_order);
              gtk_tree_path_free (path);
            }

          g_free (new_order);
        }


      GTK_TREE_ITER_INIT (tree_iter, model->stamp, iter);
      path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &tree_iter);
      gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &tree_iter);
      gtk_tree_path_free (path);
    } /* end for all files */
}



static void
thunar_tree_view_model_set_loading (ThunarTreeViewModel *model,
                                    gboolean             loading)
{
  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));

  if (model->loading == loading)
    return;

  model->loading = loading;

  g_object_notify (G_OBJECT (model), "loading");
}



void
thunar_tree_view_model_load_subdir (ThunarTreeViewModel *model,
                                    GtkTreeIter         *iter)
{
  Node *node;

  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));
  _thunar_return_if_fail (iter->stamp == model->stamp);
  _thunar_return_if_fail (iter->user_data != NULL);

  THUNAR_WARN_VOID_RETURN (g_sequence_iter_is_end (iter->user_data));

  node = g_sequence_get (iter->user_data);
  THUNAR_WARN_VOID_RETURN (node == NULL);

  if (node->scheduled_unload_id != 0)
    g_source_remove (node->scheduled_unload_id);
  node->scheduled_unload_id = 0;
  thunar_tree_view_model_load_dir (node);
}



static gboolean
_thunar_tree_view_model_dir_unload_timeout (Node *node)
{
  GSequenceIter *iter;
  GtkTreeIter    tree_iter;
  GtkTreePath   *path;
  Node          *_node;

  node->loaded = FALSE;

  if (node->dir != NULL)
    {
      g_signal_handlers_disconnect_by_data (G_OBJECT (node->dir), node);
      g_object_unref (node->dir);
      node->dir = NULL;
    }

  GTK_TREE_ITER_INIT (tree_iter, node->model->stamp, node->ptr);
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (node->model), &tree_iter);
  gtk_tree_path_down (path);

  while (node->n_children > 0)
    {
      iter = g_sequence_get_iter_at_pos (node->children, 0);
      _node = g_sequence_get (iter);
      gtk_tree_model_row_deleted (GTK_TREE_MODEL (node->model), path);
      thunar_tree_view_model_node_destroy (_node);
      g_sequence_remove (iter);
      node->n_children--;
    }

  gtk_tree_path_free (path);

  g_hash_table_remove_all (node->hidden_files);
  g_hash_table_remove_all (node->set);

  thunar_tree_view_model_node_add_dummy_child (node);

  g_hash_table_remove (node->model->subdirs, node->file);

  return G_SOURCE_REMOVE;
}



static void
_thunar_tree_view_model_dir_unload_timeout_destroy (Node *node)
{
  node->scheduled_unload_id = 0;
}



void
thunar_tree_view_model_schedule_unload (ThunarTreeViewModel *model,
                                        GtkTreeIter         *iter)
{
  Node *node;

  _thunar_return_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model));
  _thunar_return_if_fail (iter->stamp == model->stamp);
  _thunar_return_if_fail (iter->user_data != NULL);

  THUNAR_WARN_VOID_RETURN (g_sequence_iter_is_end (iter->user_data));

  node = g_sequence_get (iter->user_data);
  THUNAR_WARN_VOID_RETURN (node == NULL);

  node->scheduled_unload_id = g_timeout_add_full (G_PRIORITY_LOW, CLEANUP_AFTER_COLLAPSE_DELAY,
                                                  G_SOURCE_FUNC (_thunar_tree_view_model_dir_unload_timeout),
                                                  node, (GDestroyNotify) _thunar_tree_view_model_dir_unload_timeout_destroy);
}



static gboolean
_thunar_tree_view_model_matches_search_terms (ThunarTreeViewModel *model,
                                              ThunarFile          *file)
{
  gboolean matched;
  gchar   *name_n;

  name_n = (gchar *) thunar_file_get_display_name (file);
  if (name_n == NULL)
    return FALSE;

  name_n = thunar_g_utf8_normalize_for_search (name_n, TRUE, TRUE);
  matched = thunar_util_search_terms_match (model->search_terms, name_n);
  g_free (name_n);

  return matched;
}



static void
_thunar_tree_view_model_search_file_destroyed (Node *node, ThunarFile *file)
{
  GHashTable *files = g_hash_table_new (g_direct_hash, NULL);

  g_hash_table_add (files, file);
  _thunar_tree_view_model_dir_files_removed (node->model->root, files);
  g_hash_table_destroy (files);
}



static void
_thunar_tree_view_model_search_file_changed (Node *node, ThunarFile *file)
{
  GHashTable *files = g_hash_table_new (g_direct_hash, NULL);

  g_hash_table_add (files, file);

  if (_thunar_tree_view_model_matches_search_terms (node->model, node->file) == TRUE)
    thunar_tree_view_model_dir_files_changed (node->model->root, files);
  else
    _thunar_tree_view_model_dir_files_removed (node->model->root, files);

  g_hash_table_destroy (files);
}



static gboolean
thunar_tree_view_model_update_search_files (ThunarTreeViewModel *model)
{
  ThunarFile *file;

  g_mutex_lock (&model->mutex_add_search_files);

  for (GList *lp = model->search_files; lp != NULL; lp = lp->next)
    {
      file = THUNAR_FILE (lp->data);
      if (THUNAR_IS_FILE (file) == FALSE)
        {
          g_warning ("failed to add file to search results");
          continue;
        }

      /* take a reference on that file */
      g_object_ref (file);

      if (_thunar_tree_view_model_matches_search_terms (model, file))
        {
          Node *node;
          node = thunar_tree_view_model_dir_add_file (model->root, lp->data);
          if (node != NULL)
            {
              /* start watching the file */
              g_signal_connect_swapped (file, "destroy", G_CALLBACK (_thunar_tree_view_model_search_file_destroyed), node);
              g_signal_connect_swapped (file, "changed", G_CALLBACK (_thunar_tree_view_model_search_file_changed), node);
              thunar_file_watch (file);
              node->file_watch_active = TRUE;
            }
        }

      g_object_unref (file);
    }

  g_object_notify_by_pspec (G_OBJECT (model), tree_model_props[PROP_NUM_FILES]);

  if (model->search_files != NULL)
    thunar_g_list_free_full (model->search_files);
  model->search_files = NULL;

  g_mutex_unlock (&model->mutex_add_search_files);

  return TRUE;
}



void
thunar_tree_view_model_add_search_files (ThunarTreeViewModel *model,
                                         GList               *files)
{
  ThunarTreeViewModel *_model = THUNAR_TREE_VIEW_MODEL (model);

  g_mutex_lock (&_model->mutex_add_search_files);

  _model->search_files = g_list_concat (_model->search_files, files);

  g_mutex_unlock (&_model->mutex_add_search_files);
}

static gboolean
_thunar_tree_view_model_match_pattern_foreach (GtkTreeModel *model,
                                               GtkTreePath  *path,
                                               GtkTreeIter  *iter,
                                               gpointer      data)
{
  ThunarFile           *file;
  const gchar          *display_name;
  gchar                *normalized_display_name;
  gboolean              name_matched;
  struct _MatchForeach *mf = (struct _MatchForeach *) data;

  file = thunar_tree_view_model_get_file (THUNAR_TREE_VIEW_MODEL (model), iter);
  if (file == NULL)
    return FALSE;

  display_name = thunar_file_get_display_name (file);
  if (display_name == NULL)
    return FALSE;

  normalized_display_name = thunar_g_utf8_normalize_for_search (display_name,
                                                                !mf->match_diacritics,
                                                                !mf->case_sensitive);
  name_matched = g_pattern_spec_match_string (mf->pspec, normalized_display_name);
  g_free (normalized_display_name);

  if (name_matched)
    mf->paths = g_list_prepend (mf->paths, gtk_tree_path_copy (path));

  return FALSE;
}

/**
 * thunar_standard_view_model_get_paths_for_pattern:
 * @store          : a #ThunarStandardViewModel instance.
 * @pattern        : the pattern to match.
 * @case_sensitive    : %TRUE to use case sensitive search.
 * @match_diacritics : %TRUE to use case sensitive search.
 *
 * Looks up all rows in the @store that match @pattern and returns
 * a list of #GtkTreePath<!---->s corresponding to the rows.
 *
 * The caller is responsible to free the returned list using:
 * <informalexample><programlisting>
 * g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
 * </programlisting></informalexample>
 *
 * Return value: the list of #GtkTreePath<!---->s that match @pattern.
 **/
GList *
thunar_tree_view_model_get_paths_for_pattern (ThunarTreeViewModel *model,
                                              const gchar         *pattern,
                                              gboolean             case_sensitive,
                                              gboolean             match_diacritics)
{
  struct _MatchForeach mf;
  gchar               *normalized_pattern;

  _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), NULL);
  _thunar_return_val_if_fail (g_utf8_validate (pattern, -1, NULL), NULL);

  /* compile the pattern */
  normalized_pattern = thunar_g_utf8_normalize_for_search (pattern, !match_diacritics, !case_sensitive);
  mf.pspec = g_pattern_spec_new (normalized_pattern);
  g_free (normalized_pattern);

  mf.paths = NULL;
  mf.case_sensitive = case_sensitive;
  mf.match_diacritics = match_diacritics;

  /* find all rows that match the given pattern */
  gtk_tree_model_foreach (GTK_TREE_MODEL (model),
                          _thunar_tree_view_model_match_pattern_foreach, &mf);

  /* release the pattern */
  g_pattern_spec_free (mf.pspec);

  return mf.paths;
}
