Reference counting in GtkTreeModel, ambiguity on node visibility

Many people are confused by what exactly the reference counting methods ref_node() and unref_node() in the GtkTreeModel interface are intended for. These methods are a way for views (or more generically, model observers) to let models know when nodes are being displayed. For example, GtkTreeview will obtain a reference on a node when the node is visible and release the reference when the node becomes invisible. And this is immediately the main source of the confusion: the confusion typically centers around the exact definition of node visibility.

There are two definitions of visibility in use:

  1. A node is visible when it is visible on the screen, that is, the user can currently see it. This is the most obvious definition of this term. For a node to be visible according to this definition, the node’s parent must be in expanded state, secondly, the coordinates of the node must be within the coordinates that are currently visible in the tree view’s scrollable viewport.
  2. A node is visible when its parent node is in the expanded state (or the node is in the root level in which case you can say that it is always visible because its implicit parent is always in expanded state). Do note that when a node is visible according to this definition, it might not be immediately visible to the user! This definition does not say anything about the current position of the scrollbar.

For the reference counting mechanism of GtkTreeModel, the second definition is in use. So, what is the point exactly in knowing whether a node’s parent is expanded? The design rationale of the reference counting mechanism is to be able to delay loading content of child nodes until it is really necessary. A useful use case is a file system model. You do not want to load in an entire file system tree when the model is being instantiated. Instead, only the root level is loaded. All nodes in the root level will be referenced when the model is attached to a view. In response to this, one could decide to load ahead the child nodes of the root level nodes. Once a root level node is expanded, the children of this node get referenced, and so on. Similarly, when nodes are collapsed, nodes are unreferenced. This information can be used to possibly prune nodes from the tree which are no longer necessary to keep in memory.

Rather unfortunate is that usually you want to avoid loading child nodes if the parent node has not yet been expanded. Especially when you are for example operating on a remote file system. A preferred approach is often to delay loading the nodes until the parent is expanded, displaying a “Loading …” node in the level until the real child nodes are retrieved. The reference counting mechanism does not really aid you to implement that, except that you can re-use it for cache pruning.

The reference counting scheme is not of much use for lists either. Generally, for lists, developers are mostly interested in the first definition of visible. When dealing with lists with large numbers of nodes, you want to avoid loading all data from the data source if this is expensive. Alas, because all nodes in the root level get referenced as soon as the model is attached to a view and there is no relation between the reference count and whether a node is visible on the screen, other methods have to be used to implement this. The fact that models are separated from views and that there is no mechanism to relay information about which nodes are visible on the screen to a model, makes it hard to implement this. What is really needed is a proper “lazy loading model interface”, such that views and models can exchange information on this.

The scheme does work very well for GtkTreeModelSort and GtkTreeModelFilter. These models actually must load child nodes as soon their parent is referenced. GtkTreeModelFilter must process the child nodes (i.e. performing the filter function on these nodes) in order to determine whether the child nodes are visible at all and thus whether the parent node actually has children. Recall that as soon as a node is visible, it is queried whether it has any children such that an expander can be drawn next to the node.