context: //
  // PropertyEditorCell.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.Reflection;
  using System.Collections;
  using System.ComponentModel;
  using MonoDevelop.Components.PropertyGrid.PropertyEditors;
  using Gtk;
  using Gdk;
  
  namespace MonoDevelop.Components.PropertyGrid
  {
  	public class PropertyEditorCell
  	{
  		Pango.Layout layout;
  		PropertyDescriptor property; 
  		object obj;
  		Gtk.Widget container;
  		EditorManager editorManager;
  		
  		public object Instance {
  			get { return obj; }
  		}
  		
  		public PropertyDescriptor Property {
  			get { return property; }
  		}
  		
  		public Gtk.Widget Container {
  			get { return container; }
  		}
  		
  		internal EditorManager EditorManager {
  			get { return editorManager; }
  		}
  		
  		internal void Initialize (Widget container, EditorManager editorManager, PropertyDescriptor property, object obj)
  		{
  			this.container = container;
  			this.editorManager = editorManager;
  			
  			layout = new Pango.Layout (container.PangoContext);
  			layout.Width = -1;
  			
  			Pango.FontDescription des = container.Style.FontDescription.Copy();
  			layout.FontDescription = des;
  			
  			this.property = property;
  			this.obj = obj;
  			Initialize ();
  		}
  
  		public EditSession StartEditing (Gdk.Rectangle cell_area, StateType state)
  		{
  			IPropertyEditor ed = CreateEditor (cell_area, state);
  			if (ed == null)
  				return null;
  			return new EditSession (container, obj, property, ed);
  		}
  		
  		protected virtual string GetValueText ()
  		{
  			if (obj == null) return "";
  			object val = property.GetValue (obj);
  			if (val == null) return "";
  			else return property.Converter.ConvertToString (val);
  		}
  		
  		protected virtual string GetValueMarkup ()
  		{
  			return null;
  		}
  		
  		string GetNormalizedText (string s)
  		{
  			if (s == null)
  				return "";
  
  			int i = s.IndexOf ('\n');
  			if (i == -1)
  				return s;
  			
  			s = s.TrimStart ('\n',' ','\t');
  			i = s.IndexOf ('\n');
  			if (i != -1)
  				return s.Substring (0, i) + "...";
  			else
  				return s;
  		}
  		
  		public object Value {
  			get { return obj != null ? property.GetValue (obj) : null; }
  		}
  		
  		protected virtual void Initialize ()
  		{
  			string s = GetValueMarkup ();
  			if (s != null)
  				layout.SetMarkup (GetNormalizedText (s));
  			else
  				layout.SetText (GetNormalizedText (GetValueText ()));
  		}
  		
  		public virtual void GetSize (int availableWidth, out int width, out int height)
  		{
  			layout.GetPixelSize (out width, out height);
  		}
  		
  		public virtual void Render (Drawable window, Gdk.Rectangle bounds, StateType state)
  		{
  			int w, h;
  			layout.GetPixelSize (out w, out h);
  			int dy = (bounds.Height - h) / 2;
  			window.DrawLayout (container.Style.TextGC (state), bounds.X, dy + bounds.Y, layout);
  		}
  		
  		protected virtual IPropertyEditor CreateEditor (Gdk.Rectangle cell_area, StateType state)
  		{
  			if (DialogueEdit && (!property.IsReadOnly || EditsReadOnlyObject)) {
  				return new PropertyDialogueEditor (this);
  			}
  			else {
  				Type editorType = editorManager.GetEditorType (property);
  				if (editorType == null)
  					return null;
  				
  				IPropertyEditor editor = Activator.CreateInstance (editorType) as IPropertyEditor;
  				if (editor == null)
  					throw new Exception ("The property editor '" + editorType + "' must implement the interface IPropertyEditor");
  				return editor;
  			}
  		}
  		
  		/// <summary>
  		/// Whether the editor should show a button.
  		/// </summary>
  		public virtual bool DialogueEdit {
  			get { return false; }
  		}
  
  		/// <summary>
  		/// If the property is read-only, is is usually not edited. If the editor
  		/// can edit sub-properties of a read-only complex object, this must return true.
  		/// <remarks>The default value is false.</remarks>
  		/// </summary>
  		/// <returns>True if the editor can edit read-only properties</returns>
  		public virtual bool EditsReadOnlyObject {
  			get { return false; }
  		}
  		
  		public virtual void LaunchDialogue ()
  		{
  			if (DialogueEdit)
  				throw new NotImplementedException();
  		}
  	}
  	
  	
  	class DefaultPropertyEditor: Gtk.Entry, IPropertyEditor
  	{
  		PropertyDescriptor property;
  		
  		public void Initialize (EditSession session)
  		{
  			this.property = session.Property;
  		}
  		
  		public object Value {
  			get { 
  				return Convert.ChangeType (Text, property.PropertyType); 
  			}
  			set {
  				if (value == null)
  					Text = "";
  				else
  					Text = Convert.ToString (value); 
  			}
  		}
  		
  		protected override void OnChanged ()
  		{
  			base.OnChanged ();
  			if (ValueChanged != null)
  				ValueChanged (this, EventArgs.Empty);
  		}
  
  		public event EventHandler ValueChanged;
  	}
  	
  	public class EditSession : ITypeDescriptorContext
  	{
  		PropertyDescriptor property; 
  		object obj;
  		Gtk.Widget container;
  		IPropertyEditor currentEditor;
  		bool syncing;
  		
  		public event EventHandler Changed;
  		
  		public EditSession (Gtk.Widget container, object instance, PropertyDescriptor property, IPropertyEditor currentEditor)
  		{
  			this.property = property;
  			this.obj = instance;
  			this.container = container;
  			this.currentEditor = currentEditor;
  			
  			currentEditor.Initialize (this);
  			if (instance != null)
  				currentEditor.Value = property.GetValue (instance);
  			
  			currentEditor.ValueChanged += OnValueChanged;
  		}
  		
  		public void Dispose ()
  		{
  			currentEditor.Dispose ();
  		}
  		
  		public object Instance {
  			get { return obj; }
  		}
  		
  		public PropertyDescriptor Property {
  			get { return property; }
  		}
  		
  		PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor {
  			get { return property; }
  		}
  		
  		public Gtk.Widget Container {
  			get { return container; }
  		}
  		
  		public IPropertyEditor Editor {
  			get { return currentEditor; }
  		}
  		
  		void OnValueChanged (object s, EventArgs a)
  		{
  			if (!syncing) {
  				syncing = true;
  				if (!property.IsReadOnly) {
  					property.SetValue (obj, currentEditor.Value);
  					if (Changed != null)
  						Changed (s, a);
  				}
  				syncing = false;
  			}
  		}
  		
  /*		public void AttachObject (object ob)
  		{
  			if (ob == null)
  				throw new ArgumentNullException ("ob");
  			
  			syncing = true;
  			this.obj = ob;
  			currentEditor.AttachObject (obj);
  			
  			// It is the responsibility of the editor to convert value types
  			object initial = property.GetValue (obj);
  			currentEditor.Value = initial;
  			
  			syncing = false;
  		}
  */		
  		public void UpdateEditor ()
  		{
  			if (!syncing) {
  				syncing = true;
  				currentEditor.Value = property.GetValue (obj);
  				syncing = false;
  			}
  		}
  		
  		#region FIXME Unimplemented ITypeDescriptorContext and IServiceProvider members
  		
  		object IServiceProvider.GetService (Type serviceType)
  		{
  			return null;
  		}
  		
  		void ITypeDescriptorContext.OnComponentChanged ()
  		{
  		}
  		
  		bool ITypeDescriptorContext.OnComponentChanging ()
  		{
  			return true;
  		}
  		
  		IContainer ITypeDescriptorContext.Container { get { return null; } }
  		
  		#endregion
  	}
  	
  	class CellRendererWidget: Gtk.DrawingArea
  	{
  		PropertyEditorCell cell;
  		object obj;
  		PropertyDescriptor property;
  		EditorManager em;
  		
  		public CellRendererWidget (PropertyEditorCell cell)
  		{
  			this.cell = cell;
  			this.obj = cell.Instance;
  			this.property = cell.Property;
  			em = cell.EditorManager;
  			this.ModifyBg (Gtk.StateType.Normal, this.Style.White);
  		}
  		
  		protected override bool OnExposeEvent (EventExpose evnt)
  		{
  			bool res = base.OnExposeEvent (evnt);
  			cell.Initialize (this, em, property, obj);
  			
  			Gdk.Rectangle rect = Allocation;
  			rect.Inflate (-3, 0);// Add some margin
  			
  			cell.Render (this.GdkWindow, rect, StateType.Normal);
  			return res;
  		}
  	}
  	
  	class PropertyDialogueEditor: HBox, IPropertyEditor
  	{
  		PropertyEditorCell cell;
  		
  		public PropertyDialogueEditor (PropertyEditorCell cell)
  		{
  			this.cell = cell;
  			Spacing = 3;
  			PackStart (new CellRendererWidget (cell), true, true, 0);
  			Label buttonLabel = new Label ();
  			buttonLabel.UseMarkup = true;
  			buttonLabel.Xpad = 0; buttonLabel.Ypad = 0;
  			buttonLabel.Markup = "<span size=\"small\">...</span>";
  			Button dialogueButton = new Button (buttonLabel);
  			dialogueButton.Clicked += new EventHandler (DialogueButtonClicked);
  			PackStart (dialogueButton, false, false, 0);
  
gt: 			this.ModifyBg (Gtk.StateType.Normal, this.Style.White);