// NodeStore.cs - Tree store implementation for TreeView. // // Author: Mike Kestner // // Copyright (c) 2003-2005 Novell, Inc. // Copyright (c) 2009 Christian Hoff // // 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., 59 Temple Place - Suite 330, // Boston, MA 02111-1307, USA. namespace Gtk { using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; public class NodeStore : GLib.Object, IEnumerable { NodeStoreImplementor implementor; public NodeStore (Type node_type) { implementor = new NodeStoreImplementor (node_type); } internal TreeModelAdapter Adapter { get { return new TreeModelAdapter (implementor); } } internal TreeIter GetIter (ITreeNode node) { return implementor.GetIter (node); } internal TreePath GetPath (ITreeNode node) { return implementor.GetPath (node); } public ITreeNode GetNode (TreePath path) { return implementor.GetNode (path); } public void AddNode (ITreeNode node) { implementor.AddNode (node); } public void AddNode (ITreeNode node, int position) { implementor.AddNode (node, position); } public void RemoveNode (ITreeNode node) { implementor.RemoveNode (node); } public void Clear () { implementor.Clear (); } public IEnumerator GetEnumerator () { return implementor.GetEnumerator (); } internal class NodeStoreImplementor : GLib.Object, ITreeModelImplementor, IEnumerable { TreeModelAdapter model_adapter; GLib.GType[] ctypes; MemberInfo [] getters; int n_cols; bool list_only = false; ArrayList nodes = new ArrayList (); public readonly int Stamp; public NodeStoreImplementor (Type node_type) { // Create a random stamp for the iters Random RandomStampGen = new Random (); this.Stamp = RandomStampGen.Next (int.MinValue, int.MaxValue); ScanType (node_type); model_adapter = new Gtk.TreeModelAdapter (this); } void ScanType (Type type) { TreeNodeAttribute tna = (TreeNodeAttribute) Attribute.GetCustomAttribute (type, typeof (TreeNodeAttribute), false); if (tna != null) list_only = tna.ListOnly; var minfos = new List (); foreach (PropertyInfo pi in type.GetProperties ()) foreach (TreeNodeValueAttribute attr in pi.GetCustomAttributes (typeof (TreeNodeValueAttribute), false)) minfos.Add (pi); foreach (FieldInfo fi in type.GetFields ()) foreach (TreeNodeValueAttribute attr in fi.GetCustomAttributes (typeof (TreeNodeValueAttribute), false)) minfos.Add (fi); n_cols = minfos.Count; ctypes = new GLib.GType [n_cols]; getters = new MemberInfo [n_cols]; foreach (MemberInfo mi in minfos) { foreach (TreeNodeValueAttribute attr in mi.GetCustomAttributes (typeof (TreeNodeValueAttribute), false)) { int col = attr.Column; if (getters [col] != null) throw new Exception (String.Format ("You have two TreeNodeValueAttributes with the Column={0}", col)); getters [col] = mi; Type t = mi is PropertyInfo ? ((PropertyInfo) mi).PropertyType : ((FieldInfo) mi).FieldType; ctypes [col] = (GLib.GType) t; } } } public TreeModelFlags Flags { get { TreeModelFlags result = TreeModelFlags.ItersPersist; if (list_only) result |= TreeModelFlags.ListOnly; return result; } } public int NColumns { get { return n_cols; } } public GLib.GType GetColumnType (int col) { return ctypes [col]; } #region Gtk.TreePath handling internal TreePath GetPath (ITreeNode node) { TreePath path = new TreePath (); int idx; while (node.Parent != null) { idx = node.Parent.IndexOf (node); if (idx < 0) throw new Exception ("Badly formed tree"); path.PrependIndex (idx); node = node.Parent; } idx = Nodes.IndexOf (node); if (idx < 0) throw new Exception ("Node not found in Nodes list"); path.PrependIndex (idx); path.Owned = false; return path; } public ITreeNode GetNode (TreePath path) { if (path == null) throw new ArgumentNullException (); int[] indices = path.Indices; if (indices[0] >= Nodes.Count) return null; ITreeNode node = Nodes [indices [0]] as ITreeNode; int i; for (i = 1; i < path.Depth; i++) { if (indices [i] >= node.ChildCount) return null; node = node [indices [i]]; } return node; } #endregion #region Gtk.TreeIter handling IList gc_handles = new List (); protected override void Dispose (bool disposing) { // Free all the GCHandles pointing to the iters since they won't be garbage collected foreach (GCHandle handle in gc_handles) handle.Free (); base.Dispose (disposing); } internal void GetIter (ITreeNode node, ref TreeIter iter) { if (node == null) throw new ArgumentNullException ("node"); iter.Stamp = this.Stamp; GCHandle gch = GCHandle.Alloc (node); iter.UserData = (IntPtr) gch; gc_handles.Add (gch); } public TreeIter GetIter (ITreeNode node) { Gtk.TreeIter result = Gtk.TreeIter.Zero; GetIter (node, ref result); return result; } public ITreeNode GetNode (TreeIter iter) { if (iter.Stamp != this.Stamp) throw new InvalidOperationException (String.Format ("iter belongs to a different model; it's stamp is not equal to the stamp of this model({0})", this.Stamp.ToString ())); System.Runtime.InteropServices.GCHandle gch = (System.Runtime.InteropServices.GCHandle) iter.UserData; return gch.Target as ITreeNode; } void ITreeModelImplementor.RefNode (Gtk.TreeIter iter) { } void ITreeModelImplementor.UnrefNode (Gtk.TreeIter iter) { } #endregion public bool GetIter (out TreeIter iter, TreePath path) { if (path == null) throw new ArgumentNullException ("path"); ITreeNode node = GetNode (path); if (node == null) { iter = TreeIter.Zero; return false; } else { iter = GetIter (node); return true; } } public Gtk.TreePath GetPath (TreeIter iter) { return GetPath (GetNode (iter)); } public void GetValue (Gtk.TreeIter iter, int col, ref GLib.Value val) { ITreeNode node = GetNode (iter); val.Init (ctypes [col]); object col_val; if (getters [col] is PropertyInfo) col_val = ((PropertyInfo) getters [col]).GetValue (node, null); else col_val = ((FieldInfo) getters [col]).GetValue (node); val.Val = col_val; } public bool IterNext (ref TreeIter iter) { ITreeNode node = GetNode (iter); int idx; if (node.Parent == null) idx = Nodes.IndexOf (node); else idx = node.Parent.IndexOf (node); if (idx < 0) throw new Exception ("Node not found in Nodes list"); if (node.Parent == null) { if (++idx >= Nodes.Count) return false; node = Nodes [idx] as ITreeNode; } else { if (++idx >= node.Parent.ChildCount) return false; node = node.Parent [idx]; } GetIter (node, ref iter); return true; } public bool IterPrevious (ref TreeIter iter) { ITreeNode node = GetNode (iter); int idx; if (node.Parent == null) idx = Nodes.IndexOf (node); else idx = node.Parent.IndexOf (node); if (idx < 0) throw new Exception ("Node not found in Nodes list"); else if (idx == 0) return false; node = node.Parent == null ? Nodes [idx - 1] as ITreeNode : node.Parent [idx - 1]; GetIter (node, ref iter); return true; } public bool IterChildren (out Gtk.TreeIter first_child, Gtk.TreeIter parent) { first_child = Gtk.TreeIter.Zero; if (parent.Equals (TreeIter.Zero)) { if (Nodes.Count <= 0) return false; first_child = GetIter (Nodes [0] as ITreeNode); } else { ITreeNode node = GetNode (parent); if (node.ChildCount <= 0) return false; first_child = GetIter (node [0]); } return true; } public bool IterHasChild (Gtk.TreeIter iter) { return IterNChildren (iter) > 0; } public int IterNChildren (Gtk.TreeIter iter) { if (iter.Equals (TreeIter.Zero)) return Nodes.Count; else return GetNode (iter).ChildCount; } public bool IterNthChild (out Gtk.TreeIter child, Gtk.TreeIter parent, int n) { child = TreeIter.Zero; if (parent.Equals (TreeIter.Zero)) { if (Nodes.Count <= n) return false; child = GetIter (Nodes [n] as ITreeNode); } else { ITreeNode parent_node = GetNode (parent); if (parent_node.ChildCount <= n) return false; child = GetIter (parent_node [n]); } return true; } public bool IterParent (out Gtk.TreeIter parent, Gtk.TreeIter child) { parent = TreeIter.Zero; ITreeNode child_node = GetNode (child); if (child_node.Parent == null) return false; else { parent = GetIter (child_node.Parent); return true; } } private IList Nodes { get { return nodes as IList; } } private void changed_cb (object o, EventArgs args) { ITreeNode node = o as ITreeNode; model_adapter.EmitRowChanged (GetPath (node), GetIter (node)); } private void EmitRowInserted (ITreeNode node) { model_adapter.EmitRowInserted (GetPath (node), GetIter (node)); for (int i = 0; i < node.ChildCount; i++) EmitRowInserted (node [i]); } private void child_added_cb (object sender, ITreeNode child) { AddNodeInternal (child); EmitRowInserted (child); } private void child_deleted_cb (object sender, ITreeNode child, int idx) { ITreeNode node = sender as ITreeNode; TreePath path = GetPath (node); TreePath child_path = path.Copy (); child_path.AppendIndex (idx); model_adapter.EmitRowDeleted (child_path); if (node.ChildCount <= 0) model_adapter.EmitRowHasChildToggled (GetPath (node), GetIter (node)); } private void AddNodeInternal (ITreeNode node) { node.Changed += new EventHandler (changed_cb); node.ChildAdded += new TreeNodeAddedHandler (child_added_cb); node.ChildRemoved += new TreeNodeRemovedHandler (child_deleted_cb); for (int i = 0; i < node.ChildCount; i++) AddNodeInternal (node [i]); } public void AddNode (ITreeNode node) { nodes.Add (node); AddNodeInternal (node); EmitRowInserted (node); } public void AddNode (ITreeNode node, int position) { nodes.Insert (position, node); AddNodeInternal (node); EmitRowInserted (node); } public void RemoveNode (ITreeNode node) { int idx = nodes.IndexOf (node); if (idx < 0) return; nodes.Remove (node); TreePath path = new TreePath (); path.AppendIndex (idx); model_adapter.EmitRowDeleted (path); } public void Clear () { while (nodes.Count > 0) RemoveNode ((ITreeNode)nodes [0]); } public IEnumerator GetEnumerator () { return nodes.GetEnumerator (); } } } }