[model] Add work-in-progress GtkTreeModel implementation



commit f4b5b329e0265193234ac5ace3f32289e5495e35
Author: Ryan Lortie <desrt desrt ca>
Date:   Thu Mar 11 01:34:51 2010 -0500

    Add work-in-progress GtkTreeModel implementation

 Makefile.am         |    2 +-
 configure.ac        |    4 +
 gtk/.gitignore      |    3 +
 gtk/Makefile.am     |    8 ++
 gtk/fs.vala         |  173 ++++++++++++++++++++++++++++
 gtk/mg.vala         |   24 ++++
 gtk/model-gtk.pc.in |   11 ++
 gtk/model-gtk.vala  |  319 +++++++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 543 insertions(+), 1 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 93547f5..ddccabc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,2 +1,2 @@
 DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc
-SUBDIRS = model docs
+SUBDIRS = model gtk docs
diff --git a/configure.ac b/configure.ac
index 12ca73b..47f4c0f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,11 +2,13 @@ AC_INIT(model, 0.2.0.1)
 AM_INIT_AUTOMAKE
 AM_SILENT_RULES([yes])
 AC_PROG_LIBTOOL
+AM_PROG_VALAC
 GTK_DOC_CHECK
 AC_PROG_CC
 
 PKG_CHECK_MODULES(introspection, gobject-introspection-1.0 >= 0.6.3)
 PKG_CHECK_MODULES(gobject, gobject-2.0)
+PKG_CHECK_MODULES(gtk, gtk+-2.0)
 
 AC_SUBST(girdir, ${datadir}/gir-1.0)
 AC_SUBST(typelibdir, ${libdir}/girepository-1.0)
