Significantly improves performance when e.g. removing (filtering) a lot
of rows from the filter model. Fixes bug 616871.
This commit includes changes by Kristian Rietveld to make the patch apply
on top of the treemodel-fix branch and pass all newly written unit tests.
This allows for more thorough testing of "has child" filter functions.
We also test a has child filter function with a sort model as
child model, to verify that we receive enough signals to function
properly.
The filter model is now more strict about the signals which are emitted,
so the base tests have been expanded to test with both the root level
nodes collapsed and expanded.
- Before we kept a reference on all nodes in non-root levels. This has
been changed, now we keep a reference on the first node of each level.
If, due to changes in the model, another node becomes the first node in
the level, the reference is transferred to this new first node.
- All non-root levels keep a reference on their parent.
- By making use of the external ref count, the filter model now emits less
unnecessary signals.
- GtkTreeModelFilter does support filter functions which decide visibility
of a given node based on the number of or visibility of children.
To accomplish this, a child level of a node is cached when its
parent has an external ref count > 0, because changes to the node might
affect this parent.
- An optimization for not building the root level in case the inserted
node is not visible in gtk_tree_model_filter_row_inserted() has been
removed. In this case, we still need to build the root level and
possibly a child level to monitor for signals which might make
this row visible.
In gtk_tree_model_filter_check_ancestors(), also handle the case when
a node is already in the cache, but invisible, in the root level.
With the upcoming changes to GtkTreeModelFilter's ref counting this
case can occur.
We need to distinguish between the ref count objects have on us versus
the ref count we have on our child model. To keep track of the former,
we introduce the "external ref count" in this commit. The zero_ref_count
needs to be determined from the external ref count, because objects that
have a ref count on us have say in which levels must be cached and which
can be released.
Before the caching in GtkTreeModelFilter was essentially broken and
levels were never released. This was caused because the zero_ref_count
was connected to the ref count the filter model had on its child model.
Now that this depends on the external ref count, this is working fine and
as to be expected.
The row-deleted signal should be emitted after the internal data
structures have been updated. In gtk_tree_model_filter_remove_elt_from_level
and gtk_tree_model_filter_virtual_root_deleted the signal was still being
emitted before the updates were carried out.
Now that we call unref_node in free_level, we have to take care that
free_level may only unref (parent) nodes when these still exist in the
child model. After row-deleted has been received for a node, its
children may no longer unref this node.
Referencing a parent node for each referenced node is overdone. Instead,
we now reference the parent from build_level and unreference in free_level.
Each level keeps a single reference on its immediate parent. This both
alleviates the performence problems and should perfectly serve the purpose.
The bulk of the fix is to walk the chain of ancestors, starting at the
root level, and check if the visibility of any of the ancestors has
changed. If yes, the necessary signals are emitted so that this change
is propagated properly. This walk is done after a node has been
inserted, changed or deleted, see function
gtk_tree_model_filter_check_ancestors().
Bug reported, and initial debugging and analysis, by Xavier Claessens.
Suggested by Xavier Claessens / bug 621076.
(Additional obseration: this should speed up the filter model's
handling of row-inserted as a binary search is now used instead
of a linear scan).