[model] make the GtkTreeModel lazy



commit 1600bb072e76669a64aaa7194bfd4a6ddf383bf1
Author: Ryan Lortie <desrt desrt ca>
Date:   Fri Mar 12 02:42:32 2010 -0500

    make the GtkTreeModel lazy

 gtk/mg.vala        |   11 +-
 gtk/model-gtk.vala |  524 ++++++++++++++++++++++++++++++++++++----------------
 2 files changed, 373 insertions(+), 162 deletions(-)
---
diff --git a/gtk/mg.vala b/gtk/mg.vala
index 025acd8..8d9bbe3 100644
--- a/gtk/mg.vala
+++ b/gtk/mg.vala
@@ -1,6 +1,11 @@
 Gtk.TreeView v;
 Model.List list;
 
+bool setup () {
+  v.set_model (new Model.GtkModel (list, {typeof(Model.List), typeof(string),typeof(string)}, {"contents", "name", "type"}, false));
+  return false;
+}
+
 void main (string[]args) {
   Gtk.init (ref args);
 
@@ -8,10 +13,10 @@ void main (string[]args) {
   w.set_default_size (800, 600);
   list = new FS.Directory (File.new_for_path ("/home/desrt/Desktop/small"));
   v = new Gtk.TreeView ();
-  v.insert_column_with_attributes (0, "name", new Gtk.CellRendererText (), "text", 0);
-  v.insert_column_with_attributes (0, "type", new Gtk.CellRendererText (), "text", 1);
-  v.set_model (new Model.GtkModel (list, {typeof(string),typeof(string)}, {"name", "type"}, "contents"));
+  v.insert_column_with_attributes (0, "name", new Gtk.CellRendererText (), "text", 1);
+  v.insert_column_with_attributes (0, "type", new Gtk.CellRendererText (), "text", 2);
   var sc = new Gtk.ScrolledWindow (null, null);
+  setup ();
   sc.add (v);
   w.add (sc);
   w.show_all ();
diff --git a/gtk/model-gtk.vala b/gtk/model-gtk.vala
index e5ec1b7..cc42ec7 100644
--- a/gtk/model-gtk.vala
+++ b/gtk/model-gtk.vala
@@ -1,215 +1,397 @@
 namespace Model {
+	/* The ListTracker is responsible for tracking a Model.List in a way that is convenient for a GtkTreeModel
+	 * implementation.  It attempts to be lazy, minimising work where possible and only sending signals for
+	 * things that an outside observer can know has changed.  For example, if an outside observer knows that the
+	 * list has children but not how many and a new child is added, then no "insert" signal will be sent because
+	 * as far as the outside observer knows, that child was always there.
+	 *
+	 * To this end, there are three states that a ListTracker can be in:
+	 *
+	 *   0 - Freshly created: this is the initial state.  The ListTracker is holding reference to a Model.List
+	 *                        and watching for changes.  The ListTracker knows the length of the Model.List but
+	 *                        has not exposed this information.  No child elements have been accessed.  At this
+	 *                        point the only signal that will be sent on the model in response to changes is
+	 *                        row_has_child_toggled, if appropriate.  Not sending remove (and specifically)
+	 *                        insert signals from this state decreases the likelihood of a view making
+	 *                        unnecessary query calls back into the model in response to those signals and
+	 *                        forcing more of the model to be pulled into existence in order to reply to those
+	 *                        queries.
+	 *
+	 *   1 - Length exposed:  this is the state that the ListTracker enters after the length has been exposed
+	 *                        (ie: get_length() has been called).  In this state the ListTracker gains no
+	 *                        additional internal information about the Model.List, but behaves differently with
+	 *                        respect to signal emissions.  Specifically: insert and removes to the list are
+	 *                        singled as inserts and removes on the GtkTreeModel so that the GtkTreeModel can
+	 *                        know that the length has changed.  We could probably get away with not sending the
+	 *                        proper signals here but it would technically be possible for a view to detect the
+	 *                        inconsistency so we err on the safe side.
+	 *
+	 *   2 - Child exposed:   this is the state that the ListTracker enters after a child has been exposed (ie:
+	 *                        get_child() has been called).  In this state, the ListTracker has created the
+	 *                        appropriate number of DictionaryTracker children and is holding reference to them
+	 *                        in both linked list and array form.  Changes are signaled as per the previous
+	 *                        state.
+	 *
+	 * The change between state 0 and state 1 is signaled by the setting of the 'length_exposed' flag to true.
+	 * The change between state 1 and state 2 is signaled by setting of 'first' and 'array' to non-null values.
+	 * Note that the state 2 implies the 'length_exposed' is already set to true since full signals will need to
+	 * be sent once any child has been exposed.
+	 *
+	 * We don't bother with a state where we're not monitoring anything at all because the ListTracker is only
+	 * created in the first place when it is required to satisfy a call which is *at least* has_child() at which
+	 * point we need to be monitoring the Model.List so that we can emit row_has_child_toggled.
+	 */
 	class ListTracker {
-		internal DictionaryTracker? first;
+		/* We store our child DictionaryTrackers in two forms: an array and a linked list.  Each
+		 * DictionaryTracker contains a 'next' pointer and the 'first' pointer here always points at the first
+		 * one.
+		 *
+		 * Having only a linked list would cause the following two operations to occur in O(n) time because they
+		 * would require traversal of the linked list:
+		 *
+		 *   - getting a child at a particular index.  This is used for implementing
+		 *     GtkTreeModel.iter_nth_child() and Gtk.TreeModel.get_iter (ie: from Gtk.TreePath).
+		 *
+		 *   - getting the index of a particular child.  This is used for implementing GtkTreeModel.get_path
+		 *     (ie: essentially a reverse-lookup from a Gtk.TreeIter).
+		 *
+		 * The only way to make the first operation occur in O(1) time is to keep an array of the
+		 * DictionaryTrackers.  The only way to make the second operation occur in O(1) time is to store within
+		 * each DictionaryTracker its own index within its parent.
+		 *
+		 * Keeping this information at all times would make large change operations on the list very slow since
+		 * GtkTreeModel requires emission of delete/insert signals one at a time and the model must be
+		 * consistent at the time each signal is emitted (ie: each signal emission would cost O(n) to resize the
+		 * array and update the index on each child).
+		 *
+		 * For this reason, the array is not kept in the midst of change operations and is only recreated after
+		 * all changes have been merged and signaled.  During this time 'array' is set to null and linked list
+		 * traversal must be used for both forward and reverse lookups.  During times when 'array' is not set to
+		 * null then 'array' can be used for forward lookups and the index stored by each DictionaryTracker can
+		 * be assumed to be valid.
+		 */
 		DictionaryTracker[]? array;
-		internal int length;
+		DictionaryTracker? first;
+
+		// The signal handler id for the 'changed' signal on the Model.List
 		ulong changed_id;
 
+		// The number of items in the list.  This is always known, even when 'length_exposed' is set to false.
+		ulong length;
+
+		/* Weak references to avoid reference cycles.  The parent reference is used for
+		 * Gtk.TreeModel.iter_get_parent() and the model reference is used for emitting signals.
+		 */
 		internal weak Model.DictionaryTracker? parent;
 		internal weak GtkModel model;
-		Model.List source;
 
+		// We hold a reference on the list in order to keep it alive so it will keep sending signals to us.
+		Model.List? source;
+
+		/* Multiple Model.List change operations tend to be signaled in ascending order of position (as
+		 * suggested to implementors by the Model.List API documentation).  In order to avoid re-traversing the
+		 * linked list up to the current point, we keep a cache of where we left off last time and what the
+		 * position was.  This lets us merely 'seek' to the start of the next group of changes.
+		 *
+		 * As a side-effect, we can inspect 'cached_child != null' to determine if we are currently in the midst
+		 * of a change.  We can use this information to avoid creating 'array' for ourselves during reentrancy
+		 * from a signal emission.
+		 */
 		DictionaryTracker **cached_child;
 		ulong cached_child_index;
 
-		internal Gtk.TreePath get_tree_path () {
-			if (parent != null) {
-				return ((!) parent).get_tree_path ();
-			} else {
-				return new Gtk.TreePath ();
-			}
+		// Set to true if the length has been exposed (see overview comment for ListTracker)
+		bool length_exposed;
+
+		internal bool has_child () {
+			return length > 0;
 		}
 
-		void source_changed (Model.List source, ulong position, ulong deleted, ulong inserted, bool more) {
-			bool was_empty;
+		internal ulong n_children () {
+			length_exposed = true;
 
-			was_empty = length == 0;
-			array = null;
+			return length;
+		}
 
-			/* seek to position */
-			if (cached_child_index > position) {
-				cached_child = null;
-			}
+		internal DictionaryTracker? get_child_for_index (ulong index) {
+			length_exposed = true;
 
-			if (cached_child == null) {
-				cached_child = &first;
+			if (index < length) {
+				if (first == null) {
+					/* length > 0, so we should have at least one child here.
+					 * Clearly we are in state 1 and need to move to state 2.
+					 */
+					create_child_linked_list ();
+					assert (first != null);
 
-				for (cached_child_index = 0; cached_child_index < position; cached_child_index++) {
-					cached_child = &((DictionaryTracker) (*cached_child)).next;
+					/* This could easily be a reentrance from a signal emission.
+					 * Only create 'array' if it's not.
+					 */
+					if (cached_child == null) {
+						create_child_array ();
+					}
 				}
-			}
 
-			var path = get_tree_path ();
-			path.append_index ((int) position);
+				if (array == null) {
+					var link = first;
 
-			while (deleted --> 0) {
-				model.row_deleted (path);
+					while (index --> 0) {
+						link = link.next;
+					}
 
-				var tmp = *cached_child;
-				*cached_child = (owned) ((DictionaryTracker) tmp).next;
-				delete tmp;
-				length--;
+					return link;
+				} else {
+					assert (index < array.length);
+					assert (array[index] != null);
+					return array[index];
+				}
+			} else {
+				return null;
 			}
+		}
 
-			path.up ();
+		internal ulong get_index_for_child (DictionaryTracker child) {
+			assert (length_exposed);
+			assert (first != null);
 
-			while (inserted --> 0) {
-				var dict = source.get_child (position) as Model.Dictionary;
-				Gtk.TreeIter iter;
-
-				*cached_child = new DictionaryTracker (this, dict, *cached_child);
-				path.append_index ((int) position++);
+			if (array != null) {
+				assert (array[child.index] == child);
+				return child.index;
+			} else {
+				var link = first;
+				var i = 0;
 
-				if (model.write_iter (out iter, *cached_child)) {
-					model.row_inserted (path, iter);
+				while (link != child) {
+					link = link.next;
+					i++;
 				}
 
-				path.up ();
-				length++;
+				return i;
 			}
+		}
 
-			if (was_empty != (length == 0)) {
-				Gtk.TreeIter iter;
-
-				if (model.write_iter (out iter, parent)) {
-					model.row_has_child_toggled (path, iter);
-				}
-			}
+		internal Model.Dictionary get_dictionary_for_child (DictionaryTracker child) {
+			var dictionary = source.get_child (get_index_for_child (child)) as Model.Dictionary;
+			assert (dictionary != null);
+			return dictionary;
+		}
 
-			if (more == false) {
-				cached_child = null;
-				build_array ();
+		void create_child_linked_list () {
+			for (var i = 0; i < length; i++) {
+				first = new DictionaryTracker (this, first);
 			}
 		}
 
-		void build_array () {
+		void create_child_array () {
 			var link = first;
 
+			assert (array == null);
+
 			array = new DictionaryTracker[length];
 			for (var i = 0; i < length; i++) {
 				array[i] = link;
 				link.index = i;
+
 				link = link.next;
 			}
-
 			assert (link == null);
 		}
 
-		internal ListTracker (GtkModel model, Model.List source, DictionaryTracker? parent = null) {
+		internal ListTracker (GtkModel model, DictionaryTracker? parent, Model.List? source) {
+			this.length_exposed = length_exposed;
 			this.parent = parent;
 			this.source = source;
 			this.model = model;
 
-			length = (int) source.n_children ();
-			for (var i = length; i --> 0;) {
-				var dict = source.get_child (i) as Model.Dictionary;
-				first = new DictionaryTracker (this, dict, first);
+			if (source != null) {
+				length = source.n_children ();
+				changed_id = source.changed.connect (source_changed);
 			}
+		}
 
-			changed_id = source.changed.connect (source_changed);
-			build_array ();
+		internal Gtk.TreePath get_tree_path () {
+			if (parent == null) {
+				return new Gtk.TreePath ();
+			}
+
+			return parent.get_tree_path ();
 		}
 
-		internal DictionaryTracker? get_nth (int n) {
-			if (array != null) {
-				return array[n];
-			} else {
-				var dt = first;
+		void check_has_child_toggled (bool was_empty, Gtk.TreePath path) {
+			if (was_empty != (length == 0)) {
+				Gtk.TreeIter iter;
 
-				while (dt != null && n --> 0) {
-					dt = dt.next;
+				if (model.write_iter (out iter, parent)) {
+					model.row_has_child_toggled (path, iter);
 				}
-
-				return dt;
 			}
 		}
 
-		internal DictionaryTracker? get_for_path (Gtk.TreePath path, int depth = 0) {
-			var item = get_nth (path.get_indices ()[depth++]);
+		void source_changed (Model.List source, ulong position, ulong deleted, ulong inserted, bool more) {
+			var was_empty = length == 0;
+			var path = get_tree_path ();
 
-			if (item != null && depth < path.get_depth ()) {
-				if (item.children != null) {
-					item = item.children.get_for_path (path, depth);
-				} else {
-					item = null;
+			if (!length_exposed) {
+				// If the length hasn't been exposed then only signal changes in has_child.
+				length += inserted - deleted;
+				check_has_child_toggled (was_empty, path);
+				return;
+			}
+
+			array = null;
+
+			/* seek to position */
+			if (cached_child_index > position) {
+				cached_child = null;
+			}
+
+			if (cached_child == null) {
+				cached_child = &first;
+
+				for (cached_child_index = 0; cached_child_index < position; cached_child_index++) {
+					cached_child = &((DictionaryTracker) (*cached_child)).next;
 				}
 			}
 
-			return item;
-		}
+			path.append_index ((int) position);
 
-		internal int find (DictionaryTracker child) {
-			if (array != null) {
-				assert (array[child.index] == child);
-				return child.index;
-			} else {
-				var link = first;
-				var i = 0;
+			while (deleted --> 0) {
+				model.row_deleted (path);
 
-				while (link != child) {
-					link = link.next;
-					i++;
+				var tmp = *cached_child;
+				*cached_child = (owned) ((DictionaryTracker) tmp).next;
+				delete tmp;
+				length--;
+			}
+
+			path.up ();
+
+			while (inserted --> 0) {
+				*cached_child = new DictionaryTracker (this, *cached_child);
+				weak DictionaryTracker dt = *cached_child;
+				Gtk.TreeIter iter;
+
+				path.append_index ((int) position++);
+				model.write_iter (out iter, dt);
+				cached_child = &dt.next;
+				length++;
+
+				model.row_inserted (path, iter);
+
+				/* Huge hack alert:
+				 *
+				 * Although GtkTreeModel allows models to be 'lazy' by not producing data until the view has
+				 * asked for it, the API does not allow for the possibility to be lazy about inserting new data.
+				 * This is a simple consequence of the fact that all signals must be sent at all times.
+				 *
+				 * Consider the case of a filesystem model where a new (populated) directory suddenly appears
+				 * (as if moved in from another location).  It is not enough to simply send the 'row_inserted'
+				 * signal for the directory.  In this case GtkTreeView (at least) assumes that the added
+				 * directory is empty and no expander widget is shown.
+				 *
+				 * The proper way to do this would be to recursively enumerate the new child.  No thanks.
+				 *
+				 * As a workaround, we can send a 'row_has_child_toggled' signal, even though the view has never
+				 * asked for 'has_child' for the new row.  Although at this point the model is now technically
+				 * inconsistent (since we didn't signal the insertion of those new children), GtkTreeView seems
+				 * to stumble along...
+				 */
+				if (dt.get_list_tracker ().has_child ()) {
+					model.row_has_child_toggled (path, iter);
 				}
 
-				return i;
+				path.up ();
+			}
+
+			if (more == false) {
+				cached_child = null;
+				create_child_array ();
 			}
+
+			check_has_child_toggled (was_empty, path);
 		}
 
-		internal void delete_all () {
+		internal void set_source (Model.List? source) {
 			if (length > 0) {
-				var path = get_tree_path ();
-				path.append_index (0);
-
-				array = null;
-				while (first != null) {
-					model.row_deleted (path);
-					first = first.next;
-					length--;
-				}
-
+				// Create a synthetic change event that deletes all items
+				source_changed (this.source, 0, length, 0, false);
 				assert (length == 0);
-				path.up ();
+			}
 
-				Gtk.TreeIter iter;
-				if (model.write_iter (out iter, parent)) {
-					model.row_has_child_toggled (path, iter);
+			if (this.source != null) {
+				this.source.disconnect (changed_id);
+			}
+
+			this.source = source;
+
+			if (source != null) {
+				if (length_exposed) {
+					source_changed (source, 0, 0, source.n_children (), false);
+				} else {
+					length = source.n_children ();
 				}
+
+				changed_id = source.changed.connect (source_changed);
 			}
 		}
 
 		~ListTracker () {
-			source.disconnect (changed_id);
+			if (source != null) {
+				source.disconnect (changed_id);
+			}
 		}
 	}
 
+	/* The DictionaryTracker is responsible for tracking a Model.Dictionary in a way that is convenient for a
+	 * GtkTreeModel implementation.  It attempts to be lazy, minimising work where possible and only sending
+	 * signals for things that an outside observer can know has changed.  For example, if an outside observer
+	 * has never requested the value of any key of the dictionary then the DictionaryTracker won't be watching
+	 * for changes in that value.
+	 *
+	 * To this end, there are three states that a DictionaryTracker can be in:
+	 *
+	 *   0 - Freshly created: this is the initial state.  The DictionaryTracker is essentially an empty shell at
+	 *                        this point.  It doesn't even contain a reference to the Model.Dictionary that it
+	 *                        represents.
+	 *
+	 *   1 - Value exposed:   this is the state that the DictionaryTracker enters after any requests have been
+	 *                        made against it requesting the value of a key in the dictionary (including the
+	 *                        "children" key).  In this state the DictionaryTracker holds reference to the
+	 *                        underlying Model.Reference for each value and watches for changes on them.
+	 *
+	 *   2 - List exposed:    this is the state that the DictionaryTracker enters after any request has been
+	 *                        made for the ListTracker for the children of the dictionary.
+	 *
+	 * The model is in the 'value exposed' state if the 'references' array is non-null.  The model is in the
+	 * 'list exposed' state if 'children' is non-null.
+	 *
+	 * The DictionaryTracker is deliberately antagonistic in that it will not ever hold a reference on the
+	 * Model.Dictionary that it is tracking -- only the Model.Reference for each value of the dictionary.  This
+	 * can expose bugs in model implementations, particularly with respect to change notifications.
+	 **/
 	class DictionaryTracker {
-		Model.Reference? children_reference;
-		internal ListTracker? children;
-		ulong children_handler;
+		// These are both null until some amount of data has been accessed
+		Model.Reference[]? references;
+		ulong[]? reference_handlers;
 
-		Model.Reference[] references;
-		ulong[] reference_handlers;
+		// This is null until the list tracker has been accessed.
+		ListTracker? children;
 
+		// Linkage...
 		internal weak ListTracker siblings;
-		internal int index;
-
 		internal DictionaryTracker? next;
 
-		void children_reference_changed (Model.Reference reference) {
-			var list = reference.get_value () as Model.List;
+		/* It is not valid to access this directly.  It is only set to the correct value in the case that
+		 * 'siblings.array' is non-null.  The code to check this and to calculate the value (by traversal) if
+		 * needed lives in ListTracker, so siblings.get_index_for_child() should be used.
+		 */
+		internal ulong index;
 
-			if (children != null) {
-				children.delete_all ();
-			}
-
-			if (list != null) {
-				children = new ListTracker (siblings.model, list, this);
-			} else {
-				children = null;
+		void reference_changed (Model.Reference reference) {
+			if (reference == references[0] && children != null) {
+				children.set_source (reference.get_value () as Model.List);
 			}
-		}
 
-		void reference_changed (Model.Reference reference) {
 			var path = get_tree_path ();
 			Gtk.TreeIter iter;
 
@@ -217,38 +399,47 @@ namespace Model {
 			siblings.model.row_changed (path, iter);
 		}
 
-		public DictionaryTracker (ListTracker siblings, Model.Dictionary source, DictionaryTracker? next) {
-			var model = siblings.model;
+		internal ListTracker get_list_tracker () {
+			if (children == null) {
+				children = new ListTracker (siblings.model, this, get_value (0) as Model.List);
+			}
 
-			this.siblings = siblings;
-			this.next = next;
+			return children;
+		}
 
-			if (model.children_key != null) {
-				children_reference = source.get_reference ((!) model.children_key);
-				children_handler = children_reference.changed.connect (children_reference_changed);
-				children_reference_changed ((!) children_reference);
+		internal Model.Object get_value (int index) {
+			if (references == null) {
+				setup_references ();
 			}
 
+			return references[index].get_value ();
+		}
+
+		void setup_references () {
+			var source = siblings.get_dictionary_for_child (this);
+			var model = siblings.model;
+
 			references = new Model.Reference[model.keys.length];
 			reference_handlers = new ulong[model.keys.length];
+
 			for (var i = 0; i < model.keys.length; i++) {
 				references[i] = source.get_reference (model.keys[i]);
 				reference_handlers[i] = references[i].changed.connect (reference_changed);
 			}
 		}
 
+		public DictionaryTracker (ListTracker siblings, DictionaryTracker? next) {
+			this.siblings = siblings;
+			this.next = next;
+		}
+
 		internal Gtk.TreePath get_tree_path () {
 			var path = siblings.get_tree_path ();
-			path.append_index (siblings.find (this));
+			path.append_index ((int) siblings.get_index_for_child (this));
 			return path;
 		}
 
-		internal Model.Object get_value (int index) {
-			return references[index].get_value ();
-		}
-
 		~DictionaryTracker () {
-			children_reference.disconnect (children_handler);
 			for (var i = 0; i < references.length; i++) {
 				references[i].disconnect (reference_handlers[i]);
 			}
@@ -256,8 +447,8 @@ namespace Model {
 	}
 
 	public class GtkModel : GLib.Object, Gtk.TreeModel {
-		internal string? children_key;
 		internal string[] keys;
+		bool list_only;
 		Type[] types;
 
 		ListTracker root;
@@ -272,15 +463,29 @@ namespace Model {
 
 			flags = Gtk.TreeModelFlags.ITERS_PERSIST;
 
-			if (this.children_key == null) {
+			if (list_only) {
 				flags |= Gtk.TreeModelFlags.LIST_ONLY;
 			}
 
 			return flags;
 		}
 
+		/* workaround for poor binding of Gtk.TreePath */
+		weak int[] get_tree_path_indices (Gtk.TreePath path) {
+			weak int[] indices = path.get_indices ();
+			indices.length = path.get_depth ();
+			return indices;
+		}
+
 		bool get_iter (out Gtk.TreeIter iter, Gtk.TreePath path) {
-			return write_iter (out iter, root.get_for_path (path));
+			var indices = get_tree_path_indices (path);
+			DictionaryTracker? dt;
+			dt = root.get_child_for_index (indices[0]);
+			for (var i = 1; dt != null && i < indices.length; i++) {
+				dt = dt.get_list_tracker ().get_child_for_index (indices[i]);
+			}
+			
+			return write_iter (out iter, dt);
 		}
 
 		int get_n_columns () {
@@ -288,11 +493,11 @@ namespace Model {
 		}
 
 		Gtk.TreePath get_path (Gtk.TreeIter iter) {
-			return iter_to_dt (iter).get_tree_path ();
+			return iter_to_dictionary_tracker (iter).get_tree_path ();
 		}
 
 		void get_value (Gtk.TreeIter iter, int column, out GLib.Value value) {
-			var dt = iter_to_dt (iter);
+			var dt = iter_to_dictionary_tracker (iter);
 			var object = dt.get_value (column);
 
 			value.init (types[column]);
@@ -320,16 +525,16 @@ namespace Model {
 			}
 		}
 
-		DictionaryTracker iter_to_dt (Gtk.TreeIter iter) {
+		DictionaryTracker iter_to_dictionary_tracker (Gtk.TreeIter iter) {
 			assert (iter.stamp == stamp);
 			assert (iter.user_data != null);
 
 			return (DictionaryTracker) iter.user_data;
 		}
 
-		ListTracker? iter_to_lt (Gtk.TreeIter? iter) {
+		ListTracker? iter_to_list_tracker (Gtk.TreeIter? iter) {
 			if (iter != null) {
-				return iter_to_dt (iter).children;
+				return iter_to_dictionary_tracker (iter).get_list_tracker ();
 			} else {
 				return root;
 			}
@@ -343,32 +548,32 @@ namespace Model {
 		}
 
 		bool iter_children (out Gtk.TreeIter iter, Gtk.TreeIter? parent) {
-			var lt = iter_to_lt (parent);
-			return write_iter (out iter, lt == null ? null : lt.first);
+			var lt = iter_to_list_tracker (parent);
+			return write_iter (out iter, lt == null ? null : lt.get_child_for_index (0));
 		}
 
 		bool iter_has_child (Gtk.TreeIter iter) {
-			var lt = iter_to_lt (iter);
-			return lt != null && lt.length > 0;
+			var lt = iter_to_list_tracker (iter);
+			return lt != null && lt.has_child ();
 		}
 
 		int iter_n_children (Gtk.TreeIter? iter) {
-			var lt = iter_to_lt (iter);
-			return lt == null ? 0 : lt.length;
+			var lt = iter_to_list_tracker (iter);
+			return lt == null ? 0 : (int) lt.n_children ();
 		}
 
 		bool iter_next (ref Gtk.TreeIter iter) {
-			var dt = iter_to_dt (iter);
+			var dt = iter_to_dictionary_tracker (iter);
 			return write_iter (out iter, dt.next);
 		}
 
 		bool iter_nth_child (out Gtk.TreeIter iter, Gtk.TreeIter? parent, int n) {
-			var lt = iter_to_lt (parent);
-			return write_iter (out iter, lt == null ? null : lt.get_nth (n));
+			var lt = iter_to_list_tracker (parent);
+			return write_iter (out iter, lt == null ? null : lt.get_child_for_index (n));
 		}
 
 		bool iter_parent (out Gtk.TreeIter iter, Gtk.TreeIter child) {
-			var dt = iter_to_dt (child);
+			var dt = iter_to_dictionary_tracker (child);
 			return write_iter (out iter, dt.siblings.parent);
 		}
 
@@ -378,12 +583,13 @@ namespace Model {
 		void unref_node (Gtk.TreeIter iter) {
 		}
 
-		public GtkModel (Model.List list, Type[] types, string[] keys, string? children_key) {
+		public GtkModel (Model.List list, Type[] types, string[] keys, bool list_only) {
 			this.types = types;
 			this.keys = keys;
-			this.children_key = children_key;
+			this.list_only = list_only;
+			this.stamp = 23423423;
 
-			this.root = new ListTracker (this, list);
+			this.root = new ListTracker (this, null, list);
 
 			assert (types.length == keys.length);
 		}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]