Index: thunar/thunar-folder.c =================================================================== --- thunar/thunar-folder.c (revision 28900) +++ thunar/thunar-folder.c (working copy) @@ -547,6 +547,13 @@ } else if (lp != NULL) { + /* HACK: when the treeview expands a folder like /proc that has a + * large numer of updates, the process is locked because of all the + * file changes. to prevent this, we check the ref count of the file, if + * it is only reffed by the folder, we do no updates. */ + if (G_OBJECT (lp->data)->ref_count < 2) + return; + /* update/destroy the file */ if (event == THUNAR_VFS_MONITOR_EVENT_DELETED) thunar_file_destroy (lp->data); Index: thunar/thunar-tree-model.c =================================================================== --- thunar/thunar-tree-model.c (revision 28900) +++ thunar/thunar-tree-model.c (working copy) @@ -40,6 +40,9 @@ /* convenience macros */ #define G_NODE(node) ((GNode *) (node)) #define THUNAR_TREE_MODEL_ITEM(item) ((ThunarTreeModelItem *) (item)) +#define G_NODE_HAS_DUMMY(node) (node->children != NULL \ + && node->children->data == NULL \ + && node->children->next == NULL) @@ -106,6 +109,9 @@ gpointer user_data); static void thunar_tree_model_sort (ThunarTreeModel *model, GNode *node); +static void thunar_tree_model_unload (ThunarTreeModel *model); +static gboolean thunar_tree_model_unload_idle (gpointer user_data); +static void thunar_tree_model_unload_idle_destroy (gpointer user_data); static void thunar_tree_model_file_changed (ThunarFileMonitor *file_monitor, ThunarFile *file, ThunarTreeModel *model); @@ -127,7 +133,6 @@ static void thunar_tree_model_item_free (ThunarTreeModelItem *item); static void thunar_tree_model_item_reset (ThunarTreeModelItem *item); static void thunar_tree_model_item_load_folder (ThunarTreeModelItem *item); -static void thunar_tree_model_item_unload_folder (ThunarTreeModelItem *item); static void thunar_tree_model_item_files_added (ThunarTreeModelItem *item, GList *files, ThunarFolder *folder); @@ -139,8 +144,12 @@ static void thunar_tree_model_item_notify_loading (ThunarTreeModelItem *item, GParamSpec *pspec, ThunarFolder *folder); +static void thunar_tree_model_node_insert_dummy (GNode *parent, + ThunarTreeModel *model); static void thunar_tree_model_node_drop_dummy (GNode *node, ThunarTreeModel *model); +static gboolean thunar_tree_model_node_traverse_unload (GNode *node, + gpointer user_data); static gboolean thunar_tree_model_node_traverse_changed (GNode *node, gpointer user_data); static gboolean thunar_tree_model_node_traverse_remove (GNode *node, @@ -149,6 +158,8 @@ gpointer user_data); static gboolean thunar_tree_model_node_traverse_free (GNode *node, gpointer user_data); +static gboolean thunar_tree_model_node_traverse_visible (GNode *node, + gpointer user_data); @@ -159,41 +170,44 @@ struct _ThunarTreeModel { - GObject __parent__; + 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; + gint stamp; #endif /* removable volumes */ - ThunarVfsVolumeManager *volume_manager; - GList *hidden_volumes; + ThunarVfsVolumeManager *volume_manager; + GList *hidden_volumes; - ThunarFileMonitor *file_monitor; + ThunarFileMonitor *file_monitor; - gboolean sort_case_sensitive; + gboolean sort_case_sensitive; - GNode *root; - - /* when this setting is enabled, we do not ref nodes. this is - * used to avoid a race condition when gtk traverses the tree - * and reads the iter data. See bug #2502. - */ - gboolean lock_ref_node; + ThunarTreeModelVisibleFunc visible_func; + gpointer visible_data; + + GNode *root; + + guint unload_idle_id; }; struct _ThunarTreeModelItem { gint ref_count; - gint load_idle_id; + guint load_idle_id; ThunarFile *file; ThunarFolder *folder; ThunarVfsVolume *volume; ThunarTreeModel *model; + + /* list of children of this node that are + * not visible in the treeview */ + GSList *invisible_children; }; typedef struct @@ -313,7 +327,8 @@ /* initialize the model data */ model->sort_case_sensitive = TRUE; - model->lock_ref_node = FALSE; + model->visible_func = (ThunarTreeModelVisibleFunc) exo_noop_true; + model->visible_data = NULL; /* connect to the file monitor */ model->file_monitor = thunar_file_monitor_get_default (); @@ -366,6 +381,10 @@ ThunarTreeModel *model = THUNAR_TREE_MODEL (object); GList *lp; + /* remove unload idle */ + if (model->unload_idle_id != 0) + g_source_remove (model->unload_idle_id); + /* disconnect from the file monitor */ g_signal_handlers_disconnect_by_func (G_OBJECT (model->file_monitor), thunar_tree_model_file_changed, model); g_object_unref (G_OBJECT (model->file_monitor)); @@ -712,7 +731,7 @@ { ThunarTreeModel *model = THUNAR_TREE_MODEL (tree_model); GNode *child; - + _thunar_return_val_if_fail (parent == NULL || parent->user_data != NULL, FALSE); _thunar_return_val_if_fail (parent == NULL || parent->stamp == model->stamp, FALSE); @@ -767,10 +786,6 @@ _thunar_return_if_fail (iter->user_data != NULL); _thunar_return_if_fail (iter->stamp == model->stamp); - /* leave when locked */ - if (model->lock_ref_node) - return; - /* determine the node for the iterator */ node = G_NODE (iter->user_data); if (G_UNLIKELY (node == model->root)) @@ -785,6 +800,10 @@ } else { + /* schedule a reload of the folder if it is unloaded earlier */ + if (item->ref_count == 0) + thunar_tree_model_item_load_folder (item); + /* increment the reference count */ item->ref_count += 1; } @@ -812,15 +831,12 @@ item = node->data; if (G_LIKELY (item != NULL)) { - /* check if this was the last reference */ - if (G_UNLIKELY (item->ref_count == 1)) - { - /* schedule to unload the folder contents */ - thunar_tree_model_item_unload_folder (item); - } - /* decrement the reference count */ item->ref_count -= 1; + + /* schedule an unload of the tree if an item is released by the treeview */ + if (G_UNLIKELY (item->ref_count == 0)) + thunar_tree_model_unload (model); } } @@ -847,9 +863,9 @@ GtkTreeIter iter; SortTuple *sort_array; GNode *child_node; - gint n_children; + guint n_children; gint *new_order; - gint n; + guint n; /* determine the number of children of the node */ n_children = g_node_n_children (node); @@ -905,6 +921,48 @@ static void +thunar_tree_model_unload (ThunarTreeModel *model) +{ + /* schedule an idle unload, if not already done */ + if (model->unload_idle_id == 0) + { + /* the unload idle has a delay of 500 ms to make sure all the nodes + * are unreffed by the treeview. this allows the traverse unref work + * more efficiently */ + model->unload_idle_id = g_timeout_add_full (G_PRIORITY_LOW, 500, thunar_tree_model_unload_idle, + model, thunar_tree_model_unload_idle_destroy); + } +} + + + +static gboolean +thunar_tree_model_unload_idle (gpointer user_data) +{ + ThunarTreeModel *model = THUNAR_TREE_MODEL (user_data); + + GDK_THREADS_ENTER (); + + /* walk through the tree and release all the nodes with a ref count of 0 */ + g_node_traverse (model->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + thunar_tree_model_node_traverse_unload, model); + + GDK_THREADS_LEAVE (); + + return FALSE; +} + + + +static void +thunar_tree_model_unload_idle_destroy (gpointer user_data) +{ + THUNAR_TREE_MODEL (user_data)->unload_idle_id = 0; +} + + + +static void thunar_tree_model_file_changed (ThunarFileMonitor *file_monitor, ThunarFile *file, ThunarTreeModel *model) @@ -915,7 +973,8 @@ _thunar_return_if_fail (THUNAR_IS_FILE (file)); /* traverse the model and emit "row-changed" for the file's nodes */ - g_node_traverse (model->root, G_POST_ORDER, G_TRAVERSE_ALL, -1, thunar_tree_model_node_traverse_changed, file); + if (thunar_file_is_directory (file)) + g_node_traverse (model->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, thunar_tree_model_node_traverse_changed, file); } @@ -927,7 +986,6 @@ ThunarTreeModelItem *item = NULL; GtkTreePath *path; GtkTreeIter iter; - GNode *dummy; GNode *node; GList list; GList *lp; @@ -961,16 +1019,8 @@ gtk_tree_path_free (path); /* add the dummy node */ - node = g_node_append_data (node, NULL); + thunar_tree_model_node_insert_dummy (node, model); - /* determine the iterator for the dummy node */ - GTK_TREE_ITER_INIT (iter, model->stamp, node); - - /* tell the view about the dummy node */ - path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); - gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); - gtk_tree_path_free (path); - /* drop our reference on the volume */ g_object_unref (G_OBJECT (volume)); } @@ -1026,15 +1076,7 @@ g_node_traverse (node->children, G_POST_ORDER, G_TRAVERSE_ALL, -1, thunar_tree_model_node_traverse_remove, model); /* append the dummy node */ - dummy = g_node_append_data (node, NULL); - - /* determine the iterator for the dummy node */ - GTK_TREE_ITER_INIT (iter, model->stamp, dummy); - - /* tell the view about the dummy node */ - path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); - gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); - gtk_tree_path_free (path); + thunar_tree_model_node_insert_dummy (node, model); } /* generate an iterator for the item */ @@ -1055,9 +1097,7 @@ ThunarVfsVolume *volume, ThunarTreeModel *model) { - GtkTreePath *path; - GtkTreeIter iter; - GNode *node; + GNode *node; _thunar_return_if_fail (THUNAR_VFS_IS_VOLUME_MANAGER (volume_manager)); _thunar_return_if_fail (THUNAR_VFS_IS_VOLUME (volume)); @@ -1080,15 +1120,7 @@ g_node_traverse (node->children, G_POST_ORDER, G_TRAVERSE_ALL, -1, thunar_tree_model_node_traverse_remove, model); /* add the dummy node */ - node = g_node_append_data (node, NULL); - - /* determine the iterator for the dummy node */ - GTK_TREE_ITER_INIT (iter, model->stamp, node); - - /* tell the view about the dummy node */ - path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); - gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); - gtk_tree_path_free (path); + thunar_tree_model_node_insert_dummy (node, model); } @@ -1240,6 +1272,14 @@ item->folder = NULL; } + /* free all the invisible children */ + if (item->invisible_children != NULL) + { + g_slist_foreach (item->invisible_children, (GFunc) g_object_unref, NULL); + g_slist_free (item->invisible_children); + item->invisible_children = NULL; + } + /* disconnect from the file */ if (G_LIKELY (item->file != NULL)) { @@ -1269,14 +1309,6 @@ static void -thunar_tree_model_item_unload_folder (ThunarTreeModelItem *item) -{ - // FIXME: Unload the children of item and add a dummy child -} - - - -static void thunar_tree_model_item_files_added (ThunarTreeModelItem *item, GList *files, ThunarFolder *folder) @@ -1292,6 +1324,7 @@ _thunar_return_if_fail (THUNAR_IS_FOLDER (folder)); _thunar_return_if_fail (item->folder == folder); + _thunar_return_if_fail (model->visible_func != NULL); /* process all specified files */ for (lp = files; lp != NULL; lp = lp->next) @@ -1301,6 +1334,15 @@ if (!thunar_file_is_directory (file)) continue; + /* if this file should be visible */ + if (!model->visible_func (model, file, model->visible_data)) + { + /* file is invisible, insert it in the invisible list and continue */ + item->invisible_children = g_slist_prepend (item->invisible_children, + g_object_ref (G_OBJECT (file))); + continue; + } + /* lookup the node for the item (on-demand) */ if (G_UNLIKELY (node == NULL)) node = g_node_find (model->root, G_POST_ORDER, G_TRAVERSE_ALL, item); @@ -1309,7 +1351,7 @@ child_item = thunar_tree_model_item_new_with_file (model, file); /* check if the node has only the dummy child */ - if (g_node_n_children (node) == 1 && g_node_first_child (node)->data == NULL) + if (G_NODE_HAS_DUMMY (node)) { /* replace the dummy node with the new node */ child_node = g_node_first_child (node); @@ -1338,15 +1380,7 @@ } /* add a dummy child node */ - child_node = g_node_append_data (child_node, NULL); - - /* determine the tree iter for the dummy */ - GTK_TREE_ITER_INIT (child_iter, model->stamp, child_node); - - /* emit a "row-inserted" for the dummy node */ - child_path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &child_iter); - gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), child_path, &child_iter); - gtk_tree_path_free (child_path); + thunar_tree_model_node_insert_dummy (child_node, model); } /* sort the folders if any new ones were added */ @@ -1367,6 +1401,7 @@ GNode *child_node; GNode *node; GList *lp; + GSList *link; _thunar_return_if_fail (THUNAR_IS_FOLDER (folder)); _thunar_return_if_fail (item->folder == folder); @@ -1402,6 +1437,24 @@ gtk_tree_path_free (path); } } + + /* we also need to release all the invisible folders */ + if (item->invisible_children != NULL) + { + for (lp = files; lp != NULL; lp = lp->next) + { + /* find the file in the hidden list */ + link = g_slist_find (item->invisible_children, lp->data); + if (link != NULL) + { + /* release the file */ + g_object_unref (G_OBJECT (lp->data)); + + /* remove from the list */ + item->invisible_children = g_slist_delete_link (item->invisible_children, link); + } + } + } } @@ -1423,7 +1476,8 @@ node = g_node_find (item->model->root, G_POST_ORDER, G_TRAVERSE_ALL, item); /* ...and drop the dummy for the node */ - thunar_tree_model_node_drop_dummy (node, item->model); + if (G_NODE_HAS_DUMMY (node)) + thunar_tree_model_node_drop_dummy (node, item->model); } } @@ -1436,7 +1490,17 @@ GList *files; _thunar_return_val_if_fail (item->folder == NULL, FALSE); + +#ifndef NDEBUG + /* find the node in the tree */ + GNode *node = g_node_find (item->model->root, G_POST_ORDER, G_TRAVERSE_ALL, item); + /* debug check to make sure the node is empty or contains a dummy node. + * if this is not true, the node already contains sub folders which means + * something went wrong. */ + _thunar_return_val_if_fail (node->children == NULL || G_NODE_HAS_DUMMY (node), FALSE); +#endif + GDK_THREADS_ENTER (); /* check if we don't have a file yet and this is a mounted volume */ @@ -1485,41 +1549,93 @@ static void +thunar_tree_model_node_insert_dummy (GNode *parent, + ThunarTreeModel *model) +{ + GNode *node; + GtkTreeIter iter; + GtkTreePath *path; + + _thunar_return_if_fail (THUNAR_IS_TREE_MODEL (model)); + _thunar_return_if_fail (g_node_n_children (parent) == 0); + + /* add the dummy node */ + node = g_node_append_data (parent, NULL); + + /* determine the iterator for the dummy node */ + GTK_TREE_ITER_INIT (iter, model->stamp, node); + + /* tell the view about the dummy node */ + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); + gtk_tree_path_free (path); +} + + + +static void thunar_tree_model_node_drop_dummy (GNode *node, ThunarTreeModel *model) { GtkTreePath *path; GtkTreeIter iter; - /* check if we still have the dummy child node */ - if (g_node_n_children (node) == 1 && node->children->data == NULL) + _thunar_return_if_fail (THUNAR_IS_TREE_MODEL (model)); + _thunar_return_if_fail (G_NODE_HAS_DUMMY (node)); + + /* determine the iterator for the dummy */ + GTK_TREE_ITER_INIT (iter, model->stamp, node->children); + + /* determine the path for the iterator */ + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + if (G_LIKELY (path != NULL)) { - /* determine the iterator for the dummy */ - GTK_TREE_ITER_INIT (iter, model->stamp, node->children); + /* notify the view */ + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); - /* determine the path for the iterator */ - path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); - if (G_LIKELY (path != NULL)) - { - /* notify the view */ - gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + /* drop the dummy from the model */ + g_node_destroy (node->children); - /* drop the dummy from the model */ - g_node_destroy (node->children); + /* determine the iter to the parent node */ + GTK_TREE_ITER_INIT (iter, model->stamp, node); - /* determine the iter to the parent node */ - GTK_TREE_ITER_INIT (iter, model->stamp, node); + /* determine the path to the parent node */ + gtk_tree_path_up (path); - /* determine the path to the parent node */ - gtk_tree_path_up (path); + /* emit a "row-has-child-toggled" for the parent */ + gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), path, &iter); - /* emit a "row-has-child-toggled" for the parent */ - gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), path, &iter); + /* release the path */ + gtk_tree_path_free (path); + } +} - /* release the path */ - gtk_tree_path_free (path); - } + + +static gboolean +thunar_tree_model_node_traverse_unload (GNode *node, + gpointer user_data) +{ + ThunarTreeModelItem *item = node->data; + ThunarTreeModel *model = THUNAR_TREE_MODEL (user_data); + + if (item && item->folder != NULL && item->ref_count == 0) + { + /* disconnect from the folder */ + g_signal_handlers_disconnect_matched (G_OBJECT (item->folder), G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, item); + g_object_unref (G_OBJECT (item->folder)); + item->folder = NULL; + + /* remove all the children of the node */ + while (node->children) + g_node_traverse (node->children, G_POST_ORDER, G_TRAVERSE_ALL, -1, + thunar_tree_model_node_traverse_remove, model); + + /* insert a dummy node */ + thunar_tree_model_node_insert_dummy (node, model); } + + return FALSE; } @@ -1528,13 +1644,14 @@ thunar_tree_model_node_traverse_changed (GNode *node, gpointer user_data) { - ThunarTreeModel *model; - GtkTreePath *path; - GtkTreeIter iter; - ThunarFile *file = THUNAR_FILE (user_data); + ThunarTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + ThunarFile *file = THUNAR_FILE (user_data); + ThunarTreeModelItem *item = THUNAR_TREE_MODEL_ITEM (node->data); /* check if the node's file is the file that changed */ - if (node->data != NULL && THUNAR_TREE_MODEL_ITEM (node->data)->file == file) + if (G_UNLIKELY (item != NULL && item->file == file)) { /* determine the tree model from the item */ model = THUNAR_TREE_MODEL_ITEM (node->data)->model; @@ -1557,8 +1674,12 @@ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); gtk_tree_path_free (path); } + + /* stop traversing */ + return TRUE; } + /* continue traversing */ return FALSE; } @@ -1623,6 +1744,96 @@ +static gboolean +thunar_tree_model_node_traverse_visible (GNode *node, + gpointer user_data) +{ + ThunarTreeModelItem *item = node->data; + ThunarTreeModel *model = THUNAR_TREE_MODEL (user_data); + GtkTreePath *path; + GtkTreeIter iter; + GNode *child_node; + GSList *lp, *lnext; + ThunarTreeModelItem *parent, *child; + ThunarFile *file; + + _thunar_return_val_if_fail (model->visible_func != NULL, FALSE); + + if (G_LIKELY (item)) + { + /* check if this file should be visibily in the treeview */ + if (!model->visible_func (model, item->file, model->visible_data)) + { + /* delete all the children of the node */ + while (node->children) + g_node_traverse (node->children, G_POST_ORDER, G_TRAVERSE_ALL, -1, + thunar_tree_model_node_traverse_remove, model); + + /* generate an iterator for the item */ + GTK_TREE_ITER_INIT (iter, model->stamp, node); + + /* remove this item from the tree */ + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + + /* insert the file in the invisible list of the parent */ + parent = node->parent->data; + if (G_LIKELY (parent)) + parent->invisible_children = g_slist_prepend (parent->invisible_children, + g_object_ref (G_OBJECT (item->file))); + + /* free the item and destroy the node */ + thunar_tree_model_item_free (item); + g_node_destroy (node); + } + else + { + /* this node should be visible. check if the node has invisible + * files that should be visible too */ + for (lp = item->invisible_children, child_node = NULL; lp != NULL; lp = lnext) + { + lnext = lp->next; + file = THUNAR_FILE (lp->data); + + if (model->visible_func (model, file, model->visible_data)) + { + /* allocate a new item for the file */ + child = thunar_tree_model_item_new_with_file (model, file); + + /* insert a new node for the child */ + child_node = g_node_append_data (node, child); + + /* determine the tree iter for the child */ + GTK_TREE_ITER_INIT (iter, model->stamp, child_node); + + /* emit a "row-inserted" for the new node */ + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); + gtk_tree_path_free (path); + + /* release the reference on the file hold by the invisible list */ + g_object_unref (G_OBJECT (file)); + + /* delete the file in the list */ + item->invisible_children = g_slist_delete_link (item->invisible_children, lp); + + /* insert dummy */ + thunar_tree_model_node_insert_dummy (child_node, model); + } + } + + /* sort this node if one of new children have been added */ + if (child_node != NULL) + thunar_tree_model_sort (model, node); + } + } + + return FALSE; +} + + + /** * thunar_tree_model_get_default: * @@ -1712,11 +1923,44 @@ +/** + * thunar_tree_model_set_visible_func: + * @model : a #ThunarTreeModel. + * @func : a #ThunarTreeModelVisibleFunc, the visible function. + * @data : User data to pass to the visible function, or %NULL. + * + * Sets the visible function used when filtering the #ThunarTreeModel. + * The function should return %TRUE if the given row should be visible + * and %FALSE otherwise. + **/ void -thunar_tree_model_set_lock_ref_node (ThunarTreeModel *model, - gboolean lock_ref_node) +thunar_tree_model_set_visible_func (ThunarTreeModel *model, + ThunarTreeModelVisibleFunc func, + gpointer data) { _thunar_return_if_fail (THUNAR_IS_TREE_MODEL (model)); - - model->lock_ref_node = !!lock_ref_node; + _thunar_return_if_fail (func != NULL); + + /* set the new visiblity function and user data */ + model->visible_func = func; + model->visible_data = data; } + + + +/** + * thunar_tree_model_refilter: + * @model : a #ThunarTreeModel. + * + * Walks all the folders in the #ThunarTreeModel and updates their + * visibility. + **/ +void +thunar_tree_model_refilter (ThunarTreeModel *model) +{ + _thunar_return_if_fail (THUNAR_IS_TREE_MODEL (model)); + + /* traverse all nodes to update their visibility */ + g_node_traverse (model->root, G_POST_ORDER, G_TRAVERSE_ALL, -1, + thunar_tree_model_node_traverse_visible, model); +} Index: thunar/thunar-tree-model.h =================================================================== --- thunar/thunar-tree-model.h (revision 28900) +++ thunar/thunar-tree-model.h (working copy) @@ -27,6 +27,10 @@ typedef struct _ThunarTreeModelClass ThunarTreeModelClass; typedef struct _ThunarTreeModel ThunarTreeModel; +typedef gboolean (* ThunarTreeModelVisibleFunc) (ThunarTreeModel *model, + ThunarFile *file, + gpointer data); + #define THUNAR_TYPE_TREE_MODEL (thunar_tree_model_get_type ()) #define THUNAR_TREE_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), THUNAR_TYPE_TREE_MODEL, ThunarTreeModel)) #define THUNAR_TREE_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), THUNAR_TYPE_TREE_MODEL, ThunarTreeModelClass)) @@ -57,12 +61,14 @@ ThunarTreeModel *thunar_tree_model_get_default (void); -gboolean thunar_tree_model_get_case_sensitive (ThunarTreeModel *model); -void thunar_tree_model_set_case_sensitive (ThunarTreeModel *model, - gboolean case_sensitive); +gboolean thunar_tree_model_get_case_sensitive (ThunarTreeModel *model); +void thunar_tree_model_set_case_sensitive (ThunarTreeModel *model, + gboolean case_sensitive); -void thunar_tree_model_set_lock_ref_node (ThunarTreeModel *model, - gboolean lock_ref_node); +void thunar_tree_model_set_visible_func (ThunarTreeModel *model, + ThunarTreeModelVisibleFunc func, + gpointer data); +void thunar_tree_model_refilter (ThunarTreeModel *model); G_END_DECLS; Index: thunar/thunar-tree-view.c =================================================================== --- thunar/thunar-tree-view.c (revision 28900) +++ thunar/thunar-tree-view.c (working copy) @@ -118,7 +118,7 @@ static gboolean thunar_tree_view_delete_selected_files (ThunarTreeView *view); static void thunar_tree_view_context_menu (ThunarTreeView *view, GdkEventButton *event, - GtkTreeModel *filter, + GtkTreeModel *model, GtkTreeIter *iter); static GdkDragAction thunar_tree_view_get_dest_actions (ThunarTreeView *view, GdkDragContext *context, @@ -148,8 +148,8 @@ static void thunar_tree_view_new_files (ThunarVfsJob *job, GList *path_list, ThunarTreeView *view); -static gboolean thunar_tree_view_filter_visible_func (GtkTreeModel *filter, - GtkTreeIter *iter, +static gboolean thunar_tree_view_visible_func (ThunarTreeModel *model, + ThunarFile *file, gpointer user_data); static gboolean thunar_tree_view_selection_func (GtkTreeSelection *selection, GtkTreeModel *model, @@ -361,7 +361,6 @@ GtkTreeViewColumn *column; GtkTreeSelection *selection; GtkCellRenderer *renderer; - GtkTreeModel *filter; /* initialize the view internals */ view->cursor_idle_id = -1; @@ -376,13 +375,9 @@ /* connect to the default tree model */ view->model = thunar_tree_model_get_default (); + thunar_tree_model_set_visible_func (view->model, thunar_tree_view_visible_func, view); + gtk_tree_view_set_model (GTK_TREE_VIEW (view), GTK_TREE_MODEL (view->model)); - /* setup the filter for the tree view */ - filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (view->model), NULL); - gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter), thunar_tree_view_filter_visible_func, view, NULL); - gtk_tree_view_set_model (GTK_TREE_VIEW (view), filter); - g_object_unref (G_OBJECT (filter)); - /* configure the tree view */ gtk_tree_view_set_enable_search (GTK_TREE_VIEW (view), FALSE); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); @@ -543,7 +538,7 @@ ThunarFile *current_directory) { ThunarTreeView *view = THUNAR_TREE_VIEW (navigator); - GtkTreeModel *filter; + ThunarFile *file, *file_parent; /* check if we already use that directory */ if (G_UNLIKELY (view->current_directory == current_directory)) @@ -562,11 +557,30 @@ /* take a reference on the directory */ g_object_ref (G_OBJECT (current_directory)); - /* update the filter if the new current directory is hidden */ - if (!view->show_hidden && thunar_file_is_hidden (current_directory)) + /* update the filter if the new current directory, or one of it's parents, is hidden */ + if (!view->show_hidden) { - filter = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter)); + /* look if the file or one of it's parents is hidden */ + for (file = g_object_ref (G_OBJECT (current_directory)); file != NULL; file = file_parent) + { + /* check if this file is hidden */ + if (thunar_file_is_hidden (file)) + { + /* update the filter */ + thunar_tree_model_refilter (view->model); + + /* release the file */ + g_object_unref (G_OBJECT (file)); + + break; + } + + /* get the file parent */ + file_parent = thunar_file_get_parent (file, NULL); + + /* release the file */ + g_object_unref (G_OBJECT (file)); + } } /* schedule an idle source to set the cursor to the current directory */ @@ -624,7 +638,6 @@ GdkEventButton *event) { ThunarTreeView *view = THUNAR_TREE_VIEW (widget); - GtkTreeModel *filter; GtkTreePath *path; GtkTreeIter iter; gboolean result; @@ -646,11 +659,10 @@ if (G_UNLIKELY (event->button == 3 && event->type == GDK_BUTTON_PRESS)) { /* determine the iterator for the path */ - filter = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); - if (gtk_tree_model_get_iter (filter, &iter, path)) + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->model), &iter, path)) { /* popup the context menu */ - thunar_tree_view_context_menu (view, event, filter, &iter); + thunar_tree_view_context_menu (view, event, GTK_TREE_MODEL (view->model), &iter); /* we effectively handled the event */ result = TRUE; @@ -875,15 +887,15 @@ { GtkTreeSelection *selection; ThunarTreeView *view = THUNAR_TREE_VIEW (widget); - GtkTreeModel *filter; + GtkTreeModel *model; GtkTreeIter iter; /* determine the selected row */ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); - if (gtk_tree_selection_get_selected (selection, &filter, &iter)) + if (gtk_tree_selection_get_selected (selection, &model, &iter)) { /* popup the context menu */ - thunar_tree_view_context_menu (view, NULL, filter, &iter); + thunar_tree_view_context_menu (view, NULL, model, &iter); return TRUE; } else if (GTK_WIDGET_CLASS (thunar_tree_view_parent_class)->popup_menu != NULL) @@ -927,14 +939,12 @@ { ThunarVfsVolume *volume; ThunarTreeView *view = THUNAR_TREE_VIEW (tree_view); - GtkTreeModel *filter; GtkWidget *window; gboolean expandable = TRUE; GError *error = NULL; /* determine the volume for the iterator */ - filter = gtk_tree_view_get_model (tree_view); - gtk_tree_model_get (filter, iter, THUNAR_TREE_MODEL_COLUMN_VOLUME, &volume, -1); + gtk_tree_model_get (GTK_TREE_MODEL (view->model), iter, THUNAR_TREE_MODEL_COLUMN_VOLUME, &volume, -1); /* check if we have a volume */ if (G_UNLIKELY (volume != NULL)) @@ -985,7 +995,7 @@ static void thunar_tree_view_context_menu (ThunarTreeView *view, GdkEventButton *event, - GtkTreeModel *filter, + GtkTreeModel *model, GtkTreeIter *iter) { ThunarVfsVolume *volume; @@ -1000,7 +1010,7 @@ return; /* determine the file and volume for the given iter */ - gtk_tree_model_get (filter, iter, + gtk_tree_model_get (model, iter, THUNAR_TREE_MODEL_COLUMN_FILE, &file, THUNAR_TREE_MODEL_COLUMN_VOLUME, &volume, -1); @@ -1209,7 +1219,6 @@ { GdkDragAction actions = 0; GdkDragAction action = 0; - GtkTreeModel *filter; GtkTreePath *path = NULL; GtkTreeIter iter; ThunarFile *file = NULL; @@ -1221,14 +1230,11 @@ /* determine the path for x/y */ if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view), x, y, &path, NULL, NULL, NULL)) { - /* determine the tree model filter */ - filter = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); - /* determine the iter for the given path */ - if (gtk_tree_model_get_iter (filter, &iter, path)) + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->model), &iter, path)) { /* determine the file for the given path */ - gtk_tree_model_get (filter, &iter, THUNAR_TREE_MODEL_COLUMN_FILE, &file, -1); + gtk_tree_model_get (GTK_TREE_MODEL (view->model), &iter, THUNAR_TREE_MODEL_COLUMN_FILE, &file, -1); if (G_LIKELY (file != NULL)) { /* check if the file accepts the drop */ @@ -1288,7 +1294,7 @@ GtkTreePath **ancestor_return, gboolean *exact_return) { - GtkTreeModel *filter; + GtkTreeModel *model = GTK_TREE_MODEL (view->model); GtkTreePath *child_path; GtkTreeIter child_iter; GtkTreeIter iter; @@ -1296,15 +1302,14 @@ gboolean found = FALSE; /* determine the iter for the current path */ - filter = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); - if (!gtk_tree_model_get_iter (filter, &iter, path)) + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (view->model), &iter, path)) return FALSE; /* process all items at this level */ do { /* check if the file for iter is an ancestor of the current directory */ - gtk_tree_model_get (filter, &iter, THUNAR_TREE_MODEL_COLUMN_FILE, &file, -1); + gtk_tree_model_get (model, &iter, THUNAR_TREE_MODEL_COLUMN_FILE, &file, -1); if (G_UNLIKELY (file == NULL)) continue; @@ -1312,16 +1317,16 @@ if (G_UNLIKELY (file == view->current_directory)) { /* we found the very best ancestor iter! */ - *ancestor_return = gtk_tree_model_get_path (filter, &iter); + *ancestor_return = gtk_tree_model_get_path (model, &iter); *exact_return = TRUE; found = TRUE; } else if (thunar_file_is_ancestor (view->current_directory, file)) { /* check if we can find an even better ancestor below this node */ - if (gtk_tree_model_iter_children (filter, &child_iter, &iter)) + if (gtk_tree_model_iter_children (model, &child_iter, &iter)) { - child_path = gtk_tree_model_get_path (filter, &child_iter); + child_path = gtk_tree_model_get_path (model, &child_iter); found = thunar_tree_view_find_closest_ancestor (view, child_path, ancestor_return, exact_return); gtk_tree_path_free (child_path); } @@ -1330,7 +1335,7 @@ if (G_UNLIKELY (!found)) { /* we found an ancestor, not an exact match */ - *ancestor_return = gtk_tree_model_get_path (filter, &iter); + *ancestor_return = gtk_tree_model_get_path (model, &iter); *exact_return = FALSE; found = TRUE; } @@ -1339,7 +1344,7 @@ /* release the file reference */ g_object_unref (G_OBJECT (file)); } - while (!found && gtk_tree_model_iter_next (filter, &iter)); + while (!found && gtk_tree_model_iter_next (model, &iter)); return found; } @@ -1350,14 +1355,14 @@ thunar_tree_view_get_selected_file (ThunarTreeView *view) { GtkTreeSelection *selection; - GtkTreeModel *filter; + GtkTreeModel *model; GtkTreeIter iter; ThunarFile *file = NULL; /* determine file for the selected row */ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); - if (gtk_tree_selection_get_selected (selection, &filter, &iter)) - gtk_tree_model_get (filter, &iter, THUNAR_TREE_MODEL_COLUMN_FILE, &file, -1); + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + gtk_tree_model_get (model, &iter, THUNAR_TREE_MODEL_COLUMN_FILE, &file, -1); return file; } @@ -1369,13 +1374,13 @@ { GtkTreeSelection *selection; ThunarVfsVolume *volume = NULL; - GtkTreeModel *filter; + GtkTreeModel *model; GtkTreeIter iter; /* determine file for the selected row */ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); - if (gtk_tree_selection_get_selected (selection, &filter, &iter)) - gtk_tree_model_get (filter, &iter, THUNAR_TREE_MODEL_COLUMN_VOLUME, &volume, -1); + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + gtk_tree_model_get (model, &iter, THUNAR_TREE_MODEL_COLUMN_VOLUME, &volume, -1); return volume; } @@ -1538,7 +1543,7 @@ { /* determine the toplevel window */ window = gtk_widget_get_toplevel (GTK_WIDGET (view)); - + /* try to eject the volume */ if (!thunar_vfs_volume_eject (volume, window, &error)) { @@ -1789,28 +1794,22 @@ static gboolean -thunar_tree_view_filter_visible_func (GtkTreeModel *filter, - GtkTreeIter *iter, - gpointer user_data) +thunar_tree_view_visible_func (ThunarTreeModel *model, + ThunarFile *file, + gpointer user_data) { ThunarTreeView *view = THUNAR_TREE_VIEW (user_data); - ThunarFile *file; gboolean visible = TRUE; + _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); + _thunar_return_val_if_fail (THUNAR_IS_TREE_MODEL (model), FALSE); + /* if show_hidden is TRUE, nothing is filtered */ if (G_LIKELY (!view->show_hidden)) { - /* check if we should display this row */ - gtk_tree_model_get (filter, iter, THUNAR_TREE_MODEL_COLUMN_FILE, &file, -1); - if (G_LIKELY (file != NULL)) - { - /* we display all non-hidden file and hidden files that are ancestors of the current directory */ - visible = !thunar_file_is_hidden (file) || (view->current_directory == file) - || (view->current_directory != NULL && thunar_file_is_ancestor (view->current_directory, file)); - - /* release the reference on the file */ - g_object_unref (G_OBJECT (file)); - } + /* we display all non-hidden file and hidden files that are ancestors of the current directory */ + visible = !thunar_file_is_hidden (file) || (view->current_directory == file) + || (view->current_directory != NULL && thunar_file_is_ancestor (view->current_directory, file)); } return visible; @@ -1820,7 +1819,7 @@ static gboolean thunar_tree_view_selection_func (GtkTreeSelection *selection, - GtkTreeModel *filter, + GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer user_data) @@ -1835,10 +1834,10 @@ return TRUE; /* determine the iterator for the path */ - if (gtk_tree_model_get_iter (filter, &iter, path)) + if (gtk_tree_model_get_iter (model, &iter, path)) { /* determine the file for the iterator */ - gtk_tree_model_get (filter, &iter, THUNAR_TREE_MODEL_COLUMN_FILE, &file, -1); + gtk_tree_model_get (model, &iter, THUNAR_TREE_MODEL_COLUMN_FILE, &file, -1); if (G_LIKELY (file != NULL)) { /* rows with files can be selected */ @@ -1850,7 +1849,7 @@ else { /* but maybe the row has a volume */ - gtk_tree_model_get (filter, &iter, THUNAR_TREE_MODEL_COLUMN_VOLUME, &volume, -1); + gtk_tree_model_get (model, &iter, THUNAR_TREE_MODEL_COLUMN_VOLUME, &volume, -1); if (G_LIKELY (volume != NULL)) { /* rows with volumes can also be selected */ @@ -2120,9 +2119,8 @@ thunar_tree_view_set_show_hidden (ThunarTreeView *view, gboolean show_hidden) { - GtkTreeModel *filter; - _thunar_return_if_fail (THUNAR_IS_TREE_VIEW (view)); + _thunar_return_if_fail (THUNAR_IS_TREE_MODEL (view->model)); /* normalize the value */ show_hidden = !!show_hidden; @@ -2133,19 +2131,10 @@ /* apply the new setting */ view->show_hidden = show_hidden; - /* lock loading nodes in the tree, see bug #2505 */ - thunar_tree_model_set_lock_ref_node (THUNAR_TREE_MODEL (view->model), TRUE); + /* update the model */ + thunar_tree_model_refilter (view->model); - /* update the filter */ - filter = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); - gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter)); - - /* release the lock */ - thunar_tree_model_set_lock_ref_node (THUNAR_TREE_MODEL (view->model), FALSE); - /* notify listeners */ g_object_notify (G_OBJECT (view), "show-hidden"); } } - - Index: thunar-vfs/thunar-vfs-monitor.c =================================================================== --- thunar-vfs/thunar-vfs-monitor.c (revision 28900) +++ thunar-vfs/thunar-vfs-monitor.c (working copy) @@ -409,32 +409,26 @@ static gboolean -thunar_vfs_monitor_fam_watch (GIOChannel *channel, - GIOCondition condition, - gpointer user_data) +thunar_vfs_monitor_fam_process_events (ThunarVfsMonitor *monitor) { ThunarVfsMonitorEvent event; - ThunarVfsMonitor *monitor = THUNAR_VFS_MONITOR (user_data); FAMEvent fe; - /* check for an error on the FAM connection */ - if (G_UNLIKELY ((condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) != 0)) - { -error: - /* terminate the FAM connection */ - thunar_vfs_monitor_fam_cancel (monitor); + _thunar_vfs_return_val_if_fail (THUNAR_VFS_IS_MONITOR (monitor), FALSE); - /* thats it, no more FAM */ - return FALSE; - } - /* process all pending FAM events */ while (FAMPending (&monitor->fc)) { /* query the next pending event */ if (G_UNLIKELY (FAMNextEvent (&monitor->fc, &fe) < 0)) - goto error; + { + /* terminate the FAM connection */ + thunar_vfs_monitor_fam_cancel (monitor); + /* thats it, no more FAM */ + return FALSE; + } + /* translate the event code */ switch (fe.code) { @@ -456,13 +450,42 @@ } /* schedule a notification for the monitor */ - g_mutex_lock (monitor->lock); thunar_vfs_monitor_queue_notification (monitor, fe.fr.reqnum, THUNAR_VFS_MONITOR_TAG_FAM, event, fe.filename); - g_mutex_unlock (monitor->lock); } return TRUE; } + + + +static gboolean +thunar_vfs_monitor_fam_watch (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + ThunarVfsMonitor *monitor = THUNAR_VFS_MONITOR (user_data); + gboolean result = FALSE; + + /* acquire the monitor lock */ + g_mutex_lock (monitor->lock); + + /* check for an error on the FAM connection */ + if (G_UNLIKELY ((condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) != 0)) + { + /* terminate the FAM connection */ + thunar_vfs_monitor_fam_cancel (monitor); + } + else + { + /* process all pending FAM events */ + result = thunar_vfs_monitor_fam_process_events (monitor); + } + + /* release the monitor lock */ + g_mutex_unlock (monitor->lock); + + return result; +} #endif @@ -634,8 +657,14 @@ /* drop the FAM request from the daemon */ if (G_LIKELY (monitor->fc_watch_id >= 0 && _thunar_vfs_path_is_local (handle->path))) { - if (FAMCancelMonitor (&monitor->fc, &handle->fr) < 0) - thunar_vfs_monitor_fam_cancel (monitor); + /* make sure there are no pending events before removing the request. + * if we don't do this, fam will lock up when a lot of requests are + * removed in a short time (collapse a treeview node for example) */ + if (thunar_vfs_monitor_fam_process_events (monitor)) + { + if (FAMCancelMonitor (&monitor->fc, &handle->fr) < 0) + thunar_vfs_monitor_fam_cancel (monitor); + } } #endif