Hi everyone,

Thanks to Mike it's now possible (using gtk-sharp from svn trunk) to
implement custom TreeModels[1]. Thus I wrote a model that stores
objects and makes the object and a selected set of properties
available as columns in the model.

For example, to show a Person in a tree view, one could simply write:

ObjectStore model = new ObjectStore (typeof(Person), "Firstname", "Lastname");
model.Add(new Person("John", "Lance"));

TreeView view = new TreeView (new TreeModelAdapter (model));
view.AppendColumn ("First", new CellRendererText (), "text", 1); //
The first column is the object
view.AppendColumn ("Last", new CellRendererText (), "text", 2);
sw.Add (view);

Attached is the code and a sample program.
Comments? Ideas?

[1] Any GInterface actually: http://mono-project.com/ImplementingGInterfaces
// ObjectModel.cs - ObjectModel implementation and demo
// Based on the TreeModelDemo sample from gtk-sharp.
//
// Author: Eskil Bylund <[EMAIL PROTECTED]>
//
// Copyright (c) 2007 Eskil Bylund
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of version 2 of the Lesser GNU General 
// Public License as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the
// Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
// Boston, MA  02110-1301  USA.

namespace GtkSamples
{
	using System;
	using System.Collections.Generic;
	using System.Reflection;
	using System.Runtime.InteropServices;
	using Gtk;

	using System.IO; // For the demo

	public class TreeModelDemo : Gtk.Window
	{
		public TreeModelDemo () : base("TreeModel demo")
		{
			DefaultSize = new Gdk.Size (640,480);
			ScrolledWindow sw = new ScrolledWindow ();
			Add(sw);

			ObjectStore model = new ObjectStore (typeof(FileSystemInfo), "Name", "FullName");
			TreeModelAdapter adapter = new TreeModelAdapter (model);

			DirectoryInfo root = new DirectoryInfo ("/home");
			model.Add (root);
			foreach (FileSystemInfo info in root.GetFileSystemInfos ()) {
				model.Add (root, info);
			}

			TreeView view = new TreeView (adapter);
			view.HeadersVisible = true;
			view.AppendColumn ("Name", new CellRendererText (), "text", 1);
			view.AppendColumn ("Path", new CellRendererText (), "text", 2);
			sw.Add (view);

			sw.ShowAll ();
		}

		protected override bool OnDeleteEvent (Gdk.Event ev)
		{
			Application.Quit ();
			return true;
		}

		public static void Main (string[] args)
		{
			Application.Init ();
			Gtk.Window win = new TreeModelDemo ();	
			win.Show ();
			Application.Run ();
		}

	}

	// TODO: Provide a strongly typed model? What about storing objects of a
	// different type in the same model?

	// FIXME: The columns in the model are of the same type as the properties.
	// Would it be better to convert the properties that are objects to string
	// to make the model easy to use with eg. CellRendererText? Make if configurable?

	// FIXME: Rename the class to something else?

	/// <summary>
	/// A TreeModel that makes an object and the selected properties from it
	/// available as columns in the model.
	/// </summary>
	public class ObjectStore : GLib.Object, TreeModelImplementor
	{
		private TreeModelAdapter adapter;
		private Type objectType;
		private string[] properties;

		private Dictionary<object, GCHandle> iterCache;

		private Dictionary<object, List<object>> parents;
		private Dictionary<object, object> childToParent;
		private List<object> roots;
		private List<object> objects;

		public ObjectStore (Type type, params string[] properties)
		{
			if (type == null)
				throw new ArgumentNullException("type");

			foreach (string property in properties) {
				if (type.GetProperty (property) == null)
					throw new ArgumentException("One of the specified properties does not exist.");
			}

			this.objectType = type;
			this.properties = properties ?? new string[] {};

			iterCache = new Dictionary<object, GCHandle> ();
			parents = new Dictionary<object, List<object>> ();
			childToParent = new Dictionary<object, object> ();
			roots = new List<object> ();
			objects = new List<object> ();

			adapter = new TreeModelAdapter (this);
		}

		// Properties

		public TreeModelFlags Flags
		{
			get { return TreeModelFlags.ItersPersist; }
		}

		public int NColumns
		{
			get { return 1 + properties.Length; }
		}

