Index: thunar/thunar-tree-model.c =================================================================== --- thunar/thunar-tree-model.c (revision 28827) +++ 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,57 @@ 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; + ThunarTreeModelVisibleFunc visible_func; + gpointer visible_data; + + 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; + guint unload_idle_id; }; +typedef enum +{ + HAS_DUMMY_NODE, + DUMMY_NODE_UNKNOWN, + NO_DUMMY_NODE, + LOAD_SUBFOLDERS +} ItemState; + struct _ThunarTreeModelItem { gint ref_count; - gint load_idle_id; + + guint load_idle_id; + ThunarFile *file; ThunarFolder *folder; ThunarVfsVolume *volume; ThunarTreeModel *model; + + /* state of the item */ + ItemState state; + + /* list of children of this node that are + * note visible in the tree */ + GSList *invisible_children; }; typedef struct @@ -239,7 +266,7 @@ type = g_type_register_static (G_TYPE_OBJECT, I_("ThunarTreeModel"), &info, 0); g_type_add_interface_static (type, GTK_TYPE_TREE_MODEL, &tree_model_info); } - + return type; } @@ -313,7 +340,8 @@ /* initialize the model data */ model->sort_case_sensitive = TRUE; - model->lock_ref_node = FALSE; + model->visible_func = NULL; + model->visible_data = NULL; /* connect to the file monitor */ model->file_monitor = thunar_file_monitor_get_default (); @@ -346,6 +374,10 @@ /* add the dummy node */ g_node_append_data (node, NULL); + + /* set state and schedule a load */ + item->state = DUMMY_NODE_UNKNOWN; + thunar_tree_model_item_load_folder (item); } /* release the system defined path */ @@ -366,6 +398,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 +748,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 +803,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)) @@ -780,11 +812,21 @@ item = node->data; if (G_UNLIKELY (item == NULL)) { - /* tell the parent to load the folder */ - thunar_tree_model_item_load_folder (node->parent->data); + /* get the parent */ + item = node->parent->data; + + /* set the state of the parent */ + item->state = LOAD_SUBFOLDERS; + + /* tell the parent to load the subfolders */ + thunar_tree_model_item_load_folder (item); } else { + /* the item was previously unloaded, load the dummy state again */ + if (item->ref_count == 0) + thunar_tree_model_item_load_folder (item); + /* increment the reference count */ item->ref_count += 1; } @@ -816,7 +858,7 @@ if (G_UNLIKELY (item->ref_count == 1)) { /* schedule to unload the folder contents */ - thunar_tree_model_item_unload_folder (item); + thunar_tree_model_unload (model); } /* decrement the reference count */ @@ -831,7 +873,6 @@ gconstpointer b, gpointer user_data) { - /* just sort by name (case-sensitive) */ return thunar_file_compare_by_name (THUNAR_TREE_MODEL_ITEM (((const SortTuple *) a)->node->data)->file, THUNAR_TREE_MODEL_ITEM (((const SortTuple *) b)->node->data)->file, THUNAR_TREE_MODEL (user_data)->sort_case_sensitive); @@ -847,11 +888,11 @@ 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 */ + /* get the number of child nodes */ n_children = g_node_n_children (node); if (G_UNLIKELY (n_children <= 1)) return; @@ -863,7 +904,7 @@ sort_array = g_new (SortTuple, n_children); /* generate the sort array of tuples */ - for (child_node = g_node_first_child (node), n = 0; n < n_children; child_node = g_node_next_sibling (child_node), ++n) + for (child_node = node->children, n = 0; n < n_children; child_node = child_node->next, n++) { sort_array[n].node = child_node; sort_array[n].offset = n; @@ -875,7 +916,7 @@ /* start out with an empty child list */ node->children = NULL; - /* update our internals and generate the new order */ + /* update our internals and generate the new order for the visible items */ new_order = g_newa (gint, n_children); for (n = 0; n < n_children; ++n) { @@ -905,6 +946,41 @@ static void +thunar_tree_model_unload (ThunarTreeModel *model) +{ + /* schedule an idle unload, if not already done */ + if (model->unload_idle_id == 0) + { + model->unload_idle_id = g_idle_add_full (G_PRIORITY_LOW, 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); + + /* 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); + + 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) @@ -927,7 +1003,6 @@ ThunarTreeModelItem *item = NULL; GtkTreePath *path; GtkTreeIter iter; - GNode *dummy; GNode *node; GList list; GList *lp; @@ -960,17 +1035,10 @@ gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); gtk_tree_path_free (path); - /* add the dummy node */ - node = g_node_append_data (node, NULL); + /* append the dummy node */ + thunar_tree_model_node_insert_dummy (node, model); + item->state = HAS_DUMMY_NODE; - /* 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 +1094,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 +1115,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 +1138,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); } @@ -1176,6 +1226,7 @@ item = _thunar_slice_new0 (ThunarTreeModelItem); item->file = g_object_ref (G_OBJECT (file)); item->model = model; + item->state = HAS_DUMMY_NODE; return item; } @@ -1192,6 +1243,7 @@ item = _thunar_slice_new0 (ThunarTreeModelItem); item->volume = g_object_ref (G_OBJECT (volume)); item->model = model; + item->state = HAS_DUMMY_NODE; /* check if the volume is mounted */ if (thunar_vfs_volume_is_mounted (volume)) @@ -1240,6 +1292,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)) { @@ -1259,7 +1319,7 @@ thunar_tree_model_item_load_folder (ThunarTreeModelItem *item) { /* schedule the "load" idle source (if not already done) */ - if (G_LIKELY (item->folder == NULL && item->load_idle_id == 0)) + if (G_LIKELY (item->load_idle_id == 0)) { item->load_idle_id = g_idle_add_full (G_PRIORITY_HIGH, thunar_tree_model_item_load_idle, item, thunar_tree_model_item_load_idle_destroy); @@ -1269,14 +1329,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) @@ -1293,65 +1345,115 @@ _thunar_return_if_fail (THUNAR_IS_FOLDER (folder)); _thunar_return_if_fail (item->folder == folder); - /* process all specified files */ - for (lp = files; lp != NULL; lp = lp->next) + switch (item->state) { - /* we don't care for anything except folders */ - file = THUNAR_FILE (lp->data); - if (!thunar_file_is_directory (file)) - continue; - - /* lookup the node for the item (on-demand) */ - if (G_UNLIKELY (node == NULL)) + /* the item is waiting for a dummy node. this means we (still) don't know + * if it has visible child nodes. */ + case DUMMY_NODE_UNKNOWN: + case NO_DUMMY_NODE: + /* find the node */ node = g_node_find (model->root, G_POST_ORDER, G_TRAVERSE_ALL, item); - /* allocate a new item for the file */ - child_item = thunar_tree_model_item_new_with_file (model, file); + for (lp = files; lp != NULL; lp = lp->next) + { + /* we don't care for anything except folders. if it is a folder + * ask the user if it is visible */ + if (thunar_file_is_directory (lp->data) + && (model->visible_func == NULL + || model->visible_func (model, lp->data, model->visible_data))) + { + if (item->state == NO_DUMMY_NODE) + { + /* insert a node if there is no node */ + node = g_node_find (model->root, G_POST_ORDER, G_TRAVERSE_ALL, item); + thunar_tree_model_node_insert_dummy (node, model); + } - /* check if the node has only the dummy child */ - if (g_node_n_children (node) == 1 && g_node_first_child (node)->data == NULL) - { - /* replace the dummy node with the new node */ - child_node = g_node_first_child (node); - child_node->data = child_item; + /* update the state */ + item->state = HAS_DUMMY_NODE; - /* determine the tree iter for the child */ - GTK_TREE_ITER_INIT (child_iter, model->stamp, child_node); + break; + } + } + break; - /* emit a "row-changed" for the new node */ - child_path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &child_iter); - gtk_tree_model_row_changed (GTK_TREE_MODEL (model), child_path, &child_iter); - gtk_tree_path_free (child_path); - } - else - { - /* insert a new item for the child */ - child_node = g_node_append_data (node, child_item); + /* the item is (except for the root nodes) reffed and we need to load the + * subfolders. each subfolder added to the tree is initialized with an + * DUMMY_NODE_UNKNOWN dummy state. */ + case LOAD_SUBFOLDERS: + /* process all specified files */ + for (lp = files; lp != NULL; lp = lp->next) + { + /* we don't care for anything except folders */ + file = THUNAR_FILE (lp->data); + if (!thunar_file_is_directory (file)) + continue; - /* determine the tree iter for the child */ - GTK_TREE_ITER_INIT (child_iter, model->stamp, child_node); + /* if this file should be visible */ + if (model->visible_func + && model->visible_func (model, file, model->visible_data) == FALSE) + { + /* 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; + } - /* emit a "row-inserted" for the new 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); - } + /* allocate a new item for the file */ + child_item = thunar_tree_model_item_new_with_file (model, file); - /* add a dummy child node */ - child_node = g_node_append_data (child_node, NULL); + /* 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); - /* determine the tree iter for the dummy */ - GTK_TREE_ITER_INIT (child_iter, model->stamp, child_node); + /* check if the node has only the dummy child */ + if (G_NODE_HAS_DUMMY (node)) + { + /* replace the dummy node with the new node */ + child_node = g_node_first_child (node); + child_node->data = child_item; - /* 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); - } + /* determine the tree iter for the child */ + GTK_TREE_ITER_INIT (child_iter, model->stamp, child_node); - /* sort the folders if any new ones were added */ - if (G_LIKELY (node != NULL)) - thunar_tree_model_sort (model, node); + /* emit a "row-changed" for the new node */ + child_path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &child_iter); + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), child_path, &child_iter); + gtk_tree_path_free (child_path); + } + else + { + /* insert a new item for the child */ + child_node = g_node_append_data (node, child_item); + + /* determine the tree iter for the child */ + GTK_TREE_ITER_INIT (child_iter, model->stamp, child_node); + + /* emit a "row-inserted" for the new 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); + } + + /* insert a dummy node */ + thunar_tree_model_node_insert_dummy (child_node, model); + + /* we want to know if the item needs a dummy */ + child_item->state = DUMMY_NODE_UNKNOWN; + + /* schedule a load for the dummy */ + thunar_tree_model_item_load_folder (child_item); + } + + /* sort the folders if any new ones were added */ + if (G_LIKELY (node != NULL)) + thunar_tree_model_sort (model, node); + break; + + case HAS_DUMMY_NODE: + /* nothing to do */ + break; + }; } @@ -1367,6 +1469,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); @@ -1377,19 +1480,33 @@ /* check if the node has any visible children */ if (G_LIKELY (node->children != NULL)) { - /* process all files */ - for (lp = files; lp != NULL; lp = lp->next) + if (item->state == HAS_DUMMY_NODE) { - /* find the child node for the file */ - for (child_node = g_node_first_child (node); child_node != NULL; child_node = g_node_next_sibling (child_node)) - if (child_node->data != NULL && THUNAR_TREE_MODEL_ITEM (child_node->data)->file == lp->data) - break; + /* remove the dummy node */ + thunar_tree_model_node_traverse_remove (node->children, model); - /* drop the child node (and all descendant nodes) from the model */ - if (G_LIKELY (child_node != NULL)) - g_node_traverse (child_node, G_POST_ORDER, G_TRAVERSE_ALL, -1, thunar_tree_model_node_traverse_remove, model); + /* change the state */ + item->state = NO_DUMMY_NODE; + + /* schedule a folder reload to see if we need a dummy */ + thunar_tree_model_item_load_folder (item); } + else + { + /* process all files */ + for (lp = files; lp != NULL; lp = lp->next) + { + /* find the child node for the file */ + for (child_node = g_node_first_child (node); child_node != NULL; child_node = g_node_next_sibling (child_node)) + if (child_node->data != NULL && THUNAR_TREE_MODEL_ITEM (child_node->data)->file == lp->data) + break; + /* drop the child node (and all descendant nodes) from the model */ + if (G_LIKELY (child_node != NULL)) + g_node_traverse (child_node, G_POST_ORDER, G_TRAVERSE_ALL, -1, thunar_tree_model_node_traverse_remove, model); + } + } + /* check if all children of the node where dropped */ if (G_UNLIKELY (node->children == NULL)) { @@ -1400,8 +1517,31 @@ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), path, &iter); gtk_tree_path_free (path); + + /* from now on we also accept a dummies if the node is not in + * subfolder load state */ + if (item->state != LOAD_SUBFOLDERS) + item->state = NO_DUMMY_NODE; } } + + /* 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); + } + } + } } @@ -1416,14 +1556,19 @@ _thunar_return_if_fail (THUNAR_IS_FOLDER (folder)); _thunar_return_if_fail (item->folder == folder); - /* be sure to drop the dummy child node once the folder is loaded */ - if (G_LIKELY (!thunar_folder_get_loading (folder))) + /* drop the dummy if the state is still unknown + * but loading the folder is finished */ + if (item->state == DUMMY_NODE_UNKNOWN + && !thunar_folder_get_loading (folder)) { - /* lookup the node for the item... */ + /* determine the node */ node = g_node_find (item->model->root, G_POST_ORDER, G_TRAVERSE_ALL, item); - /* ...and drop the dummy for the node */ + /* drop the dummy node */ thunar_tree_model_node_drop_dummy (node, item->model); + + /* update the state */ + item->state = NO_DUMMY_NODE; } } @@ -1435,8 +1580,6 @@ ThunarTreeModelItem *item = user_data; GList *files; - _thunar_return_val_if_fail (item->folder == NULL, FALSE); - GDK_THREADS_ENTER (); /* check if we don't have a file yet and this is a mounted volume */ @@ -1449,24 +1592,24 @@ /* verify that we have a file */ if (G_LIKELY (item->file != NULL)) { - /* open the folder for the item */ - item->folder = thunar_folder_get_for_file (item->file); - if (G_LIKELY (item->folder != NULL)) + /* verify that we have a folder */ + if (item->folder == NULL) { - /* connect signals */ + /* create a folder */ + item->folder = thunar_folder_get_for_file (item->file); g_signal_connect_swapped (G_OBJECT (item->folder), "files-added", G_CALLBACK (thunar_tree_model_item_files_added), item); g_signal_connect_swapped (G_OBJECT (item->folder), "files-removed", G_CALLBACK (thunar_tree_model_item_files_removed), item); g_signal_connect_swapped (G_OBJECT (item->folder), "notify::loading", G_CALLBACK (thunar_tree_model_item_notify_loading), item); + } - /* load the initial set of files (if any) */ - files = thunar_folder_get_files (item->folder); - if (G_UNLIKELY (files != NULL)) - thunar_tree_model_item_files_added (item, files, item->folder); + /* get all the known files of this folder */ + files = thunar_folder_get_files (item->folder); + if (G_UNLIKELY (files != NULL)) + thunar_tree_model_item_files_added (item, files, item->folder); - /* notify for "loading" if already loaded */ - if (!thunar_folder_get_loading (item->folder)) - g_object_notify (G_OBJECT (item->folder), "loading"); - } + /* notify for "loading" if already loaded */ + if (!thunar_folder_get_loading (item->folder)) + g_object_notify (G_OBJECT (item->folder), "loading"); } GDK_THREADS_LEAVE (); @@ -1485,14 +1628,40 @@ 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)); + + /* 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; + _thunar_return_if_fail (THUNAR_IS_TREE_MODEL (model)); + /* check if we still have the dummy child node */ - if (g_node_n_children (node) == 1 && node->children->data == NULL) + if (G_NODE_HAS_DUMMY (node)) { /* determine the iterator for the dummy */ GTK_TREE_ITER_INIT (iter, model->stamp, node->children); @@ -1525,16 +1694,54 @@ 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 (G_LIKELY (item && item->ref_count == 0)) + { + /* disconnect from the folder */ + if (G_LIKELY (item->folder != NULL)) + { + 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; + } + + if (node->children) + { + /* 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); + + _thunar_assert (node->children == NULL); + + /* change item status and insert a dummy node */ + thunar_tree_model_node_insert_dummy (node, model); + item->state = DUMMY_NODE_UNKNOWN; + } + } + + return FALSE; +} + + + +static gboolean 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 (item != NULL && item->file == file) { /* determine the tree model from the item */ model = THUNAR_TREE_MODEL_ITEM (node->data)->model; @@ -1557,8 +1764,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 +1834,102 @@ +static gboolean +thunar_tree_model_node_traverse_visible (GNode *node, + gpointer user_data) +{ + ThunarTreeModelItem *item = node->data; + gboolean visible = TRUE; + ThunarTreeModel *model = THUNAR_TREE_MODEL (user_data); + GtkTreePath *path; + GtkTreeIter iter; + GNode *child_node; + GSList *lp, *lnext; + ThunarTreeModelItem *parent, *child; + ThunarFile *file; + + if (G_LIKELY (item && model->visible_func)) + { + /* request if this node/file should be visible or hidden */ + visible = !!model->visible_func (model, item->file, model->visible_data); + if (G_UNLIKELY (visible == FALSE)) + { + /* 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); + + /* set new state */ + child->state = DUMMY_NODE_UNKNOWN; + + /* insert dummy */ + thunar_tree_model_node_insert_dummy (child_node, model); + + /* schedule folder load */ + thunar_tree_model_item_load_folder (child); + } + } + + /* 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 +2019,36 @@ +/** + * thunar_tree_model_set_visible_func: + * @model : a #ThunarTreeModel. + * @func : + * @data : + **/ 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; + + /* set the new visiblity function and user data */ + model->visible_func = func; + model->visible_data = data; } + + + +/** + * thunar_tree_model_refilter: + * @model : a #ThunarTreeModel. + **/ +void +thunar_tree_model_refilter (ThunarTreeModel *model) +{ + _thunar_return_if_fail (THUNAR_IS_TREE_MODEL (model)); + + /* traverse all nodes to update the state */ + 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 28827) +++ 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 28827) +++ 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 hidden files are not visible */ + 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; } @@ -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 28827) +++ thunar-vfs/thunar-vfs-monitor.c (working copy) @@ -170,12 +170,23 @@ +static gboolean +thunar_vfs_monitor_print_handles (gpointer user_data) +{ + g_message ("%d handles in file monitor", g_list_length (THUNAR_VFS_MONITOR (user_data)->handles)); + return TRUE; +} + + + static void thunar_vfs_monitor_init (ThunarVfsMonitor *monitor) { /* initialize the monitor */ monitor->cond = g_cond_new (); monitor->lock = g_mutex_new (); + + g_timeout_add_seconds (10, thunar_vfs_monitor_print_handles, monitor); #ifdef HAVE_LIBFAM if (FAMOpen2 (&monitor->fc, PACKAGE_NAME) == 0) @@ -634,6 +645,10 @@ /* drop the FAM request from the daemon */ if (G_LIKELY (monitor->fc_watch_id >= 0 && _thunar_vfs_path_is_local (handle->path))) { + /* make sure there are no pending events when we remove the request. if we + * don't do this, fam will lock up if too many requests are cancelled */ + thunar_vfs_monitor_fam_watch (NULL, 0, monitor); + if (FAMCancelMonitor (&monitor->fc, &handle->fr) < 0) thunar_vfs_monitor_fam_cancel (monitor); }