// // 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.ComponentModel; using Gdk; using Gtk; using Mono.TextEditor; namespace MonoDevelop.Components.PropertyGrid { public class PropertyEditorCell { Pango.Layout layout; ITypeDescriptorContext context; Widget container; EditorManager editorManager; public object Instance { get { return context.Instance; } } public PropertyDescriptor Property { get { return context.PropertyDescriptor; } } protected ITypeDescriptorContext Context { get { return context; } } public Widget Container { get { return container; } } internal EditorManager EditorManager { get { return editorManager; } } internal void Initialize (Widget container, EditorManager editorManager, ITypeDescriptorContext context) { 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.context = context; Initialize (); } public EditSession StartEditing (Rectangle cellArea, StateType state) { IPropertyEditor ed = CreateEditor (cellArea, state); if (ed == null) return null; return new EditSession (container, context, ed); } protected virtual string GetValueText () { var val = Value; if (val != null) return Property.Converter.ConvertToString (context, val); return ""; } 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) + "..."; return s; } public object Value { get { return Instance != null ? Property.GetValue (Instance) : 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, Cairo.Context ctx, Rectangle bounds, StateType state) { int w, h; layout.GetPixelSize (out w, out h); int dy = (bounds.Height - h) / 2; ctx.Save (); ctx.SetSourceColor (container.Style.Text (state).ToCairoColor ()); ctx.MoveTo (bounds.X, dy + bounds.Y); Pango.CairoHelper.ShowLayout (ctx, layout); ctx.Restore (); } protected virtual IPropertyEditor CreateEditor (Rectangle cellArea, StateType state) { if (DialogueEdit && (!Property.IsReadOnly || EditsReadOnlyObject)) { return new PropertyDialogueEditor (this, context); } else { Type editorType = editorManager.GetEditorType (context); if (editorType == null) return null; var editor = Activator.CreateInstance (editorType) as IPropertyEditor; if (editor == null) throw new Exception ("The property editor '" + editorType + "' must implement the interface IPropertyEditor"); return editor; } } /// /// Whether the editor should show a button. /// public virtual bool DialogueEdit { get { return false; } } /// /// 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. /// The default value is false. /// /// True if the editor can edit read-only properties public virtual bool EditsReadOnlyObject { get { return false; } } public virtual void LaunchDialogue () { if (DialogueEdit) throw new NotImplementedException (); } } class DefaultPropertyEditor: Entry, IPropertyEditor { PropertyDescriptor property; public void Initialize (EditSession session) { 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 { Widget container; IPropertyEditor currentEditor; bool syncing; readonly ITypeDescriptorContext context; public event EventHandler Changed; internal EditSession (Widget container, ITypeDescriptorContext context, IPropertyEditor currentEditor) { this.context = context; this.container = container; this.currentEditor = currentEditor; currentEditor.Initialize (this); if (Instance != null) currentEditor.Value = context.PropertyDescriptor.GetValue (Instance); currentEditor.ValueChanged += OnValueChanged; } public void Dispose () { currentEditor.Dispose (); } public object Instance { get { return context.Instance; } } public PropertyDescriptor Property { get { return context.PropertyDescriptor; } } PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor { get { return context.PropertyDescriptor; } } public Widget Container { get { return container; } } public IPropertyEditor Editor { get { return currentEditor; } } void OnValueChanged (object s, EventArgs a) { if (!syncing) { syncing = true; if (!context.PropertyDescriptor.IsReadOnly) { context.PropertyDescriptor.SetValue (context.Instance, 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 = context.PropertyDescriptor.GetValue (context.Instance); syncing = false; } } object IServiceProvider.GetService (Type serviceType) { return context.GetService (serviceType); } void ITypeDescriptorContext.OnComponentChanged () { context.OnComponentChanged (); } bool ITypeDescriptorContext.OnComponentChanging () { return context.OnComponentChanging (); } IContainer ITypeDescriptorContext.Container { get { return context.Container; } } } class CellRendererWidget: DrawingArea { readonly PropertyEditorCell cell; readonly ITypeDescriptorContext context; readonly EditorManager em; public CellRendererWidget (PropertyEditorCell cell, ITypeDescriptorContext context) { this.cell = cell; this.context = context; em = cell.EditorManager; this.ModifyBg (StateType.Normal, this.Style.White); } protected override bool OnExposeEvent (EventExpose evnt) { bool res = base.OnExposeEvent (evnt); cell.Initialize (this, em, context); Rectangle rect = Allocation; rect.Inflate (-3, 0);// Add some margin using (Cairo.Context ctx = CairoHelper.Create (GdkWindow)) { cell.Render (GdkWindow, ctx, rect, StateType.Normal); } return res; } } class PropertyDialogueEditor: HBox, IPropertyEditor { PropertyEditorCell cell; public PropertyDialogueEditor (PropertyEditorCell cell, ITypeDescriptorContext context) { this.cell = cell; Spacing = 3; PackStart (new CellRendererWidget (cell, context), true, true, 0); Label buttonLabel = new Label (); buttonLabel.UseMarkup = true; buttonLabel.Xpad = 0; buttonLabel.Ypad = 0; buttonLabel.Markup = "..."; Button dialogueButton = new Button (buttonLabel); dialogueButton.Clicked += DialogueButtonClicked; PackStart (dialogueButton, false, false, 0); this.ModifyBg (StateType.Normal, this.Style.White); ShowAll (); } void DialogueButtonClicked (object s, EventArgs args) { cell.LaunchDialogue (); if (ValueChanged != null) ValueChanged (this, args); } public void Initialize (EditSession session) { } public object Value { get { return cell.Value; } set { } } public event EventHandler ValueChanged; } public interface IPropertyEditor: IDisposable { // Called once to initialize the editor. void Initialize (EditSession session); // Gets/Sets the value of the editor. If the editor supports // several value types, it is the responsibility of the editor // to return values with the expected type. object Value { get; set; } // To be fired when the edited value changes. event EventHandler ValueChanged; } }