		// Methods

		public TreeIter Add (object obj)
		{
			return Add (null, obj);
		}

		public TreeIter Add (object obj, int position)
		{
			return Add (null, obj, position);
		}

		public TreeIter Add (object parent, object obj)
		{
			return AddImpl (parent, obj, false, -1);
		}

		public TreeIter Add (object parent, object obj, int position)
		{
			return AddImpl (parent, obj, true, position);
		}

		private TreeIter AddImpl (object parent, object obj, bool insert, int position)
		{
			if (obj == null)
				throw new ArgumentNullException ("obj");
			if (obj.GetType () != objectType && !obj.GetType ().IsSubclassOf (objectType))
				throw new ArgumentException ("Invalid type of object.");
			if (parent != null && !objects.Contains (parent))
				throw new ArgumentException ("Unknown parent.");
			if (objects.Contains (obj))
				throw new ArgumentException ("Object already in model.");

			bool emitToggled = false;

			if (parent != null) {
				List<object> children;
				if (!parents.TryGetValue (parent, out children)) {
					children = new List<object> ();
					parents [parent] = children;
					emitToggled = true;
				}
				AddOrInsert(children, obj, insert, position);
				childToParent.Add (obj, parent);
			} else
				AddOrInsert(roots, obj, insert, position);

			objects.Add (obj);

			TreeIter iter = IterFromNode (obj);
			adapter.EmitRowInserted (GetPath (iter), iter);
			if (emitToggled) {
				TreeIter i = IterFromNode (parent);
				adapter.EmitRowHasChildToggled (GetPath(i), i);
			}
			return iter;
		}

		public bool Contains (object obj)
		{
			return objects.Contains (obj);
		}

		public TreeIter GetIter (object obj)
		{
			if (!objects.Contains(obj))
				throw new ArgumentException ("Object not in model.");

			return IterFromNode (obj);
		}

		public void Remove (object obj)
		{
			if (!objects.Contains (obj))
				throw new ArgumentException("Object not in model");

			TreePath path = PathFromNode (obj);
			object parentToToggle = null;

			List<object> children;
			if (parents.TryGetValue (obj, out children)) {
				foreach (object child in children.ToArray ())
					Remove (child);
				parents.Remove (obj);
			}

			object parent;
			if (childToParent.TryGetValue (obj, out parent)) {
				parents [parent].Remove (obj);
				childToParent.Remove (obj);
				if (parents [parent].Count == 0) {
					parents.Remove (parent);
					parentToToggle = parent;
				}
			} else
				roots.Remove (obj);

			objects.Remove (obj);
			iterCache.Remove (obj);

			adapter.EmitRowDeleted (path);
			if (parentToToggle != null) {
				TreeIter iter = IterFromNode (parentToToggle);
				adapter.EmitRowHasChildToggled (GetPath(iter), iter);
			}
		}

		public GLib.GType GetColumnType (int col)
		{
			if (col == 0)
				return (GLib.GType)objectType;
			else {
				PropertyInfo pinfo = objectType.GetProperty (properties [col - 1]);
				return (GLib.GType)pinfo.PropertyType;
			}
		}

		public bool GetIter (out TreeIter iter, TreePath path)
		{
			if (path == null)
				throw new ArgumentNullException ("path");

			iter = TreeIter.Zero;

			object node = GetNodeAtPath (path);
			if (node == null)
				return false;

			iter = IterFromNode (node);
			return true;
		}

		public TreePath GetPath (TreeIter iter)
		{
			object node = NodeFromIter (iter);
			if (node == null)
				throw new ArgumentException ("iter");

			return PathFromNode (node);
		}

		public void GetValue (TreeIter iter, int col, ref GLib.Value val)
		{
			object node = NodeFromIter (iter);
			if (node == null || col > properties.Length)
				return;

			if (col == 0)
				val = new GLib.Value (node);
			else {
				PropertyInfo pinfo = objectType.GetProperty (properties [col - 1]);
				object pval = pinfo.GetValue (node, null);
				if (pval != null)
					val = new GLib.Value (pval);
				else
					val = new GLib.Value ((GLib.GType)pinfo.PropertyType);
			}
		}

