context: //
  // PropertyGridTree.cs
  //
  // Author:
  //   Lluis Sanchez Gual
  //
  // Copyright (C) 2007 Novell, Inc (http://www.novell.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  // 
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  using System;
  using System.Collections;
  using System.ComponentModel;
  
  using Gtk;
  using Gdk;
  using MonoDevelop.Components.PropertyGrid.PropertyEditors;
  using Docking.Tools;
  
  namespace MonoDevelop.Components.PropertyGrid
  {
  	internal class PropertyGridTree: CompactScrolledWindow
  	{
  		Gtk.TreeStore store;
  		InternalTree tree;
  		TreeViewColumn editorColumn;
  		Hashtable propertyRows;
  		ArrayList collapseStatus = new ArrayList ();
  		EditorManager editorManager;
  		PropertyGrid parentGrid;
  		
  		public event EventHandler Changed;
  		
  		private PropertySort propertySort = PropertySort.Categorized;
  
  		public PropertyGridTree (EditorManager editorManager, PropertyGrid parentGrid)
  		{
  			this.editorManager = editorManager;
  			this.parentGrid = parentGrid;
  			
  			propertyRows = new Hashtable ();
  			
  			store = new TreeStore (typeof (string), typeof(object), typeof(bool), typeof(object));
  			
  			tree = new InternalTree (this, store);
  
  			CellRendererText crt;
  			
  			TreeViewColumn col;
  
  			col = new TreeViewColumn ();
  			col.Title = "Property";
  			crt = new CellRendererPropertyGroup (tree);
  			crt.Xpad = 0;
  			col.PackStart (crt, true);
  			col.SetCellDataFunc (crt, new TreeCellDataFunc (GroupData));
  			col.Resizable = true;
  			col.Expand = false;
  			col.Sizing = TreeViewColumnSizing.Fixed;
  			col.FixedWidth = 180;
  			tree.AppendColumn (col);
  			
  			editorColumn = new TreeViewColumn ();
  			editorColumn.Title = "Value";
  			
  			CellRendererProperty crp = new CellRendererProperty (tree);
  			
  			editorColumn.PackStart (crp, true);
  			editorColumn.SetCellDataFunc (crp, new TreeCellDataFunc (PropertyData));
  			editorColumn.Sizing = TreeViewColumnSizing.Fixed;
  			editorColumn.Resizable = false;
  			editorColumn.Expand = true;
  			tree.AppendColumn (editorColumn);
  			
  			tree.HeadersVisible = false;
  			this.ShadowType = Gtk.ShadowType.None;
  			
  			this.HscrollbarPolicy = Gtk.PolicyType.Never;
  			
  			Add (tree);
  			
  			ShowAll ();
  			
  			tree.Selection.Changed += OnSelectionChanged;
  		}
  		
  		public void CommitChanges ()
  		{
  			TreePath[] sel = tree.Selection.GetSelectedRows ();
  			if (sel.Length > 0)
  				tree.SetCursor (sel[0], tree.Columns[1], false);
  		}
  		
  		public void SaveStatus ()
  		{
  			collapseStatus.Clear ();
  
  			if(tree==null || tree.Model==null)
  		   	   return;
  
  			TreeIter iter;
  			if (!tree.Model.GetIterFirst (out iter))
  				return;
  			
  			do {
  				if (!tree.GetRowExpanded (tree.Model.GetPath (iter))) {
  					collapseStatus.Add (tree.Model.GetValue (iter, 0));
  				}
  			} while (tree.Model.IterNext (ref iter));
  		}
  		
  		public void RestoreStatus ()
  		{
  			if(tree==null || tree.Model==null)
  		   	   return;
  
  			TreeIter iter;
  			if (!tree.Model.GetIterFirst (out iter))
  				return;
  			
  			// If the tree only has one group, show it always expanded
  			TreeIter iter2 = iter;
  			if (!tree.Model.IterNext (ref iter2)) {
  				tree.ExpandRow (tree.Model.GetPath (iter), true);
  				return;
  			}
  			
  			do {
  				object grp = tree.Model.GetValue (iter, 0);
  				if (!collapseStatus.Contains (grp))
  					tree.ExpandRow (tree.Model.GetPath (iter), false);
  			} while (tree.Model.IterNext (ref iter));
  		}
  		
  		public virtual void Clear ()
  		{
  			store.Clear ();
  			propertyRows.Clear ();
  		}
  		
  		public virtual void Update ()
  		{
  			// Just repaint the cells
  			QueueDraw ();
  		}
  		
  		public PropertySort PropertySort {
  			get { return propertySort; }
  			set { propertySort = value; }
  		}
  		
  		internal void Populate (PropertyDescriptorCollection properties, object instance)
  		{
  			bool categorised = PropertySort == PropertySort.Categorized;
  		
  			//transcribe browsable properties
  			ArrayList sorted = new ArrayList();
  
  			foreach (PropertyDescriptor descriptor in properties)
  				if (descriptor.IsBrowsable)
  					sorted.Add (descriptor);
  			
  			if (sorted.Count == 0)
  				return;
  			
  			InstanceData idata = new InstanceData (instance);
  			
  			if (!categorised) {
  				if (PropertySort != PropertySort.NoSort)
  					sorted.Sort(new SortByName ());
  				foreach (PropertyDescriptor pd in sorted)
  					AppendProperty (TreeIter.Zero, pd, idata);
  			}
  			else {
  				sorted.Sort (new SortByCat ());
  				string oldCat = null;
  				TreeIter catIter = TreeIter.Zero;
  				
  				foreach (PropertyDescriptor pd in sorted) {
  					if (pd.Category.Localized() != oldCat) {
  						catIter = store.AppendValues (pd.Category.Localized(), null, true, idata);
  						oldCat = pd.Category.Localized();
  					}
  					AppendProperty (catIter, pd, idata);
  				}
  			}
  		}
  		
  		internal void Update (PropertyDescriptorCollection properties, object instance)
  		{
  			foreach (PropertyDescriptor pd in properties) {
  				TreeIter it;
  				if (!store.GetIterFirst (out it))
  					continue;
  				
  				UpdateProperty (pd, it, instance);
  			}
  		}
  		
  		bool UpdateProperty (PropertyDescriptor pd, TreeIter it, object instance)
  		{
  			do {
  				PropertyDescriptor prop = (PropertyDescriptor) store.GetValue (it, 1);
  				InstanceData idata = (InstanceData) store.GetValue (it, 3);
  				if (prop != null && idata != null && prop.Name == pd.Name && idata.Instance == instance) {
  					// Don't update the current editing node, since it may cause tree update problems
  					if (!tree.Editing || !store.GetPath (tree.EditingIter).Equals (store.GetPath (it)))
  						store.SetValue (it, 1, pd);
  					return true;
  				}
  				TreeIter ci;
  				if (store.IterChildren (out ci, it)) {
  					if (UpdateProperty (pd, ci, instance))
  						return true;
  				}
  			}
  			while (store.IterNext (ref it));
  			return false;
  		}
  	
  		protected void AppendProperty (PropertyDescriptor prop, object instance)
  		{
  			AppendProperty (TreeIter.Zero, prop, new InstanceData (instance));
  		}
  		
  		protected void AppendProperty (TreeIter piter, PropertyDescriptor prop, object instance)
  		{
  			AppendProperty (piter, prop, new InstanceData (instance));
  		}
  		
  		void AppendProperty (TreeIter piter, PropertyDescriptor prop, InstanceData idata)
  		{
  			TreeIter iter;
  
  			#region this bugfix was backported to here from a newer MonoDevelop version by P.Eisenmann/S.Lohse 2016-12-06
  			// only show properties that have BrowsableAttribute set [true], or where BrowsableAttribute is not defined
  			var brows = prop.Attributes[typeof(BrowsableAttribute)] as BrowsableAttribute;
  			if (brows != null && !brows.Browsable)
  				return;
  			#endregion            
  			
  			if (piter.Equals (TreeIter.Zero))
  				iter = store.AppendValues (prop.DisplayName.Localized(), prop, false, idata);
  			else
  				iter = store.AppendValues (piter, prop.DisplayName.Localized(), prop, false, idata);
  			propertyRows [prop] = store.GetStringFromIter (iter);
  			
  			TypeConverter tc = prop.Converter;
  			if (typeof (ExpandableObjectConverter).IsAssignableFrom (tc.GetType ())) {
  				object cob = prop.GetValue (idata.Instance);
  				foreach (PropertyDescriptor cprop in TypeDescriptor.GetProperties (cob))
  					AppendProperty (iter, cprop, cob);
  			}
  		}
  		
  		protected virtual void OnObjectChanged ()
  		{
  			// Delay the notification of the change. There may be problems if the
  			// handler of this event starts its own gui loop.
  			GLib.Timeout.Add (0, delegate {
  				if (Changed != null)
  					Changed (this, EventArgs.Empty);
  				return false;
  			});
  		}
  		
  		void OnSelectionChanged (object s, EventArgs a)
  		{
  			TreePath[] rows = tree.Selection.GetSelectedRows ();
  			if (!tree.dragging && rows != null && rows.Length > 0) {
  				tree.SetCursor (rows[0], editorColumn, true);
  			}
  			TreeIter iter;
  			if (tree.Selection.GetSelected (out iter)) {
  				PropertyDescriptor prop = (PropertyDescriptor) store.GetValue (iter, 1);
  				if (prop != null)
                 parentGrid.SetHelp(prop.DisplayName.Localized(), prop.Description.Localized());
  				else
  					parentGrid.SetHelp (string.Empty, string.Empty);
  			} else {
  				parentGrid.SetHelp (string.Empty, string.Empty);
  			}
  		}
  		
  		internal void NotifyChanged ()
  		{
  			OnObjectChanged ();
  		}
  		
  		void PropertyData (Gtk.TreeViewColumn tree_column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
  		{
  			CellRendererProperty rc = (CellRendererProperty) cell;
  			bool group = (bool) model.GetValue (iter, 2);
  			if (group) {
  				rc.SetData (null, null, null);
  			} else {
  				PropertyDescriptor prop = (PropertyDescriptor) model.GetValue (iter, 1);
  				PropertyEditorCell propCell = editorManager.GetEditor (prop);
  				InstanceData idata = (InstanceData) model.GetValue (iter, 3);
  				propCell.Initialize (tree, editorManager, prop, idata.Instance);
  				rc.SetData (idata.Instance, prop, propCell);
  			}
  		}
  		
  		void GroupData (Gtk.TreeViewColumn tree_column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
  		{
  			CellRendererPropertyGroup rc = (CellRendererPropertyGroup) cell;
  			rc.IsGroup = (bool) model.GetValue (iter, 2);
  			rc.Text = (string) model.GetValue (iter, 0);
  			
  			PropertyDescriptor prop = (PropertyDescriptor) model.GetValue (iter, 1);
  			if (prop != null)
  				rc.SensitiveProperty = !prop.IsReadOnly;
  			else
  				rc.SensitiveProperty = true;
  		}
  		
  		private class SortByCat : IComparer
  		{
  			public int Compare (object x, object y)
  			{
  				int catcomp = ((PropertyDescriptor)x).Category.Localized().CompareTo (((PropertyDescriptor)y).Category.Localized());
  
  				if (catcomp == 0)
  					return ((PropertyDescriptor)x).DisplayName.Localized().CompareTo (((PropertyDescriptor)y).DisplayName.Localized());
  				else
  					return catcomp;
  			}
  		}
  
  		private class SortByName : IComparer
  		{
  			public int Compare(object x, object y)
  			{
  				return ((PropertyDescriptor)x).DisplayName.Localized().CompareTo (((PropertyDescriptor)y).DisplayName.Localized());
  			}
  		}
  	}
  	
  	class InternalTree: TreeView
  	{
  		internal ArrayList Groups = new ArrayList ();
  		Pango.Layout layout;
  		bool editing;
  		TreeIter editingIter;
  		PropertyGridTree tree;
  		internal bool dragging;
  		int dragPos;
  		Gdk.Cursor resizeCursor = new Gdk.Cursor (CursorType.SbHDoubleArrow);
  		
  		public InternalTree (PropertyGridTree tree, TreeModel model): base (model)
  		{
  			this.tree = tree;
  			layout = new Pango.Layout (this.PangoContext);
  			layout.Wrap = Pango.WrapMode.Char;
  		}
  		
  		public bool Editing {
  			get { return editing; }
  			set { editing = value; }
  		}
  		
  		public TreeIter EditingIter {
  			get { return editingIter; }
  			set { editingIter = value; }
  		}
  		
  		public PropertyGridTree PropertyTree {
  			get { return tree; }
  		}
  		
  		protected override bool OnExposeEvent (Gdk.EventExpose e)
  		{
  			Groups.Clear ();
  			
  			bool res = base.OnExposeEvent (e);
  			
  			foreach (TreeGroup grp in Groups) {
  				layout.SetMarkup ("<b>" + grp.Group + "</b>");
  				e.Window.DrawLayout (this.Style.TextGC (grp.State), grp.X, grp.Y, layout);
  			}
  			
  			return res;
  		}
  		
  		protected override void OnDestroyed ()
  		{
  			base.OnDestroyed ();
  			if (resizeCursor != null) {
  				resizeCursor.Dispose ();
  				resizeCursor = null;
  			}
  		}
  		
  		protected override bool OnMotionNotifyEvent (EventMotion evnt)
  		{
  			if (dragging) {
  				int nw = (int)(evnt.X) + dragPos;
  				if (nw <= 40) nw = 40;
  				GLib.Idle.Add (delegate {
  					Columns[0].FixedWidth = nw;
  					return false;
  				});
  			} else {
  				int w = Columns[0].Width;
  				if (Math.Abs (w - evnt.X) < 5)
  					this.GdkWindow.Cursor = resizeCursor;
  				else
  					this.GdkWindow.Cursor = null;
  			}
  			return base.OnMotionNotifyEvent (evnt);
  		}
  		
  		protected override bool OnButtonPressEvent (EventButton evnt)
  		{
  			int w = Columns[0].Width;
  			if (Math.Abs (w - evnt.X) < 5) {
  				TreePath[] rows = Selection.GetSelectedRows ();
  				if (rows != null && rows.Length > 0)
  					SetCursor (rows[0], Columns[0], false);
  				dragging = true;
  				dragPos = w - (int) evnt.X;
  				this.GdkWindow.Cursor = resizeCursor;
  			}
  			return base.OnButtonPressEvent (evnt);
  		}
  		
  		protected override bool OnButtonReleaseEvent (EventButton evnt)
  		{
  			if (dragging) {
  				this.GdkWindow.Cursor = null;
  				dragging = false;
  			}
  			return base.OnButtonReleaseEvent (evnt);
  		}
  
  		public virtual void Update ()
  		{
  		}
  	}
  	
  	class TreeGroup
  	{
  		public string Group;
  		public int X;
  		public int Y;
  		public StateType State;
  	}
  	
  	class CellRendererProperty: CellRenderer
  	{
  		TreeView tree;
  		PropertyDescriptor property;
  		object instance;
  		int rowHeight;
  		PropertyEditorCell editorCell;
  		bool sensitive = true;
  		bool visible = true;
  		
  		public CellRendererProperty (TreeView tree)
  		{
  			this.tree = tree;
  			Xalign = 0;
  			Xpad = 3;
  			
  			Mode |= Gtk.CellRendererMode.Editable;
  			Entry dummyEntry = new Gtk.Entry ();
  			dummyEntry.HasFrame = false;
  			rowHeight = dummyEntry.SizeRequest ().Height;
  		}
  		
  		public void SetData (object instance, PropertyDescriptor property, PropertyEditorCell editor)
  		{
  			this.instance = instance;
  			this.property = property;
  			if (property == null) {
  				this.CellBackgroundGdk = tree.Style.MidColors [(int) Gtk.StateType.Normal];
  				sensitive = true;
  			}
  			else {
  				this.CellBackground = null;
  				sensitive = !property.IsReadOnly || (editor != null && editor.EditsReadOnlyObject);
  			}
  			
  			editorCell = editor;
  		}
  
  		public override void GetSize (Widget widget, ref Rectangle cell_area, out int x_offset, out int y_offset, out int width, out int height)
  		{
  			if (editorCell != null)
  				editorCell.GetSize ((int)(cell_area.Width - this.Xpad * 2), out width, out height);
  			else {
  				width = height = 0;
  			}
  			
  			width += (int) this.Xpad * 2;
  			height += (int) this.Ypad * 2;
  
  			x_offset = 0;
  			y_offset = 0;
  			
  			if (height < rowHeight)
  				height = rowHeight;
  		}
  
  		protected override void Render (Drawable window, Widget widget, Rectangle background_area, Rectangle cell_area, Rectangle expose_area, CellRendererState flags)
  		{
  			if (instance == null || !visible)
  				return;
  			int width = 0, height = 0;
  
gt: 			int iwidth = cell_area.Width - (int) this.Xpad * 2;