[model] make the GtkTreeModel lazy
- From: Ryan Lortie <ryanl src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [model] make the GtkTreeModel lazy
- Date: Fri, 12 Mar 2010 07:43:03 +0000 (UTC)
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]