/* vi:set et ai sw=2 sts=2 ts=2: */ /*- * Copyright (c) 2023 Amrit Borah * * 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_CONFIG_H #include "config.h" #endif #ifdef HAVE_MEMORY_H #include #endif #ifdef HAVE_STRING_H #include #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 /* 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 */ /* 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_NUM_FILES, PROP_SHOW_HIDDEN, PROP_FOLDER_ITEM_COUNT, PROP_FILE_SIZE_BINARY, PROP_LOADING, 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); static void thunar_tree_view_model_standard_view_model_init (ThunarStandardViewModelIface *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); /************************************************* * ThunarStandardViewModel Virtual Methods *************************************************/ /* Getters */ static ThunarFolder * thunar_tree_view_model_get_folder (ThunarStandardViewModel *model); static gboolean thunar_tree_view_model_get_show_hidden (ThunarStandardViewModel *model); static gboolean thunar_tree_view_model_get_file_size_binary (ThunarStandardViewModel *model); static ThunarFile * thunar_tree_view_model_get_file (ThunarStandardViewModel *model, GtkTreeIter *iter); static GList * thunar_tree_view_model_get_paths_for_files (ThunarStandardViewModel *model, GList *files); static ThunarJob * thunar_tree_view_model_get_job (ThunarStandardViewModel *model); /* Setters */ static void thunar_tree_view_model_set_folder (ThunarStandardViewModel *model, ThunarFolder *folder, gchar *search_query); static void thunar_tree_view_model_set_show_hidden (ThunarStandardViewModel *model, gboolean show_hidden); static void thunar_tree_view_model_set_folders_first (ThunarStandardViewModel *model, gboolean folders_first); static void thunar_tree_view_model_set_file_size_binary (ThunarStandardViewModel *model, gboolean file_size_binary); static void thunar_tree_view_model_set_job (ThunarStandardViewModel *model, ThunarJob *job); /************************************************* * 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 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); /* 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 (ExoJob *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); static void thunar_tree_view_model_add_search_files (ThunarStandardViewModel *model, GList *files); /************************************************* * 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; 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; 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; }; struct _Node { ThunarFile *file; ThunarFolder *dir; Node *parent; GSequenceIter *ptr; /* self ref */ gint depth; gint n_children; gboolean loaded; gboolean expanded; 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; }; /************************************************* * 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) G_IMPLEMENT_INTERFACE (THUNAR_TYPE_STANDARD_VIEW_MODEL, thunar_tree_view_model_standard_view_model_init)) /* Reference to GParamSpec(s) of all the N_PROPERTIES */ static GParamSpec *tree_model_props[N_PROPERTIES] = { NULL, }; static void thunar_tree_view_model_class_init (ThunarTreeViewModelClass *klass) { GObjectClass *gobject_class; gpointer g_iface; 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; g_iface = g_type_default_interface_peek (THUNAR_TYPE_STANDARD_VIEW_MODEL); /** * ThunarTreeViewModel:case-sensitive: * * Tells whether the sorting should be case sensitive. **/ tree_model_props[PROP_CASE_SENSITIVE] = g_param_spec_override ("case-sensitive", g_object_interface_find_property (g_iface, "case-sensitive")); /** * ThunarTreeViewModel:date-style: * * The style used to format dates. **/ tree_model_props[PROP_DATE_STYLE] = g_param_spec_override ("date-style", g_object_interface_find_property (g_iface, "date-style")); /** * ThunarTreeViewModel:date-custom-style: * * The style used for custom format of dates. **/ tree_model_props[PROP_DATE_CUSTOM_STYLE] = g_param_spec_override ("date-custom-style", g_object_interface_find_property (g_iface, "date-custom-style")); /** * ThunarTreeViewModel:folder: * * The folder presented by this #ThunarTreeViewModel. **/ tree_model_props[PROP_FOLDER] = g_param_spec_override ("folder", g_object_interface_find_property (g_iface, "folder")); /** * ThunarTreeViewModel::folders-first: * * Tells whether to always sort folders before other files. **/ tree_model_props[PROP_FOLDERS_FIRST] = g_param_spec_override ("folders-first", g_object_interface_find_property (g_iface, "folders-first")); /** * ThunarTreeViewModel::num-files: * * The number of files in the folder presented by this #ThunarTreeViewModel. **/ tree_model_props[PROP_NUM_FILES] = g_param_spec_override ("num-files", g_object_interface_find_property (g_iface, "num-files")); /** * ThunarTreeViewModel::show-hidden: * * Tells whether to include hidden (and backup) files. **/ tree_model_props[PROP_SHOW_HIDDEN] = g_param_spec_override ("show-hidden", g_object_interface_find_property (g_iface, "show-hidden")); /** * ThunarTreeViewModel::misc-file-size-binary: * * Tells whether to format file size in binary. **/ tree_model_props[PROP_FILE_SIZE_BINARY] = g_param_spec_override ("file-size-binary", g_object_interface_find_property (g_iface, "file-size-binary")); /** * ThunarTreeViewModel:folder-item-count: * * Tells when the size column of folders should show the number of containing files **/ tree_model_props[PROP_FOLDER_ITEM_COUNT] = g_param_spec_override ("folder-item-count", g_object_interface_find_property (g_iface, "folder-item-count")); /** * ThunarTreeViewModel:loading: * * Tells if the model is yet loading a folder **/ tree_model_props[PROP_LOADING] = g_param_spec_override ("loading", g_object_interface_find_property (g_iface, "loading")); /* install properties */ g_object_class_install_properties (gobject_class, N_PROPERTIES, tree_model_props); } 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); } 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_standard_view_model_init (ThunarStandardViewModelIface *iface) { iface->get_job = thunar_tree_view_model_get_job; iface->set_job = thunar_tree_view_model_set_job; iface->get_file = thunar_tree_view_model_get_file; iface->get_folder = thunar_tree_view_model_get_folder; iface->set_folder = thunar_tree_view_model_set_folder; iface->get_show_hidden = thunar_tree_view_model_get_show_hidden; iface->set_show_hidden = thunar_tree_view_model_set_show_hidden; iface->get_paths_for_files = thunar_tree_view_model_get_paths_for_files; iface->get_file_size_binary = thunar_tree_view_model_get_file_size_binary; iface->set_file_size_binary = thunar_tree_view_model_set_file_size_binary; iface->set_folders_first = thunar_tree_view_model_set_folders_first; iface->add_search_files = thunar_tree_view_model_add_search_files; } 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_STANDARD_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_STANDARD_VIEW_MODEL (object), NULL, NULL); g_mutex_clear (&model->mutex_add_search_files); 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_STANDARD_VIEW_MODEL (model))); break; case PROP_FOLDERS_FIRST: g_value_set_boolean (value, thunar_tree_view_model_get_folders_first (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_STANDARD_VIEW_MODEL (model))); break; case PROP_FILE_SIZE_BINARY: g_value_set_boolean (value, thunar_tree_view_model_get_file_size_binary (THUNAR_STANDARD_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; 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_STANDARD_VIEW_MODEL (model), g_value_get_object (value), NULL); break; case PROP_FOLDERS_FIRST: thunar_tree_view_model_set_folders_first (THUNAR_STANDARD_VIEW_MODEL (model), g_value_get_boolean (value)); break; case PROP_SHOW_HIDDEN: thunar_tree_view_model_set_show_hidden (THUNAR_STANDARD_VIEW_MODEL (model), g_value_get_boolean (value)); break; case PROP_FILE_SIZE_BINARY: thunar_tree_view_model_set_file_size_binary (THUNAR_STANDARD_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; 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_STANDARD_VIEW_MODEL (model)); _thunar_return_if_fail (iter->stamp == (THUNAR_TREE_VIEW_MODEL (model))->stamp); node = g_sequence_get (iter->user_data); file = node->file; if (file != NULL) g_object_ref (file); 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)) { g_file = thunar_file_get_target_location (file); if (g_file == NULL) break; g_value_take_string (value, thunar_g_file_get_free_space_string (g_file, THUNAR_TREE_VIEW_MODEL (model)->file_size_binary)); 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)); 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; } static ThunarFolder * thunar_tree_view_model_get_folder (ThunarStandardViewModel *model) { _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), NULL); return THUNAR_TREE_VIEW_MODEL (model)->dir; } static gboolean thunar_tree_view_model_get_show_hidden (ThunarStandardViewModel *model) { _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE); return THUNAR_TREE_VIEW_MODEL (model)->show_hidden; } static gboolean thunar_tree_view_model_get_file_size_binary (ThunarStandardViewModel *model) { _thunar_return_val_if_fail (THUNAR_IS_TREE_VIEW_MODEL (model), FALSE); return THUNAR_TREE_VIEW_MODEL (model)->file_size_binary; } static ThunarFile * thunar_tree_view_model_get_file (ThunarStandardViewModel *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; } static GList * thunar_tree_view_model_get_paths_for_files (ThunarStandardViewModel *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; } static ThunarJob * thunar_tree_view_model_get_job (ThunarStandardViewModel *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) { exo_job_cancel (EXO_JOB (model->search_job)); g_signal_handlers_disconnect_by_data (model->search_job, model); g_object_unref (model->search_job); model->search_job = NULL; } } static void thunar_tree_view_model_set_folder (ThunarStandardViewModel *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)); _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_STANDARD_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); exo_job_launch (EXO_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)); } static void thunar_tree_view_model_set_show_hidden (ThunarStandardViewModel *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); } static void thunar_tree_view_model_set_folders_first (ThunarStandardViewModel *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); } static void thunar_tree_view_model_set_file_size_binary (ThunarStandardViewModel *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); } } static void thunar_tree_view_model_set_job (ThunarStandardViewModel *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 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 Node * thunar_tree_view_model_new_node (ThunarFile *file) { Node *_node = g_malloc (sizeof (Node)); THUNAR_WARN_RETURN_VAL (file == NULL, NULL); _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; 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->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; 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); if (thunar_file_is_directory (file) && !thunar_file_is_empty_directory (file)) thunar_tree_view_model_node_add_dummy_child (child); /* notify the model if a child has been added to previously empty folder */ if (node->ptr != NULL && node->n_children == 1) { 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; 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; } 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 get's destroyed it's handled internally but if the current_folder * get's destroyed then we simply set_folder to NULL & cleanup things */ if (node->parent == NULL) thunar_tree_view_model_set_folder (THUNAR_STANDARD_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); if (node->n_children > 0) { if (node->parent == NULL) { /* set_folder func does not emit row-deleted signal instead relies * on ThunarStandardView to disconnect & reconnect the view 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 if error is with the current directory */ thunar_tree_view_model_set_folder (THUNAR_STANDARD_VIEW_MODEL (node->model), NULL, NULL); } else thunar_tree_view_model_dir_remove_file (node->parent, node->file); } } 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); } } 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); } } 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); /* 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 (ExoJob *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; gboolean dummy_added = FALSE; 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)); continue; } 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); } if (thunar_file_is_directory (file) && !thunar_file_is_empty_directory (file) && !node->loaded && !thunar_tree_view_model_node_has_dummy_child (node)) { dummy_added = TRUE; thunar_tree_view_model_node_add_dummy_child (node); } 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); if (dummy_added) gtk_tree_model_row_has_child_toggled (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); node->expanded = TRUE; 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->expanded = FALSE; 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); } if (model->search_files != NULL) g_list_free (model->search_files); model->search_files = NULL; g_mutex_unlock (&model->mutex_add_search_files); return TRUE; } static void thunar_tree_view_model_add_search_files (ThunarStandardViewModel *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); }