@@ -17,6 +19,8 @@ AC_SUBST(vapidir, ${datadir}/vala/vapi)
 AC_OUTPUT([
   model/Makefile
   model/model.pc
+  gtk/Makefile
+  gtk/model-gtk.pc
   docs/reference/model-docs.xml
   docs/reference/Makefile
   docs/Makefile
diff --git a/gtk/.gitignore b/gtk/.gitignore
new file mode 100644
index 0000000..7ce0e80
--- /dev/null
+++ b/gtk/.gitignore
@@ -0,0 +1,3 @@
+*.stamp
+*.c
+model-gtk
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
new file mode 100644
index 0000000..9c6fba4
--- /dev/null
+++ b/gtk/Makefile.am
@@ -0,0 +1,8 @@
+bin_PROGRAMS = model-gtk
+
+AM_VALAFLAGS = --vapidir ../model --pkg model --pkg gtk+-2.0 --pkg=posix
+AM_CFLAGS = $(gtk_CFLAGS) -I../model -Wno-bad-function-cast
+model_gtk_LDADD = $(gtk_LIBS) ../model/libmodel.la
+
+model_gtk_SOURCES = model-gtk.vala fs.vala mg.vala
+pkgconfig_DATA = model-gtk.pc
diff --git a/gtk/fs.vala b/gtk/fs.vala
new file mode 100644
index 0000000..23ab72e
--- /dev/null
+++ b/gtk/fs.vala
@@ -0,0 +1,173 @@
+namespace FS {
+  public class File : Model.SimpleDictionary {
+    internal string name;
+    FileMonitor? monitor;
+    GLib.File file;
+    ulong watch_id;
+
+    static void monitor_changed (FileMonitor monitor, GLib.File file,
+                                 GLib.File? other_file,
+                                 FileMonitorEvent event, File this_file) {
+      assert (this_file.monitor == monitor);
+
+      if ((event == FileMonitorEvent.CHANGED ||
+           event == FileMonitorEvent.ATTRIBUTE_CHANGED) &&
+          this_file.file.equal (file)) {
+        this_file.update_info.begin ();
+      }
+    }
+
+    async void update_info () {
+      try {
+        var info = yield file.query_info_async ("standard::size," +
+                                                "standard::content-type," +
+                                                "owner::user",
+                                                0, 0, null);
+
+        set_integer ("size", (int) info.get_size (), true);
+        set_string ("type", info.get_content_type (), true);
+        set_string ("user", info.get_attribute_string ("owner::user"), true);
+
+        if (info.get_content_type () == "inode/directory") {
+          set ("contents", new Directory (file), true);
+        } else {
+          set ("contents", null, true);
+        }
+      } catch (Error e) {
+        /* file probably just disappeared in the meantime.  do nothing */
+      }
+    }
+
+    ~File () {
+      if (watch_id != 0) {
+        SignalHandler.disconnect (monitor, watch_id);
+      }
+    }
+
+    public File (GLib.File file, FileMonitor? monitor) {
+      this.file = file;
+      this.name = (!) file.get_basename ();
+      this.monitor = monitor;
+
+      set_string ("name", name, false);
+      set ("contents", null);
+      set ("size", null);
+      set ("type", null);
+      set ("user", null);
+
+      update_info.begin ();
+
+      if (monitor != null) {
+        this.watch_id = Signal.connect (monitor, "changed",
+                                        (Callback) monitor_changed, this);
+      }
+    }
+  }
+
+  public class Directory : Model.AbstractSortedList
+  {
+    FileMonitor? monitor;
+    ulong watch_id;
+    GLib.File file;
+
+    ~Directory () {
+      if (watch_id != 0) {
+        SignalHandler.disconnect (monitor, watch_id);
+      }
+    }
+
+    override void free_key (Model.AbstractSortedList.Key key) {
+    }
+
+    override int compare (Model.AbstractSortedList.ConstKey a,
+                          Model.AbstractSortedList.ConstKey b) {
+      return strcmp ((string) a, (string) b);
+    }
+
+    override void create_item (ulong index,
+                               Model.AbstractSortedList.ConstKey key,
+                               out Model.AbstractSortedList.Key new_key,
+                               out Model.Object new_object) {
+      var child = new File (file.get_child ((string) key), monitor);
+      weak string name = child.name;
+      new_object = child;
+      new_key = (Model.AbstractSortedList.Key) name;
+    }
+
+    override void warning (ulong index, Model.AbstractSortedList.ConstKey key,
+                           char mode, ulong current_index,
+                           Model.Object current_value) {
+      assert_not_reached ();
+    }
+
+    static int compare_strings (void *a, void *b) {
+      string **a_str = a;
+      string **b_str = b;
+
+      return strcmp (*a_str, *b_str);
+    }
+
+    async void enumerate () {
+      string[] names = null;
+
+      try {
+        var enumerator =
+          yield file.enumerate_children_async ("standard::name", 0, 0, null);
+
+        do {
+          var files = yield enumerator.next_files_async (5, 0, null);
+          names = new string[0];
+
+          foreach (var file in files) {
+            names += file.get_name ();
+          }
+
+          Posix.qsort ((void *) names, names.length, sizeof (void *),
+                       (Posix.compar_fn_t) compare_strings);
+
+          merge ("i", (Model.AbstractSortedList.ConstKey[]) names);
+        } while (names.length == 5);
+      } catch (Error e) {
+      }
+    }
+
+    static void monitor_changed (FileMonitor monitor, GLib.File file,
+                                 GLib.File? other_file,
+                                 FileMonitorEvent event, Directory this_dir) {
+      string mode = "";
+
+      assert (this_dir.monitor == monitor);
+
+      switch (event) {
+        case FileMonitorEvent.CREATED:
+          mode = "i";
+          break;
+        case FileMonitorEvent.DELETED:
+          mode = "d";
+          break;
+        default:
+          return;
+      }
+
+      string[] names = new string[] { file.get_basename () };
+      this_dir.merge (mode, (Model.AbstractSortedList.ConstKey[]) names);
+    }
+
+    public Directory (GLib.File file) {
+      Object ();
+
+      this.file = file;
+
+      try {
+        this.monitor = file.monitor_directory (0, null);
+        this.watch_id = Signal.connect (monitor, "changed",
+                                        (Callback) monitor_changed, this);
+      } catch (GLib.IOError e) {
+        this.monitor = null;
+        this.watch_id = 0;
+      }
+
+      enumerate.begin ();
+    }
+  }
+}
diff --git a/gtk/mg.vala b/gtk/mg.vala
new file mode 100644
index 0000000..3262cdc
--- /dev/null
+++ b/gtk/mg.vala
@@ -0,0 +1,24 @@
+Gtk.TreeView v;
+Model.List list;
+
+bool setup () {
+  v.set_model (new Model.GtkModel (list, {typeof(string),typeof(string)}, {"name", "type"}, "contents"));
+  return false;
+}
+
+void main (string[]args) {
+  Gtk.init (ref args);
+
+  var w = new Gtk.Window (Gtk.WindowType.TOPLEVEL);
+  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);
+  Timeout.add (1000, setup);
+  var sc = new Gtk.ScrolledWindow (null, null);
+  sc.add (v);
+  w.add (sc);
+  w.show_all ();
+
+  Gtk.main ();
+}
diff --git a/gtk/model-gtk.pc.in b/gtk/model-gtk.pc.in
new file mode 100644
index 0000000..fb91d93
--- /dev/null
+++ b/gtk/model-gtk.pc.in
@@ -0,0 +1,11 @@
+prefix= prefix@
+exec_prefix= prefix@/bin
+libdir= prefix@/lib
+includedir= prefix@/include/model
+
+Name: model
+Description: a simple data model object
+Version: @PACKAGE_VERSION@
+Requires: gobject-2.0
+Libs: -L${libdir} -lmodel
+Cflags: -I${includedir}
diff --git a/gtk/model-gtk.vala b/gtk/model-gtk.vala
new file mode 100644
index 0000000..3377418
--- /dev/null
+++ b/gtk/model-gtk.vala
@@ -0,0 +1,319 @@
+namespace Model {
+	class ListTracker {
+		internal DictionaryTracker? first;
+		//DictionaryTracker[]? array;
+		internal int length;
+		ulong changed_id;
+
+		internal weak Model.DictionaryTracker? parent;
+		internal weak GtkModel model;
+		Model.List source;
+
+		DictionaryTracker **cached_child;
+		ulong cached_child_index;
+
+		void source_changed (Model.List source, ulong position, ulong deleted, ulong inserted, bool more) {
+			Gtk.TreePath path;
+			bool was_empty;
+
+			was_empty = length == 0;
+
+			if (parent != null) {
+				path = parent.get_tree_path ();
+			} else {
+				path = new Gtk.TreePath ();
+			}
+
+			/* 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;
+				}
+			}
+
+			path.append_index ((int) position);
+
+			while (deleted --> 0) {
+				model.row_deleted (path);
+
+				var tmp = *cached_child;
+				*cached_child = (owned) ((DictionaryTracker) tmp).next;
+				delete tmp;
+				length--;
+			}
+
+			path.up ();
+
+			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 (model.write_iter (out iter, *cached_child)) {
+					model.row_inserted (path, iter);
+				}
+
+				path.up ();
+				length++;
+			}
+
+			if (was_empty != (length == 0)) {
+				Gtk.TreeIter iter;
+
+				if (model.write_iter (out iter, parent)) {
+					model.row_has_child_toggled (path, iter);
+				}
+			}
+
+			if (more == false) {
+				cached_child = null;
+			}
+		}
+
+		internal ListTracker (GtkModel model, Model.List source, DictionaryTracker? parent = null) {
+			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);
+			}
+
+			changed_id = source.changed.connect (source_changed);
+		}
+
+		internal DictionaryTracker? get_nth (int n) {
+			var dt = first;
+
+			while (dt != null && n --> 0) {
+				dt = dt.next;
+			}
+
+			return dt;
+		}
+
+		internal DictionaryTracker? get_for_path (Gtk.TreePath path, int depth = 0) {
+			var item = get_nth (path.get_indices ()[depth++]);
+
+			if (item != null && depth < path.get_depth ()) {
+				if (item.children != null) {
+					item = item.children.get_for_path (path, depth);
+				} else {
+					item = null;
+				}
+			}
+
+			return item;
+		}
+
+		~ListTracker () {
+			source.disconnect (changed_id);
+		}
+	}
+
+	class DictionaryTracker {
+		Model.Reference? children_reference;
+		internal ListTracker? children;
+
+		internal weak ListTracker siblings;
+		internal Model.Dictionary source;
+		weak GtkModel model;
+
+		internal DictionaryTracker? next;
+
+		void children_reference_changed (Model.Reference reference) {
+			var list = reference.get_value () as Model.List;
+
+			if (list != null) {
+				children = new ListTracker (model, list, this);
+			} else {
+				children = null;
+			}
+		}
+
+		public DictionaryTracker (ListTracker siblings, Model.Dictionary source, DictionaryTracker? next) {
+			this.model = siblings.model;
+			this.siblings = siblings;
+			this.source = source;
+			this.next = next;
+
+			if (model.children_key != null) {
+				children_reference = source.get_reference ((!) model.children_key);
+				children_reference.changed += children_reference_changed;
+				children_reference_changed ((!) children_reference);
+			}
+		}
+
+		internal int get_index () {
+			var chain = siblings.first;
+			var i = 0;
+
+			while (chain != this) {
+				chain = chain.next;
+				i++;
+			}
+
+			return i;
+		}
+
+		internal Gtk.TreePath get_tree_path () {
+			Gtk.TreePath path;
+
+			if (siblings.parent != null) {
+				path = siblings.parent.get_tree_path ();
+			} else {
+				path = new Gtk.TreePath ();
+			}
+
+			path.append_index (get_index ());
+
+			return path;
+		}
+	}
+
+	public class GtkModel : GLib.Object, Gtk.TreeModel {
+		internal string? children_key;
+		string[] keys;
+		Type[] types;
+
+		ListTracker root;
+		internal int stamp;
+
+		GLib.Type get_column_type (int index) {
+			return types[index];
+		}
+
+		Gtk.TreeModelFlags get_flags () {
+			Gtk.TreeModelFlags flags;
+
+			flags = Gtk.TreeModelFlags.ITERS_PERSIST;
+
+			if (this.children_key == null) {
+				flags |= Gtk.TreeModelFlags.LIST_ONLY;
+			}
+
+			return flags;
+		}
+
+		bool get_iter (out Gtk.TreeIter iter, Gtk.TreePath path) {
+			return write_iter (out iter, root.get_for_path (path));
+		}
+
+		int get_n_columns () {
+			return keys.length;
+		}
+
+		Gtk.TreePath get_path (Gtk.TreeIter iter) {
+			return iter_to_dt (iter).get_tree_path ();
+		}
+
+		void get_value (Gtk.TreeIter iter, int column, out GLib.Value value) {
+			var dt = iter_to_dt (iter);
+			var object = dt.source.get_value (keys[column]);
+
+			value.init (types[column]);
+
+			if (types[column] == typeof (string)) {
+				var string_object = object as Model.String;
+				if (string_object != null) {
+					value.set_string (string_object.get ());
+				}
+			} else if (types[column] == typeof (int)) {
+				var integer_object = object as Model.Integer;
+				if (integer_object != null) {
+					value.set_int (integer_object.get ());
+				}
+			} else if (types[column] == typeof (double)) {
+				var float_object = object as Model.Float;
+				if (float_object != null) {
+					value.set_double (float_object.get ());
+				}
+			} if (types[column] == typeof (bool)) {
+				var boolean_object = object as Model.Boolean;
+				if (boolean_object != null) {
+					value.set_boolean (boolean_object.get ());
+				}
+			}
+		}
+
+		DictionaryTracker iter_to_dt (Gtk.TreeIter iter) {
+			assert (iter.stamp == stamp);
+			assert (iter.user_data != null);
+
+			return (DictionaryTracker) iter.user_data;
+		}
+
+		ListTracker? iter_to_lt (Gtk.TreeIter? iter) {
+			if (iter != null) {
+				return iter_to_dt (iter).children;
+			} else {
+				return root;
+			}
+		}
+
+		internal bool write_iter (out Gtk.TreeIter iter, DictionaryTracker? dt) {
+			iter.stamp = (dt != null) ? stamp : 0;
+			iter.user_data = (void *) dt;
+
+			return dt != null;
+		}
+
+		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);
+		}
+
+		bool iter_has_child (Gtk.TreeIter iter) {
+			var lt = iter_to_lt (iter);
+			return lt != null && lt.length > 0;
+		}
+
+		int iter_n_children (Gtk.TreeIter? iter) {
+			var lt = iter_to_lt (iter);
+			return lt == null ? 0 : lt.length;
+		}
+
+		bool iter_next (ref Gtk.TreeIter iter) {
+			var dt = iter_to_dt (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));
+		}
+
+		bool iter_parent (out Gtk.TreeIter iter, Gtk.TreeIter child) {
+			var dt = iter_to_dt (child);
+			return write_iter (out iter, dt.siblings.parent);
+		}
+
+		void ref_node (Gtk.TreeIter iter) {
+		}
+
+		void unref_node (Gtk.TreeIter iter) {
+		}
+
+		public GtkModel (Model.List list, Type[] types, string[] keys, string? children_key) {
+			this.types = types;
+			this.keys = keys;
+			this.children_key = children_key;
+
+			this.root = new ListTracker (this, list);
+
+			assert (types.length == keys.length);
+		}
+	}
+}
+
+// vim:ts=8 sw=8 noet:



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