		public bool IterNext (ref TreeIter iter)
		{
			object node = NodeFromIter (iter);
			if (node == null)
				return false;

			List<object> currentLevel;
			if (childToParent.ContainsKey (node))
				currentLevel = parents [childToParent [node]];
			else
				currentLevel = roots;

			int index = currentLevel.IndexOf (node);
			if (index + 1 < currentLevel.Count) {
				iter = IterFromNode (currentLevel [index + 1]);
				return true;
			}
			return false;
		}

		public bool IterChildren (out TreeIter child, TreeIter parent)
		{
			child = TreeIter.Zero;

			object node = NodeFromIter (parent);
			if (node == null)
				return false;

			List<object> children;
			if (parent.UserData == IntPtr.Zero && roots.Count > 0) {
				child = IterFromNode (objects [0]);
				return true;
			} else if (parents.TryGetValue (node, out children)) {
				child = IterFromNode (children [0]);
				return true;
			}
			return false;
		}

		public bool IterHasChild (TreeIter iter)
		{
			object node = NodeFromIter (iter);
			if (node != null)
				return parents.ContainsKey (node);
			else if (iter.UserData == IntPtr.Zero)
				return true;
			return false;
		}

		public int IterNChildren (TreeIter iter)
		{
			if (iter.UserData == IntPtr.Zero)
				return roots.Count;
			else {
				object node = NodeFromIter (iter);
				if (node != null && parents.ContainsKey (node))
					return parents [node].Count;
			}
			return 0;
		}

		public bool IterNthChild (out TreeIter child, TreeIter parent, int n)
		{
			child = TreeIter.Zero;

			if (parent.UserData == IntPtr.Zero) {
				if (roots.Count <= n)
					return false;
				child = IterFromNode (roots [n]);
				return true;
			} else {
				object node = NodeFromIter (parent);

				List<object> children;
				if (node != null && parents.TryGetValue (parent, out children) && n < children.Count) {
					child = IterFromNode (children [n]);
					return true;
				}
			}
			return false;
		}

		public bool IterParent (out TreeIter parent, TreeIter child)
		{
			parent = TreeIter.Zero;

			object node = NodeFromIter (child);
			if (node != null && childToParent.ContainsKey (node)) {
				parent = IterFromNode (childToParent [node]);
				return true;
			}
			return false;
		}

		public void RefNode (TreeIter iter)
		{
		}

		public void UnrefNode (TreeIter iter)
		{
		}

		private object GetNodeAtPath (TreePath path)
		{
			if (path.Indices.Length > 0) {
				int index = path.Indices [0];
				List<object> currentLevel = roots;
				int level = 0;

				object node = null;
				do {
					if (index >= currentLevel.Count)
						return null;

					node = currentLevel [index];
					if (level + 1 < path.Indices.Length) {
						if (parents.ContainsKey (node)) {
							currentLevel = parents [node];
							level++;
						}
						else
							break;
					} else
						break;
				} while (true);

				return node;
			}
			return null;
		}

		private TreeIter IterFromNode (object node)
		{
			GCHandle gch;
			if (!iterCache.TryGetValue (node, out gch)) {
				gch = GCHandle.Alloc (node);
				iterCache [node] = gch;
			}

			TreeIter result = TreeIter.Zero;
			result.UserData = (IntPtr)gch;
			return result;
		}

		private object NodeFromIter (TreeIter iter)
		{
			GCHandle gch = (GCHandle)iter.UserData;
			return gch.Target;
		}

		private TreePath PathFromNode (object node)
		{
			if (node == null)
				return new TreePath ();

			TreePath path = new TreePath ();

			object parent;
			if (childToParent.TryGetValue (node, out parent)) {
				do {
					path.PrependIndex (parents [parent].IndexOf (node));
					node = parent;
				} while (childToParent.TryGetValue (node, out parent));
			}
			path.PrependIndex (roots.IndexOf (node));
			return path;
		}

		private static void AddOrInsert(List<object> list, object obj,
			bool insert, int position)
		{
			if (insert)
				list.Insert(position, obj);
			else
				list.Add (obj);
		}
	}
}
_______________________________________________
Gtk-sharp-list maillist  -  [email protected]
http://lists.ximian.com/mailman/listinfo/gtk-sharp-list

Reply via email to