diff options
| author | jpbruyere <jp.bruyere@hotmail.com> | 2017-03-15 05:59:51 +0100 |
|---|---|---|
| committer | jpbruyere <jp.bruyere@hotmail.com> | 2017-03-15 05:59:51 +0100 |
| commit | 45f74150dd79db5a162034ab0c43e6abdb83beea (patch) | |
| tree | 1d9f5af88c574c75117a47992302bab70f0999b6 /src/GraphicObjects/Base | |
| parent | 20ce11875dc27c7399e3f5bf2a060e2c7cbeaad7 (diff) | |
split graphic objects dirRearangeDirs
Diffstat (limited to 'src/GraphicObjects/Base')
38 files changed, 8466 insertions, 0 deletions
diff --git a/src/GraphicObjects/Base/Border.cs b/src/GraphicObjects/Base/Border.cs new file mode 100644 index 00000000..672d5ab8 --- /dev/null +++ b/src/GraphicObjects/Base/Border.cs @@ -0,0 +1,100 @@ +// +// Border.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; +using System.Diagnostics; + +namespace Crow +{ + public class Border : Container + { + #region CTOR + public Border () : base(){} + #endregion + + #region private fields + int _borderWidth; + #endregion + + #region public properties + [XmlAttributeAttribute()][DefaultValue(1)] + public virtual int BorderWidth { + get { return _borderWidth; } + set { + _borderWidth = value; + RegisterForGraphicUpdate (); + } + } + #endregion + + #region GraphicObject override + [XmlIgnore]public override Rectangle ClientRectangle { + get { + Rectangle cb = base.ClientRectangle; + cb.Inflate (- BorderWidth); + return cb; + } + } + + protected override int measureRawSize (LayoutingType lt) + { + int tmp = base.measureRawSize (lt); + return tmp < 0 ? tmp : tmp + 2 * BorderWidth; + } + protected override void onDraw (Cairo.Context gr) + { + Rectangle rBack = new Rectangle (Slot.Size); + + //rBack.Inflate (-Margin); +// if (BorderWidth > 0) +// rBack.Inflate (-BorderWidth / 2); + + Background.SetAsSource (gr, rBack); + CairoHelpers.CairoRectangle(gr, rBack, CornerRadius); + gr.Fill (); + + if (BorderWidth > 0) { + Foreground.SetAsSource (gr, rBack); + CairoHelpers.CairoRectangle(gr, rBack, CornerRadius, BorderWidth); + } + + gr.Save (); + if (ClipToClientRect) { + //clip to client zone + CairoHelpers.CairoRectangle (gr, ClientRectangle,Math.Max(0.0, CornerRadius-Margin)); + gr.Clip (); + } + + if (child != null) + child.Paint (ref gr); + gr.Restore (); + } + #endregion + } +} + diff --git a/src/GraphicObjects/Base/Button.cs b/src/GraphicObjects/Base/Button.cs new file mode 100644 index 00000000..75f43014 --- /dev/null +++ b/src/GraphicObjects/Base/Button.cs @@ -0,0 +1,124 @@ +// +// Button.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Generic; +using System.Linq; +using System.Text; +//using OpenTK.Graphics.OpenGL; + +using System.Diagnostics; + +using System.Xml.Serialization; +using Cairo; +using System.ComponentModel; + +namespace Crow +{ + public class Button : TemplatedContainer + { + string image; + bool isPressed; + Container _contentContainer; + + #region CTOR + public Button() : base() + {} + #endregion + + public event EventHandler Pressed; + public event EventHandler Released; + + #region TemplatedContainer overrides + public override GraphicObject Content { + get { + return _contentContainer == null ? null : _contentContainer.Child; + } + set { + if (_contentContainer != null) + _contentContainer.SetChild(value); + } + } + protected override void loadTemplate(GraphicObject template = null) + { + base.loadTemplate (template); + + _contentContainer = this.child.FindByName ("Content") as Container; + } + #endregion + + #region GraphicObject Overrides + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + IsPressed = true; + + base.onMouseDown (sender, e); + + //TODO:remove + NotifyValueChanged ("State", "pressed"); + } + public override void onMouseUp (object sender, MouseButtonEventArgs e) + { + IsPressed = false; + + base.onMouseUp (sender, e); + + //TODO:remove + NotifyValueChanged ("State", "normal"); + } + #endregion + + [XmlAttributeAttribute][DefaultValue("#Crow.Images.button.svg")] + public string Image { + get { return image; } + set { + if (image == value) + return; + image = value; + NotifyValueChanged ("Image", image); + } + } + [XmlAttributeAttribute][DefaultValue(false)] + public bool IsPressed + { + get { return isPressed; } + set + { + if (isPressed == value) + return; + + isPressed = value; + + NotifyValueChanged ("IsPressed", isPressed); + + if (isPressed) + Pressed.Raise (this, null); + else + Released.Raise (this, null); + } + } + } +} diff --git a/src/GraphicObjects/Base/CheckBox.cs b/src/GraphicObjects/Base/CheckBox.cs new file mode 100644 index 00000000..0a40d269 --- /dev/null +++ b/src/GraphicObjects/Base/CheckBox.cs @@ -0,0 +1,71 @@ +// +// CheckBox.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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 System.Xml.Serialization; + +namespace Crow +{ + public class CheckBox : TemplatedControl + { + bool isChecked; + + #region CTOR + public CheckBox() : base() + {} + #endregion + + public event EventHandler Checked; + public event EventHandler Unchecked; + + [XmlAttributeAttribute()][DefaultValue(false)] + public bool IsChecked + { + get { return isChecked; } + set + { + if (isChecked == value) + return; + + isChecked = value; + + NotifyValueChanged ("IsChecked", value); + + if (isChecked) + Checked.Raise (this, null); + else + Unchecked.Raise (this, null); + } + } + + public override void onMouseClick (object sender, MouseButtonEventArgs e) + { + IsChecked = !IsChecked; + base.onMouseClick (sender, e); + } + } +} diff --git a/src/GraphicObjects/Base/ComboBox.cs b/src/GraphicObjects/Base/ComboBox.cs new file mode 100644 index 00000000..15b49dbe --- /dev/null +++ b/src/GraphicObjects/Base/ComboBox.cs @@ -0,0 +1,55 @@ +// +// ComboBox.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; + +namespace Crow +{ + public class ComboBox : ListBox + { + #region CTOR + public ComboBox() : base(){ } + #endregion + + Size minimumPopupSize = "10,10"; + [XmlIgnore]public Size MinimumPopupSize{ + get { return minimumPopupSize; } + set { + minimumPopupSize = value; + NotifyValueChanged ("MinimumPopupSize", minimumPopupSize); + } + } + + public override void OnLayoutChanges (LayoutingType layoutType) + { + base.OnLayoutChanges (layoutType); + + if (layoutType == LayoutingType.Width) + MinimumPopupSize = new Size (this.Slot.Width, minimumPopupSize.Height); + } + } +} diff --git a/src/GraphicObjects/Base/Container.cs b/src/GraphicObjects/Base/Container.cs new file mode 100644 index 00000000..aac35946 --- /dev/null +++ b/src/GraphicObjects/Base/Container.cs @@ -0,0 +1,109 @@ +// +// Container.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.Reflection; +using System.ComponentModel; +using System.Linq; +using System.Threading; + +namespace Crow +{ + public class Container : PrivateContainer, IXmlSerializable + { + #region CTOR + public Container() + : base() + { + } + #endregion + + [XmlIgnore] + public GraphicObject Child { + get { return child; } + set { child = value; } + } + public virtual void SetChild(GraphicObject _child) + { + base.SetChild (_child); + } + + #region IXmlSerializable + + public override System.Xml.Schema.XmlSchema GetSchema() + { + return null; + } + public override void ReadXml(System.Xml.XmlReader reader) + { + //only read attributes in GraphicObject IXmlReader implementation + base.ReadXml(reader); + + + using (System.Xml.XmlReader subTree = reader.ReadSubtree()) + { + subTree.Read(); //skip current node + subTree.Read(); //read first child + + if (!subTree.IsStartElement()) + return; + + Type t = Type.GetType("Crow." + subTree.Name); + if (t == null) { + Assembly a = Assembly.GetEntryAssembly (); + foreach (Type expT in a.GetExportedTypes ()) { + if (expT.Name == subTree.Name) { + t = expT; + break; + } + } + } + GraphicObject go = (GraphicObject)Activator.CreateInstance(t); + + (go as IXmlSerializable).ReadXml(subTree); + + SetChild(go); + + subTree.Read();//closing tag + } + } + public override void WriteXml(System.Xml.XmlWriter writer) + { + base.WriteXml(writer); + + if (Child == null) + return; + + writer.WriteStartElement(Child.GetType().Name); + (Child as IXmlSerializable).WriteXml(writer); + writer.WriteEndElement(); + } + + #endregion + } +} + diff --git a/src/GraphicObjects/Base/Expandable.cs b/src/GraphicObjects/Base/Expandable.cs new file mode 100644 index 00000000..1cb36450 --- /dev/null +++ b/src/GraphicObjects/Base/Expandable.cs @@ -0,0 +1,141 @@ +// +// Expandable.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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 System.Xml.Serialization; + +namespace Crow +{ + public class Expandable : TemplatedContainer + { + #region CTOR + public Expandable() : base() + { + } + #endregion + + #region Private fields + bool _isExpanded; + string image; + Container _contentContainer; + #endregion + + #region Event Handlers + public event EventHandler Expand; + public event EventHandler Collapse; + #endregion + + public BooleanTestOnInstance GetIsExpandable; + + public void onClickForExpand (object sender, MouseButtonEventArgs e) + { + IsExpanded = !IsExpanded; + } + + public override GraphicObject Content { + get { + return _contentContainer == null ? null : _contentContainer.Child; + } + set { + _contentContainer.SetChild(value); + NotifyValueChanged ("HasContent", HasContent); + } + } + //TODO: move loadTemplate and ResolveBinding in TemplatedContainer + protected override void loadTemplate(GraphicObject template = null) + { + base.loadTemplate (template); + + _contentContainer = this.child.FindByName ("Content") as Container; + } + + #region Public properties + [XmlAttributeAttribute][DefaultValue("#Crow.Images.Icons.expandable.svg")] + public string Image { + get { return image; } + set { + if (image == value) + return; + image = value; + NotifyValueChanged ("Image", image); + } + } + [XmlAttributeAttribute][DefaultValue(false)] + public bool IsExpanded + { + get { return _isExpanded; } + set + { + if (value == _isExpanded) + return; + + _isExpanded = value; + + bool isExp = IsExpandable; + NotifyValueChanged ("IsExpandable", isExp); + if (!(HasContent & isExp)) + _isExpanded = false; + + NotifyValueChanged ("IsExpanded", _isExpanded); + + if (_isExpanded) + onExpand (this, null); + else + onCollapse (this, null); + } + } + [XmlIgnore]public bool HasContent { + get { return _contentContainer == null ? false : _contentContainer.Child != null; } + } + [XmlIgnore]public bool IsExpandable { + get { + try { + return GetIsExpandable == null ? true : GetIsExpandable (this); + } catch (Exception ex) { + System.Diagnostics.Debug.WriteLine ("Not Expandable error: " + ex.ToString ()); + return false; + } + } + } + #endregion + + public virtual void onExpand(object sender, EventArgs e) + { + if (_contentContainer != null) + _contentContainer.Visible = true; + + Expand.Raise (this, e); + } + public virtual void onCollapse(object sender, EventArgs e) + { + if (_contentContainer != null) + _contentContainer.Visible = false; + + Collapse.Raise (this, e); + } + } +} diff --git a/src/GraphicObjects/Base/GenericStack.cs b/src/GraphicObjects/Base/GenericStack.cs new file mode 100644 index 00000000..cecdaffd --- /dev/null +++ b/src/GraphicObjects/Base/GenericStack.cs @@ -0,0 +1,228 @@ +// +// GenericStack.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.ComponentModel; +using System.Diagnostics; +using System.Xml.Serialization; +using System; + +namespace Crow +{ + public class GenericStack : Group + { + #region CTOR + public GenericStack() + : base() + { + } + #endregion + + #region Private fields + int _spacing; + Orientation _orientation; + #endregion + + #region Public Properties + [XmlAttributeAttribute()][DefaultValue(2)] + public int Spacing + { + get { return _spacing; } + set { + if (_spacing == value) + return; + _spacing = value; + NotifyValueChanged ("Spacing", Spacing); + RegisterForLayouting (LayoutingType.Sizing|LayoutingType.ArrangeChildren); + } + } + [XmlAttributeAttribute()][DefaultValue(Orientation.Horizontal)] + public virtual Orientation Orientation + { + get { return _orientation; } + set { _orientation = value; } + } + #endregion + + #region GraphicObject Overrides + public override bool ArrangeChildren { get { return true; } } + public override void ChildrenLayoutingConstraints (ref LayoutingType layoutType) + { + //Prevent child repositionning in the direction of stacking + if (Orientation == Orientation.Horizontal) + layoutType &= (~LayoutingType.X); + else + layoutType &= (~LayoutingType.Y); + } + protected override int measureRawSize (LayoutingType lt) + { + int totSpace = 0; + for (int i = 0; i < Children.Count; i++) { + if (Children [i].Visible) + totSpace += Spacing; + } + if (totSpace > 0) + totSpace -= Spacing; + if (lt == LayoutingType.Width) { + if (Orientation == Orientation.Horizontal) + return contentSize.Width + totSpace + 2 * Margin; + }else if (Orientation == Orientation.Vertical) + return contentSize.Height + totSpace + 2 * Margin; + + return base.measureRawSize (lt); + } + public virtual void ComputeChildrenPositions() + { + int d = 0; + if (Orientation == Orientation.Horizontal) { + foreach (GraphicObject c in Children) { + if (!c.Visible) + continue; + c.Slot.X = d; + d += c.Slot.Width + Spacing; + } + } else { + foreach (GraphicObject c in Children) { + if (!c.Visible) + continue; + c.Slot.Y = d; + d += c.Slot.Height + Spacing; + } + } + IsDirty = true; + } + GraphicObject stretchedGO = null; + public override bool UpdateLayout (LayoutingType layoutType) + { + RegisteredLayoutings &= (~layoutType); + + if (layoutType == LayoutingType.ArrangeChildren) { + //allow 1 child to have size to 0 if stack has fixed or streched size policy, + //this child will occupy remaining space + //if stack size policy is Fit, no child may have stretch enabled + //in the direction of stacking. + ComputeChildrenPositions (); + + //if no layouting remains in queue for item, registre for redraw + if (RegisteredLayoutings == LayoutingType.None && IsDirty) + CurrentInterface.EnqueueForRepaint (this); + + return true; + } + + return base.UpdateLayout(layoutType); + } + + public override void OnChildLayoutChanges (object sender, LayoutingEventArgs arg) + { + GraphicObject go = sender as GraphicObject; + //Debug.WriteLine ("child layout change: " + go.LastSlots.ToString() + " => " + go.Slot.ToString()); + switch (arg.LayoutType) { + case LayoutingType.Width: + if (Orientation == Orientation.Horizontal) { + if (go.Width == Measure.Stretched) { + if (stretchedGO == null && Width != Measure.Fit) + stretchedGO = go; + else if (stretchedGO != go) { + go.Slot.Width = 0; + go.Width = Measure.Fit; + return; + } + } else + contentSize.Width += go.Slot.Width - go.LastSlots.Width; + + if (stretchedGO != null) { + int newW = Math.Max ( + this.ClientRectangle.Width - contentSize.Width - Spacing * (Children.Count - 1), + stretchedGO.MinimumSize.Width); + if (stretchedGO.MaximumSize.Width > 0) + newW = Math.Min (newW, stretchedGO.MaximumSize.Width); + if (newW != stretchedGO.Slot.Width) { + stretchedGO.Slot.Width = newW; + stretchedGO.IsDirty = true; +#if DEBUG_LAYOUTING + Debug.WriteLine ("\tAdjusting Width of " + stretchedGO.ToString()); +#endif + stretchedGO.LayoutChanged -= OnChildLayoutChanges; + stretchedGO.OnLayoutChanges (LayoutingType.Width); + stretchedGO.LayoutChanged += OnChildLayoutChanges; + stretchedGO.LastSlots.Width = stretchedGO.Slot.Width; + } + } + + if (Width == Measure.Fit) + this.RegisterForLayouting (LayoutingType.Width); + + this.RegisterForLayouting (LayoutingType.ArrangeChildren); + return; + } + break; + case LayoutingType.Height: + if (Orientation == Orientation.Vertical) { + if (go.Height == Measure.Stretched) { + if (stretchedGO == null && Height != Measure.Fit) + stretchedGO = go; + else if (stretchedGO != go){ + go.Slot.Height = 0; + go.Height = Measure.Fit; + return; + } + } else + contentSize.Height += go.Slot.Height - go.LastSlots.Height; + + if (stretchedGO != null) { + int newH = Math.Max ( + this.ClientRectangle.Height - contentSize.Height - Spacing * (Children.Count - 1), + stretchedGO.MinimumSize.Height); + if (stretchedGO.MaximumSize.Height > 0) + newH = Math.Min (newH, stretchedGO.MaximumSize.Height); + if (newH != stretchedGO.Slot.Height) { + stretchedGO.Slot.Height = newH; + stretchedGO.IsDirty = true; +#if DEBUG_LAYOUTING + Debug.WriteLine ("\tAdjusting Height of " + stretchedGO.ToString()); +#endif + stretchedGO.LayoutChanged -= OnChildLayoutChanges; + stretchedGO.OnLayoutChanges (LayoutingType.Height); + stretchedGO.LayoutChanged += OnChildLayoutChanges; + stretchedGO.LastSlots.Height = stretchedGO.Slot.Height; + } + } + + if (Height == Measure.Fit) + this.RegisterForLayouting (LayoutingType.Height); + + this.RegisterForLayouting (LayoutingType.ArrangeChildren); + return; + } + break; + } + base.OnChildLayoutChanges (sender, arg); + } + #endregion + + + } +} diff --git a/src/GraphicObjects/Base/GraphicObject.cs b/src/GraphicObjects/Base/GraphicObject.cs new file mode 100644 index 00000000..2afbe336 --- /dev/null +++ b/src/GraphicObjects/Base/GraphicObject.cs @@ -0,0 +1,1555 @@ +// +// GraphicObject.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Generic; +using System.ComponentModel; +using System.Xml.Serialization; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using Cairo; +using System.Linq; +using System.Diagnostics; +using System.IO; + +namespace Crow +{ + public class GraphicObject : IXmlSerializable, ILayoutable, IValueChange, ICloneable + { + internal static ulong currentUid = 0; + internal ulong uid = 0; + + Interface currentInterface = null; + + [XmlIgnore]public Interface CurrentInterface { + get { + if (currentInterface == null) { + currentInterface = Interface.CurrentInterface; + Initialize (); + } + return currentInterface; + } + set { + currentInterface = value; + } + } + + Rectangles clipping = new Rectangles(); + public Rectangles Clipping { get { return clipping; }} + + #region IValueChange implementation + public event EventHandler<ValueChangeEventArgs> ValueChanged; + public virtual void NotifyValueChanged(string MemberName, object _value) + { + //Debug.WriteLine ("Value changed: {0}->{1} = {2}", this, MemberName, _value); + ValueChanged.Raise(this, new ValueChangeEventArgs(MemberName, _value)); + } + #endregion + + #region CTOR + public GraphicObject () + { + #if DEBUG + uid = currentUid; + currentUid++; + #endif + } + #endregion + + /// <summary> + /// Initialize this Graphic object instance by setting style and default values and loading template if required + /// </summary> + public virtual void Initialize(){ + if (currentInterface == null) + currentInterface = Interface.CurrentInterface; + loadDefaultValues (); + } + #region private fields + LayoutingType registeredLayoutings = LayoutingType.All; + ILayoutable logicalParent; + ILayoutable parent; + string name; + Fill background = Color.Transparent; + Fill foreground = Color.White; + Font font = "droid, 10"; + Measure width, height; + int left, top; + double cornerRadius = 0; + int margin = 0; + bool focusable = false; + bool hasFocus = false; + bool isActive = false; + bool mouseRepeat; + protected bool isVisible = true; + bool isEnabled = true; + VerticalAlignment verticalAlignment = VerticalAlignment.Center; + HorizontalAlignment horizontalAlignment = HorizontalAlignment.Center; + Size maximumSize = "0,0"; + Size minimumSize = "0,0"; + bool cacheEnabled = false; + bool clipToClientRect = true; + protected object dataSource; + string style; + object tag; + #endregion + + #region public fields + /// <summary> + /// Current size and position computed during layouting pass + /// </summary> + public Rectangle Slot = new Rectangle (); + /// <summary> + /// keep last slot components for each layouting pass to track + /// changes and trigger update of other component accordingly + /// </summary> + public Rectangle LastSlots; + /// <summary> + /// keep last slot painted on screen to clear traces if moved or resized + /// TODO: we should ensure the whole parsed widget tree is the last painted + /// version to clear effective oldslot if parents have been moved or resized. + /// IDEA is to add a ScreenCoordinates function that use only lastPaintedSlots + /// </summary> + public Rectangle LastPaintedSlot; + /// <summary>Prevent requeuing multiple times the same widget</summary> + public bool IsQueueForRedraw = false; + /// <summary>drawing Cache, if null, a redraw is done, cached or not</summary> + public byte[] bmp; + public bool IsDirty = true; + /// <summary> + /// This size is computed on each child' layout changes. + /// In stacking widget, it is used to compute the remaining space for the stretched + /// widget inside the stack, which is never added to the contentSize, instead, its size + /// is deducted from (parent.ClientRectangle - contentSize) + /// </summary> + internal Size contentSize; + #endregion + + #region ILayoutable + [XmlIgnore]public LayoutingType RegisteredLayoutings { get { return registeredLayoutings; } set { registeredLayoutings = value; } } + //TODO: it would save the recurent cost of a cast in event bubbling if parent type was GraphicObject + // or we could add to the interface the mouse events + /// <summary> + /// Parent in the graphic tree, used for rendering and layouting + /// </summary> + [XmlIgnore]public virtual ILayoutable Parent { + get { return parent; } + set { + if (parent == value) + return; + DataSourceChangeEventArgs e = new DataSourceChangeEventArgs (parent, value); + lock (this) + parent = value; + + onParentChanged (this, e); + } + } + [XmlIgnore]public ILayoutable LogicalParent { + get { return logicalParent == null ? Parent : logicalParent; } + set { + if (logicalParent == value) + return; + if (logicalParent != null) + (logicalParent as GraphicObject).DataSourceChanged -= onLogicalParentDataSourceChanged; + DataSourceChangeEventArgs dsce = new DataSourceChangeEventArgs (LogicalParent, null); + logicalParent = value; + dsce.NewDataSource = LogicalParent; + if (logicalParent != null) + (logicalParent as GraphicObject).DataSourceChanged += onLogicalParentDataSourceChanged; + onLogicalParentChanged (this, dsce); + } + } + [XmlIgnore]public virtual Rectangle ClientRectangle { + get { + Rectangle cb = Slot.Size; + cb.Inflate ( - Margin); + return cb; + } + } + public virtual Rectangle ContextCoordinates(Rectangle r){ + GraphicObject go = Parent as GraphicObject; + if (go == null) + return r + Parent.ClientRectangle.Position; + return go.CacheEnabled ? + r + Parent.ClientRectangle.Position : + Parent.ContextCoordinates (r); + } + public virtual Rectangle ScreenCoordinates (Rectangle r){ + return + Parent.ScreenCoordinates(r) + Parent.getSlot().Position + Parent.ClientRectangle.Position; + } + public virtual Rectangle getSlot () { return Slot;} + #endregion + + #region EVENT HANDLERS + public event EventHandler<MouseWheelEventArgs> MouseWheelChanged; + public event EventHandler<MouseButtonEventArgs> MouseUp; + public event EventHandler<MouseButtonEventArgs> MouseDown; + public event EventHandler<MouseButtonEventArgs> MouseClick; + public event EventHandler<MouseButtonEventArgs> MouseDoubleClick; + public event EventHandler<MouseMoveEventArgs> MouseMove; + public event EventHandler<MouseMoveEventArgs> MouseEnter; + public event EventHandler<MouseMoveEventArgs> MouseLeave; + public event EventHandler<KeyboardKeyEventArgs> KeyDown; + public event EventHandler<KeyboardKeyEventArgs> KeyUp; + public event EventHandler<KeyPressEventArgs> KeyPress; + public event EventHandler Focused; + public event EventHandler Unfocused; + public event EventHandler Enabled; + public event EventHandler Disabled; + public event EventHandler<LayoutingEventArgs> LayoutChanged; + public event EventHandler<DataSourceChangeEventArgs> DataSourceChanged; + public event EventHandler<DataSourceChangeEventArgs> ParentChanged; + public event EventHandler<DataSourceChangeEventArgs> LogicalParentChanged; + #endregion + + #region public properties + /// <summary>Random value placeholder</summary> + [XmlAttributeAttribute] + public object Tag { + get { return tag; } + set { + if (tag == value) + return; + tag = value; + NotifyValueChanged ("Tag", tag); + } + } + /// <summary> + /// If enabled, resulting bitmap of graphic object is cached in an byte array + /// speeding up rendering of complex object. Default is enabled. + /// </summary> + [XmlAttributeAttribute][DefaultValue(true)] + public virtual bool CacheEnabled { + get { return cacheEnabled; } + set { + if (cacheEnabled == value) + return; + cacheEnabled = value; + NotifyValueChanged ("CacheEnabled", cacheEnabled); + } + } + /// <summary> + /// If true, rendering of GraphicObject is clipped inside client rectangle + /// </summary> + [XmlAttributeAttribute][DefaultValue(true)] + public virtual bool ClipToClientRect { + get { return clipToClientRect; } + set { + if (clipToClientRect == value) + return; + clipToClientRect = value; + NotifyValueChanged ("ClipToClientRect", clipToClientRect); + this.RegisterForRedraw (); + } + } + /// <summary> + /// Name is used in binding to reference other GraphicObjects inside the graphic tree + /// </summary> + [XmlAttributeAttribute][DefaultValue(null)] + public virtual string Name { + get { + #if DEBUG + return string.IsNullOrEmpty(name) ? this.GetType().Name + uid.ToString () : name; + #else + return name; + #endif + } + set { + if (name == value) + return; + name = value; + NotifyValueChanged("Name", name); + } + } + [XmlAttributeAttribute ()][DefaultValue(VerticalAlignment.Center)] + public virtual VerticalAlignment VerticalAlignment { + get { return verticalAlignment; } + set { + if (verticalAlignment == value) + return; + + verticalAlignment = value; + NotifyValueChanged("VerticalAlignment", verticalAlignment); + RegisterForLayouting (LayoutingType.Y); + } + } + [XmlAttributeAttribute()][DefaultValue(HorizontalAlignment.Center)] + public virtual HorizontalAlignment HorizontalAlignment { + get { return horizontalAlignment; } + set { + if (horizontalAlignment == value) + return; + horizontalAlignment = value; + NotifyValueChanged("HorizontalAlignment", horizontalAlignment); + RegisterForLayouting (LayoutingType.X); + } + } + [XmlAttributeAttribute()][DefaultValue(0)] + public virtual int Left { + get { return left; } + set { + if (left == value) + return; + left = value; + NotifyValueChanged ("Left", left); + this.RegisterForLayouting (LayoutingType.X); + } + } + [XmlAttributeAttribute()][DefaultValue(0)] + public virtual int Top { + get { return top; } + set { + if (top == value) + return; + top = value; + NotifyValueChanged ("Top", top); + this.RegisterForLayouting (LayoutingType.Y); + } + } + /// <summary> + /// When set to True, the <see cref="T:Crow.GraphicObject"/>'s width and height will be set to Fit. + /// </summary> + [XmlAttributeAttribute()][DefaultValue(false)] + public virtual bool Fit { + get { return Width == Measure.Fit && Height == Measure.Fit ? true : false; } + set { + if (value == Fit) + return; + + Width = Height = Measure.Fit; + } + } + [XmlAttributeAttribute()][DefaultValue("Inherit")] + public virtual Measure Width { + get { + return width.Units == Unit.Inherit ? + Parent is GraphicObject ? (Parent as GraphicObject).WidthPolicy : + Measure.Stretched : width; + } + set { + if (width == value) + return; + if (value.IsFixed) { + if (value < MinimumSize.Width || (value > MaximumSize.Width && MaximumSize.Width > 0)) + return; + } + Measure lastWP = WidthPolicy; + width = value; + NotifyValueChanged ("Width", width); + if (WidthPolicy != lastWP) { + NotifyValueChanged ("WidthPolicy", WidthPolicy); + //contentSize in Stacks are only update on childLayoutChange, and the single stretched + //child of the stack is not counted in contentSize, so when changing size policy of a child + //we should adapt contentSize + //TODO:check case when child become stretched, and another stretched item already exists. + if (parent is GenericStack) {//TODO:check if I should test Group instead + if ((parent as GenericStack).Orientation == Orientation.Horizontal) { + if (lastWP == Measure.Fit) + (parent as GenericStack).contentSize.Width -= this.LastSlots.Width; + else + (parent as GenericStack).contentSize.Width += this.LastSlots.Width; + } + } + } + + this.RegisterForLayouting (LayoutingType.Width); + } + } + [XmlAttributeAttribute()][DefaultValue("Inherit")] + public virtual Measure Height { + get { + return height.Units == Unit.Inherit ? + Parent is GraphicObject ? (Parent as GraphicObject).HeightPolicy : + Measure.Stretched : height; + } + set { + if (height == value) + return; + if (value.IsFixed) { + if (value < MinimumSize.Height || (value > MaximumSize.Height && MaximumSize.Height > 0)) + return; + } + Measure lastHP = HeightPolicy; + height = value; + NotifyValueChanged ("Height", height); + if (HeightPolicy != lastHP) { + NotifyValueChanged ("HeightPolicy", HeightPolicy); + if (parent is GenericStack) { + if ((parent as GenericStack).Orientation == Orientation.Vertical) { + if (lastHP == Measure.Fit) + (parent as GenericStack).contentSize.Height -= this.LastSlots.Height; + else + (parent as GenericStack).contentSize.Height += this.LastSlots.Height; + } + } + } + + this.RegisterForLayouting (LayoutingType.Height); + } + } + /// <summary> + /// Used for binding on dimensions, this property will never hold fixed size, but instead only + /// Fit or Stretched + /// </summary> + [XmlIgnore]public virtual Measure WidthPolicy { get { + return Width.IsFit ? Measure.Fit : Measure.Stretched; } } + /// <summary> + /// Used for binding on dimensions, this property will never hold fixed size, but instead only + /// Fit or Stretched + /// </summary> + [XmlIgnore]public virtual Measure HeightPolicy { get { + return Height.IsFit ? Measure.Fit : Measure.Stretched; } } + [XmlAttributeAttribute()][DefaultValue(false)] + public virtual bool Focusable { + get { return focusable; } + set { + if (focusable == value) + return; + focusable = value; + NotifyValueChanged ("Focusable", focusable); + } + } + [XmlIgnore]public virtual bool HasFocus { + get { return hasFocus; } + set { + if (value == hasFocus) + return; + + hasFocus = value; + if (hasFocus) + onFocused (this, null); + else + onUnfocused (this, null); + NotifyValueChanged ("HasFocus", hasFocus); + } + } + [XmlIgnore]public virtual bool IsActive { + get { return isActive; } + set { + if (value == isActive) + return; + + isActive = value; + NotifyValueChanged ("IsActive", isActive); + } + } + [XmlAttributeAttribute()][DefaultValue(false)] + public virtual bool MouseRepeat { + get { return mouseRepeat; } + set { + if (mouseRepeat == value) + return; + mouseRepeat = value; + NotifyValueChanged ("MouseRepeat", mouseRepeat); + } + } + bool clearBackground = false; + [XmlAttributeAttribute()][DefaultValue("Transparent")] + public virtual Fill Background { + get { return background; } + set { + if (background == value) + return; + clearBackground = false; + if (value == null) + return; + background = value; + NotifyValueChanged ("Background", background); + RegisterForRedraw (); + if (background is SolidColor) { + if ((Background as SolidColor).Equals (Color.Clear)) + clearBackground = true; + } + } + } + [XmlAttributeAttribute()][DefaultValue("White")] + public virtual Fill Foreground { + get { return foreground; } + set { + if (foreground == value) + return; + foreground = value; + NotifyValueChanged ("Foreground", foreground); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute()][DefaultValue("sans,10")] + public virtual Font Font { + get { return font; } + set { + if (value == font) + return; + font = value; + NotifyValueChanged ("Font", font); + RegisterForGraphicUpdate (); + } + } + [XmlAttributeAttribute()][DefaultValue(0.0)] + public virtual double CornerRadius { + get { return cornerRadius; } + set { + if (value == cornerRadius) + return; + cornerRadius = value; + NotifyValueChanged ("CornerRadius", cornerRadius); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute()][DefaultValue(0)] + public virtual int Margin { + get { return margin; } + set { + if (value == margin) + return; + margin = value; + NotifyValueChanged ("Margin", margin); + RegisterForGraphicUpdate (); + } + } + [XmlAttributeAttribute][DefaultValue(true)] + public virtual bool Visible { + get { return isVisible; } + set { + if (value == isVisible) + return; + + isVisible = value; + + if (isVisible) + RegisterForLayouting (LayoutingType.Sizing); + else { + lock (CurrentInterface.UpdateMutex) { + Slot.Width = 0; + LayoutChanged.Raise (this, new LayoutingEventArgs (LayoutingType.Width)); + Slot.Height = 0; + LayoutChanged.Raise (this, new LayoutingEventArgs (LayoutingType.Height)); + if (this.parent != null) + CurrentInterface.EnqueueForRepaint (this); + LastSlots.Width = LastSlots.Height = 0; + } + } + + //trigger a mouse to handle possible hover changes + CurrentInterface.ProcessMouseMove (CurrentInterface.Mouse.X, CurrentInterface.Mouse.Y); + + NotifyValueChanged ("Visible", isVisible); + } + } + [XmlAttributeAttribute][DefaultValue(true)] + public virtual bool IsEnabled { + get { return isEnabled; } + set { + if (value == isEnabled) + return; + + isEnabled = value; + + if (isEnabled) + onEnable (this, null); + else + onDisable (this, null); + + NotifyValueChanged ("IsEnabled", isEnabled); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute()][DefaultValue("1,1")] + public virtual Size MinimumSize { + get { return minimumSize; } + set { + if (value == minimumSize) + return; + + minimumSize = value; + + NotifyValueChanged ("MinimumSize", minimumSize); + RegisterForLayouting (LayoutingType.Sizing); + } + } + [XmlAttributeAttribute()][DefaultValue("0,0")] + public virtual Size MaximumSize { + get { return maximumSize; } + set { + if (value == maximumSize) + return; + + maximumSize = value; + + NotifyValueChanged ("MaximumSize", maximumSize); + RegisterForLayouting (LayoutingType.Sizing); + } + } + /// <summary> + /// Seek first logical tree upward if logicalParent is set, or seek graphic tree for + /// a not null dataSource that will be active for all descendants having dataSource=null + /// </summary> + [XmlAttributeAttribute][DefaultValue(null)] + public virtual object DataSource { + set { + if (DataSource == value) + return; + + DataSourceChangeEventArgs dse = new DataSourceChangeEventArgs (DataSource, null); + dataSource = value; + dse.NewDataSource = DataSource; + + OnDataSourceChanged (this, dse); + + NotifyValueChanged ("DataSource", DataSource); + } + get { + return dataSource == null ? + LogicalParent == null ? null : + LogicalParent is GraphicObject ? (LogicalParent as GraphicObject).DataSource : null : + dataSource; + } + } + protected virtual void onLogicalParentDataSourceChanged(object sender, DataSourceChangeEventArgs e){ + if (localDataSourceIsNull) + OnDataSourceChanged (this, e); + } + internal bool localDataSourceIsNull { get { return dataSource == null; } } + internal bool localLogicalParentIsNull { get { return logicalParent == null; } } + + public virtual void OnDataSourceChanged(object sender, DataSourceChangeEventArgs e){ + DataSourceChanged.Raise (this, e); + #if DEBUG_BINDING + Debug.WriteLine("New DataSource for => {0} \n\t{1}=>{2}", this.ToString(),e.OldDataSource,e.NewDataSource); + #endif + } + + [XmlAttributeAttribute] + public virtual string Style { + get { return style; } + set { + if (value == style) + return; + + style = value; + + NotifyValueChanged ("Style", style); + } + } + #endregion + + #region Default and Style Values loading + /// <summary> Loads the default values from XML attributes default </summary> + public void loadDefaultValues() + { + #if DEBUG_LOAD + Debug.WriteLine ("LoadDefValues for " + this.ToString ()); + #endif + + Type thisType = this.GetType (); + + if (!string.IsNullOrEmpty (Style)) { + if (Interface.DefaultValuesLoader.ContainsKey (Style)) { + Interface.DefaultValuesLoader [Style] (this); + return; + } + } else { + if (Interface.DefaultValuesLoader.ContainsKey (thisType.FullName)) { + Interface.DefaultValuesLoader [thisType.FullName] (this); + return; + } else if (!Interface.Styling.ContainsKey (thisType.FullName)) { + if (Interface.DefaultValuesLoader.ContainsKey (thisType.Name)) { + Interface.DefaultValuesLoader [thisType.Name] (this); + return; + } + } + } + + List<Style> styling = new List<Style>(); + + //Search for a style matching : + //1: Full class name, with full namespace + //2: class name + //3: style may have been registered with their ressource ID minus .style extention + // those files being placed in a Styles folder + string styleKey = Style; + if (!string.IsNullOrEmpty (Style)) { + if (Interface.Styling.ContainsKey (Style)) { + styling.Add (Interface.Styling [Style]); + } + } + if (Interface.Styling.ContainsKey (thisType.FullName)) { + styling.Add (Interface.Styling [thisType.FullName]); + if (string.IsNullOrEmpty (styleKey)) + styleKey = thisType.FullName; + } + if (Interface.Styling.ContainsKey (thisType.Name)) { + styling.Add (Interface.Styling [thisType.Name]); + if (string.IsNullOrEmpty (styleKey)) + styleKey = thisType.Name; + } + + if (string.IsNullOrEmpty (styleKey)) + styleKey = thisType.FullName; + + + //Reflexion being very slow compared to dyn method or delegates, + //I compile the initial values coded in the CustomAttribs of the class, + //all other instance of this type would not longer use reflexion to init properly + //but will fetch the dynamic initialisation method compiled for this precise type + //TODO:measure speed gain. + #region Delfault values Loading dynamic compilation + DynamicMethod dm = null; + ILGenerator il = null; + + dm = new DynamicMethod("dyn_loadDefValues", + MethodAttributes.Family | MethodAttributes.FamANDAssem | MethodAttributes.NewSlot, + CallingConventions.Standard, + typeof(void),new Type[] {CompilerServices.TObject},thisType,true); + + il = dm.GetILGenerator(256); + il.DeclareLocal(CompilerServices.TObject); + il.Emit(OpCodes.Nop); + //set local GraphicObject to root object passed as 1st argument + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Stloc_0); + + foreach (EventInfo ei in thisType.GetEvents(BindingFlags.Public | BindingFlags.Instance)) { + string expression; + if (!getDefaultEvent(ei, styling, out expression)) + continue; + //TODO:dynEventHandler could be cached somewhere, maybe a style instanciator class holding the styling delegate and bound to it. + foreach (string exp in CompilerServices.splitOnSemiColumnOutsideAccolades(expression)) { + string trimed = exp.Trim(); + if (trimed.StartsWith ("{", StringComparison.OrdinalIgnoreCase)){ + il.Emit (OpCodes.Ldloc_0);//load this as 1st arg of event Add + + //push eventInfo as 1st arg of compile + il.Emit (OpCodes.Ldloc_0); + il.Emit (OpCodes.Call, CompilerServices.miGetType); + il.Emit (OpCodes.Ldstr, ei.Name);//push event name + il.Emit (OpCodes.Call, CompilerServices.miGetEvent); + //push expression as 2nd arg of compile + il.Emit (OpCodes.Ldstr, trimed.Substring (1, trimed.Length - 2)); + //push null as 3rd arg, currentNode, not known when instanciing + il.Emit (OpCodes.Ldnull); + il.Emit (OpCodes.Callvirt, CompilerServices.miCompileDynEventHandler); + il.Emit (OpCodes.Castclass, ei.EventHandlerType); + il.Emit (OpCodes.Callvirt, ei.AddMethod); + }else + Debug.WriteLine("error in styling, event not handled : " + trimed); + } + } + + foreach (PropertyInfo pi in thisType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { + if (pi.GetSetMethod () == null) + continue; + object defaultValue; + if (!getDefaultValue (pi, styling, out defaultValue)) + continue; + + CompilerServices.EmitSetValue (il, pi, defaultValue); + } + il.Emit(OpCodes.Ret); + #endregion + + try { + Interface.DefaultValuesLoader[styleKey] = (Interface.LoaderInvoker)dm.CreateDelegate(typeof(Interface.LoaderInvoker)); + Interface.DefaultValuesLoader[styleKey] (this); + } catch (Exception ex) { + throw new Exception ("Error applying style <" + styleKey + ">:", ex); + } + } + bool getDefaultEvent(EventInfo ei, List<Style> styling, + out string expression){ + expression = ""; + if (styling.Count > 0){ + for (int i = 0; i < styling.Count; i++) { + if (styling[i].ContainsKey (ei.Name)){ + expression = (string)styling[i] [ei.Name]; + return true; + } + } + } + return false; + } + bool getDefaultValue(PropertyInfo pi, List<Style> styling, + out object defaultValue){ + defaultValue = null; + string name = ""; + + XmlIgnoreAttribute xia = (XmlIgnoreAttribute)pi.GetCustomAttribute (typeof(XmlIgnoreAttribute)); + if (xia != null) + return false; + XmlAttributeAttribute xaa = (XmlAttributeAttribute)pi.GetCustomAttribute (typeof(XmlAttributeAttribute)); + if (xaa != null) { + if (string.IsNullOrEmpty (xaa.AttributeName)) + name = pi.Name; + else + name = xaa.AttributeName; + } + + int styleIndex = -1; + if (styling.Count > 0){ + for (int i = 0; i < styling.Count; i++) { + if (styling[i].ContainsKey (name)){ + styleIndex = i; + break; + } + } + } + if (styleIndex >= 0){ + if (pi.PropertyType.IsEnum)//maybe should be in parser.. + defaultValue = Enum.Parse(pi.PropertyType, (string)styling[styleIndex] [name], true); + else + defaultValue = styling[styleIndex] [name]; + }else { + DefaultValueAttribute dv = (DefaultValueAttribute)pi.GetCustomAttribute (typeof (DefaultValueAttribute)); + if (dv == null) + return false; + defaultValue = dv.Value; + } + return true; + } + #endregion + + public virtual GraphicObject FindByName(string nameToFind){ + return string.Equals(nameToFind, name, StringComparison.Ordinal) ? this : null; + } + public virtual bool Contains(GraphicObject goToFind){ + return false; + } + + #region Queuing + /// <summary> + /// Register old and new slot for clipping + /// </summary> + public virtual void ClippingRegistration(){ + IsQueueForRedraw = false; + if (Parent == null) + return; + Parent.RegisterClip (LastPaintedSlot); + Parent.RegisterClip (Slot); + } + /// <summary> + /// Add clip rectangle to this.clipping and propagate up to root + /// </summary> + /// <param name="clip">Clip rectangle</param> + public virtual void RegisterClip(Rectangle clip){ + if (CacheEnabled && !IsDirty) + Clipping.AddRectangle (clip + ClientRectangle.Position); + if (Parent == null) + return; + GraphicObject p = Parent as GraphicObject; + if (p?.IsDirty == true && p?.CacheEnabled == true) + return; + Parent.RegisterClip (clip + Slot.Position + ClientRectangle.Position); + } + /// <summary> Full update, taking care of sizing policy </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RegisterForGraphicUpdate () + { + IsDirty = true; + if (Width.IsFit || Height.IsFit) + RegisterForLayouting (LayoutingType.Sizing); + else if (RegisteredLayoutings == LayoutingType.None) + CurrentInterface.EnqueueForRepaint (this); + } + /// <summary> query an update of the content, a redraw </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RegisterForRedraw () + { + IsDirty = true; + if (RegisteredLayoutings == LayoutingType.None) + CurrentInterface.EnqueueForRepaint (this); + } + #endregion + + #region Layouting + + /// <summary> return size of content + margins </summary> + protected virtual int measureRawSize (LayoutingType lt) { + return lt == LayoutingType.Width ? + contentSize.Width + 2 * Margin: contentSize.Height + 2 * Margin; + } + /// <summary> By default in groups, LayoutingType.ArrangeChildren is reset </summary> + public virtual void ChildrenLayoutingConstraints(ref LayoutingType layoutType){ + } + public virtual bool ArrangeChildren { get { return false; } } + public virtual void RegisterForLayouting(LayoutingType layoutType){ + if (Parent == null) + return; + lock (CurrentInterface.LayoutMutex) { + //prevent queueing same LayoutingType for this + layoutType &= (~RegisteredLayoutings); + + if (layoutType == LayoutingType.None) + return; + //dont set position for stretched item + if (Width == Measure.Stretched) + layoutType &= (~LayoutingType.X); + if (Height == Measure.Stretched) + layoutType &= (~LayoutingType.Y); + + if (!ArrangeChildren) + layoutType &= (~LayoutingType.ArrangeChildren); + + //apply constraints depending on parent type + if (Parent is GraphicObject) + (Parent as GraphicObject).ChildrenLayoutingConstraints (ref layoutType); + +// //prevent queueing same LayoutingType for this +// layoutType &= (~RegisteredLayoutings); + + if (layoutType == LayoutingType.None) + return; + + //enqueue LQI LayoutingTypes separately + if (layoutType.HasFlag (LayoutingType.Width)) + CurrentInterface.LayoutingQueue.Enqueue (new LayoutingQueueItem (LayoutingType.Width, this)); + if (layoutType.HasFlag (LayoutingType.Height)) + CurrentInterface.LayoutingQueue.Enqueue (new LayoutingQueueItem (LayoutingType.Height, this)); + if (layoutType.HasFlag (LayoutingType.X)) + CurrentInterface.LayoutingQueue.Enqueue (new LayoutingQueueItem (LayoutingType.X, this)); + if (layoutType.HasFlag (LayoutingType.Y)) + CurrentInterface.LayoutingQueue.Enqueue (new LayoutingQueueItem (LayoutingType.Y, this)); + if (layoutType.HasFlag (LayoutingType.ArrangeChildren)) + CurrentInterface.LayoutingQueue.Enqueue (new LayoutingQueueItem (LayoutingType.ArrangeChildren, this)); + } + } + + /// <summary> trigger dependant sizing component update </summary> + public virtual void OnLayoutChanges(LayoutingType layoutType) + { + #if DEBUG_LAYOUTING + CurrentInterface.currentLQI.Slot = LastSlots; + CurrentInterface.currentLQI.NewSlot = Slot; + Debug.WriteLine ("\t\t{0} => {1}",LastSlots,Slot); + #endif + + switch (layoutType) { + case LayoutingType.Width: + RegisterForLayouting (LayoutingType.X); + break; + case LayoutingType.Height: + RegisterForLayouting (LayoutingType.Y); + break; + } + LayoutChanged.Raise (this, new LayoutingEventArgs (layoutType)); + } + internal protected void raiseLayoutChanged(LayoutingEventArgs e){ + LayoutChanged.Raise (this, e); + } + /// <summary> Update layout component only one at a time, this is where the computation of alignement + /// and size take place. + /// The redrawing will only be triggered if final slot size has changed </summary> + /// <returns><c>true</c>, if layouting was possible, <c>false</c> if conditions were not + /// met and LQI has to be re-queued</returns> + public virtual bool UpdateLayout (LayoutingType layoutType) + { + //unset bit, it would be reset if LQI is re-queued + registeredLayoutings &= (~layoutType); + + switch (layoutType) { + case LayoutingType.X: + if (Left == 0) { + + if (Parent.RegisteredLayoutings.HasFlag (LayoutingType.Width) || + RegisteredLayoutings.HasFlag (LayoutingType.Width)) + return false; + + switch (HorizontalAlignment) { + case HorizontalAlignment.Left: + Slot.X = 0; + break; + case HorizontalAlignment.Right: + Slot.X = Parent.ClientRectangle.Width - Slot.Width; + break; + case HorizontalAlignment.Center: + Slot.X = Parent.ClientRectangle.Width / 2 - Slot.Width / 2; + break; + } + } else + Slot.X = Left; + + if (LastSlots.X == Slot.X) + break; + + IsDirty = true; + + OnLayoutChanges (layoutType); + + LastSlots.X = Slot.X; + break; + case LayoutingType.Y: + if (Top == 0) { + + if (Parent.RegisteredLayoutings.HasFlag (LayoutingType.Height) || + RegisteredLayoutings.HasFlag (LayoutingType.Height)) + return false; + + switch (VerticalAlignment) { + case VerticalAlignment.Top://this could be processed even if parent Height is not known + Slot.Y = 0; + break; + case VerticalAlignment.Bottom: + Slot.Y = Parent.ClientRectangle.Height - Slot.Height; + break; + case VerticalAlignment.Center: + Slot.Y = Parent.ClientRectangle.Height / 2 - Slot.Height / 2; + break; + } + } else + Slot.Y = Top; + + if (LastSlots.Y == Slot.Y) + break; + + IsDirty = true; + + OnLayoutChanges (layoutType); + + LastSlots.Y = Slot.Y; + break; + case LayoutingType.Width: + if (Visible) { + if (Width.IsFixed) + Slot.Width = Width; + else if (Width == Measure.Fit) { + Slot.Width = measureRawSize (LayoutingType.Width); + } else if (Parent.RegisteredLayoutings.HasFlag (LayoutingType.Width)) + return false; + else if (Width == Measure.Stretched) + Slot.Width = Parent.ClientRectangle.Width; + else + Slot.Width = (int)Math.Round ((double)(Parent.ClientRectangle.Width * Width) / 100.0); + + if (Slot.Width < 0) + return false; + + //size constrain + if (Slot.Width < MinimumSize.Width) { + Slot.Width = MinimumSize.Width; + //NotifyValueChanged ("WidthPolicy", Measure.Stretched); + } else if (Slot.Width > MaximumSize.Width && MaximumSize.Width > 0) { + Slot.Width = MaximumSize.Width; + //NotifyValueChanged ("WidthPolicy", Measure.Stretched); + } + } else + Slot.Width = 0; + + if (LastSlots.Width == Slot.Width) + break; + + IsDirty = true; + + OnLayoutChanges (layoutType); + + LastSlots.Width = Slot.Width; + break; + case LayoutingType.Height: + if (Visible) { + if (Height.IsFixed) + Slot.Height = Height; + else if (Height == Measure.Fit) { + Slot.Height = measureRawSize (LayoutingType.Height); + } else if (Parent.RegisteredLayoutings.HasFlag (LayoutingType.Height)) + return false; + else if (Height == Measure.Stretched) + Slot.Height = Parent.ClientRectangle.Height; + else + Slot.Height = (int)Math.Round ((double)(Parent.ClientRectangle.Height * Height) / 100.0); + + if (Slot.Height < 0) + return false; + + //size constrain + if (Slot.Height < MinimumSize.Height) { + Slot.Height = MinimumSize.Height; + //NotifyValueChanged ("HeightPolicy", Measure.Stretched); + } else if (Slot.Height > MaximumSize.Height && MaximumSize.Height > 0) { + Slot.Height = MaximumSize.Height; + //NotifyValueChanged ("HeightPolicy", Measure.Stretched); + } + } else + Slot.Height = 0; + + if (LastSlots.Height == Slot.Height) + break; + + IsDirty = true; + + OnLayoutChanges (layoutType); + + LastSlots.Height = Slot.Height; + break; + } + + //if no layouting remains in queue for item, registre for redraw + if (this.registeredLayoutings == LayoutingType.None && IsDirty) + CurrentInterface.EnqueueForRepaint (this); + + return true; + } + #endregion + + #region Rendering + /// <summary> This is the common overridable drawing routine to create new widget </summary> + protected virtual void onDraw(Context gr) + { + Rectangle rBack = new Rectangle (Slot.Size); + + Background.SetAsSource (gr, rBack); + CairoHelpers.CairoRectangle(gr,rBack,cornerRadius); + gr.Fill (); + } + + /// <summary> + /// Internal drawing context creation on a cached surface limited to slot size + /// this trigger the effective drawing routine </summary> + protected virtual void RecreateCache () + { + int stride = 4 * Slot.Width; + + int bmpSize = Math.Abs (stride) * Slot.Height; + bmp = new byte[bmpSize]; + IsDirty = false; + using (ImageSurface draw = + new ImageSurface(bmp, Format.Argb32, Slot.Width, Slot.Height, stride)) { + using (Context gr = new Context (draw)) { + gr.Antialias = Interface.Antialias; + onDraw (gr); + } + draw.Flush (); + } + } + protected virtual void UpdateCache(Context ctx){ + Rectangle rb = Slot + Parent.ClientRectangle.Position; + using (ImageSurface cache = new ImageSurface (bmp, Format.Argb32, Slot.Width, Slot.Height, 4 * Slot.Width)) { + if (clearBackground) { + ctx.Save (); + ctx.Operator = Operator.Clear; + ctx.Rectangle (rb); + ctx.Fill (); + ctx.Restore (); + } + ctx.SetSourceSurface (cache, rb.X, rb.Y); + ctx.Paint (); + } + //Clipping.clearAndClip (ctx); + Clipping.Reset(); + } + /// <summary> Chained painting routine on the parent context of the actual cached version + /// of the widget </summary> + public virtual void Paint (ref Context ctx) + { + //TODO:this test should not be necessary + if (Slot.Height < 0 || Slot.Width < 0 || parent == null) + return; + lock (this) { + if (cacheEnabled) { + if (Slot.Width > Interface.MaxCacheSize || Slot.Height > Interface.MaxCacheSize) + cacheEnabled = false; + } + + if (cacheEnabled) { + if (IsDirty) + RecreateCache (); + + UpdateCache (ctx); + if (!isEnabled) + paintDisabled (ctx, Slot + Parent.ClientRectangle.Position); + } else { + Rectangle rb = Slot + Parent.ClientRectangle.Position; + ctx.Save (); + + ctx.Translate (rb.X, rb.Y); + + onDraw (ctx); + if (!isEnabled) + paintDisabled (ctx, Slot); + + ctx.Restore (); + } + LastPaintedSlot = Slot; + } + } + void paintDisabled(Context gr, Rectangle rb){ + gr.Operator = Operator.Xor; + gr.SetSourceRGBA (0.6, 0.6, 0.6, 0.3); + gr.Rectangle (rb); + gr.Fill (); + gr.Operator = Operator.Over; + } + #endregion + + #region Keyboard handling + public virtual void onKeyDown(object sender, KeyboardKeyEventArgs e){ + KeyDown.Raise (sender, e); + } + public virtual void onKeyUp(object sender, KeyboardKeyEventArgs e){ + KeyUp.Raise (sender, e); + } + public virtual void onKeyPress(object sender, KeyPressEventArgs e){ + KeyPress.Raise (sender, e); + } + #endregion + + #region Mouse handling + public virtual bool MouseIsIn(Point m) + { + try { + if (!(Visible & isEnabled)) + return false; + if (ScreenCoordinates (Slot).ContainsOrIsEqual (m)) { + Scroller scr = Parent as Scroller; + if (scr == null) { + if (Parent is GraphicObject) + return (Parent as GraphicObject).MouseIsIn (m); + else return true; + } + return scr.MouseIsIn (scr.savedMousePos); + } + } catch (Exception ex) { + return false; + } + return false; + } + public virtual void checkHoverWidget(MouseMoveEventArgs e) + { + if (CurrentInterface.HoverWidget != this) { + CurrentInterface.HoverWidget = this; + onMouseEnter (this, e); + } + + this.onMouseMove (this, e);//without this, window border doesn't work, should be removed + } + public virtual void onMouseMove(object sender, MouseMoveEventArgs e) + { + //bubble event to the top + GraphicObject p = Parent as GraphicObject; + if (p != null) + p.onMouseMove(sender,e); + + MouseMove.Raise (this, e); + } + public virtual void onMouseDown(object sender, MouseButtonEventArgs e){ + if (CurrentInterface.eligibleForDoubleClick == this && CurrentInterface.clickTimer.ElapsedMilliseconds < Interface.DoubleClick) + onMouseDoubleClick (this, e); + else + currentInterface.clickTimer.Restart(); + CurrentInterface.eligibleForDoubleClick = null; + + if (CurrentInterface.ActiveWidget == null) + CurrentInterface.ActiveWidget = this; + if (this.Focusable && !Interface.FocusOnHover) { + BubblingMouseButtonEventArg be = e as BubblingMouseButtonEventArg; + if (be.Focused == null) { + be.Focused = this; + CurrentInterface.FocusedWidget = this; + } + } + //bubble event to the top + GraphicObject p = Parent as GraphicObject; + if (p != null) + p.onMouseDown(sender,e); + + MouseDown.Raise (this, e); + } + public virtual void onMouseUp(object sender, MouseButtonEventArgs e){ + //bubble event to the top + GraphicObject p = Parent as GraphicObject; + if (p != null) + p.onMouseUp(sender,e); + + MouseUp.Raise (this, e); + + if (MouseIsIn (e.Position) && IsActive) { + if (CurrentInterface.clickTimer.ElapsedMilliseconds < Interface.DoubleClick) + CurrentInterface.eligibleForDoubleClick = this; + onMouseClick (this, e); + } + } + public virtual void onMouseClick(object sender, MouseButtonEventArgs e){ + GraphicObject p = Parent as GraphicObject; + if (p != null) + p.onMouseClick(sender,e); + MouseClick.Raise (this, e); + } + public virtual void onMouseDoubleClick(object sender, MouseButtonEventArgs e){ + GraphicObject p = Parent as GraphicObject; + if (p != null) + p.onMouseDoubleClick(sender,e); + MouseDoubleClick.Raise (this, e); + } + public virtual void onMouseWheel(object sender, MouseWheelEventArgs e){ + GraphicObject p = Parent as GraphicObject; + if (p != null) + p.onMouseWheel(sender,e); + + MouseWheelChanged.Raise (this, e); + } + public virtual void onMouseEnter(object sender, MouseMoveEventArgs e) + { + #if DEBUG_FOCUS + Debug.WriteLine("MouseEnter => " + this.ToString()); + #endif + MouseEnter.Raise (this, e); + } + public virtual void onMouseLeave(object sender, MouseMoveEventArgs e) + { + #if DEBUG_FOCUS + Debug.WriteLine("MouseLeave => " + this.ToString()); + #endif + MouseLeave.Raise (this, e); + } + #endregion + + protected virtual void onFocused(object sender, EventArgs e){ + #if DEBUG_FOCUS + Debug.WriteLine("Focused => " + this.ToString()); + #endif + Focused.Raise (this, e); + } + protected virtual void onUnfocused(object sender, EventArgs e){ + #if DEBUG_FOCUS + Debug.WriteLine("UnFocused => " + this.ToString()); + #endif + Unfocused.Raise (this, e); + } + public virtual void onEnable(object sender, EventArgs e){ + Enabled.Raise (this, e); + } + public virtual void onDisable(object sender, EventArgs e){ + Disabled.Raise (this, e); + } + protected virtual void onParentChanged(object sender, DataSourceChangeEventArgs e) { + ParentChanged.Raise (this, e); + if (logicalParent == null) + LogicalParentChanged.Raise (this, e); + } + protected virtual void onLogicalParentChanged(object sender, DataSourceChangeEventArgs e) { + LogicalParentChanged.Raise (this, e); + } + #region IXmlSerializable + public virtual System.Xml.Schema.XmlSchema GetSchema () + { + return null; + } +// void affectMember(string name, string value){ +// Type thisType = this.GetType (); +// +// if (string.IsNullOrEmpty (value)) +// return; +// +// MemberInfo mi = thisType.GetMember (name).FirstOrDefault(); +// if (mi == null) { +// Debug.WriteLine ("XML: Unknown attribute in " + thisType.ToString() + " : " + name); +// return; +// } +// if (mi.MemberType == MemberTypes.Event) { +// this.Bindings.Add (new Binding (new MemberReference(this, mi), value)); +// return; +// } +// if (mi.MemberType == MemberTypes.Property) { +// PropertyInfo pi = mi as PropertyInfo; +// +// if (pi.GetSetMethod () == null) { +// Debug.WriteLine ("XML: Read only property in " + thisType.ToString() + " : " + name); +// return; +// } +// +// XmlAttributeAttribute xaa = (XmlAttributeAttribute)pi.GetCustomAttribute (typeof(XmlAttributeAttribute)); +// if (xaa != null) { +// if (!string.IsNullOrEmpty (xaa.AttributeName)) +// name = xaa.AttributeName; +// } +// if (value.StartsWith("{",StringComparison.Ordinal)) { +// //binding +// if (!value.EndsWith("}", StringComparison.Ordinal)) +// throw new Exception (string.Format("XML:Malformed binding: {0}", value)); +// +// this.Bindings.Add (new Binding (new MemberReference(this, pi), value.Substring (1, value.Length - 2))); +// return; +// } +// if (pi.GetCustomAttribute (typeof(XmlIgnoreAttribute)) != null) +// return; +// if (xaa == null)//not define as xmlAttribute +// return; +// +// if (pi.PropertyType == typeof(string)) { +// pi.SetValue (this, value, null); +// return; +// } +// +// if (pi.PropertyType.IsEnum) { +// pi.SetValue (this, Enum.Parse (pi.PropertyType, value), null); +// } else { +// MethodInfo me = pi.PropertyType.GetMethod ("Parse", new Type[] { typeof(string) }); +// pi.SetValue (this, me.Invoke (null, new string[] { value }), null); +// } +// } +// } + public virtual void ReadXml (System.Xml.XmlReader reader) + { + if (reader.HasAttributes) { + + style = reader.GetAttribute ("Style"); + + loadDefaultValues (); + + while (reader.MoveToNextAttribute ()) { + if (reader.Name == "Style") + continue; + + //affectMember (reader.Name, reader.Value); + } + reader.MoveToElement (); + }else + loadDefaultValues (); + } + public virtual void WriteXml (System.Xml.XmlWriter writer) + { + foreach (PropertyInfo pi in this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) { + if (pi.GetSetMethod () == null) + continue; + + bool isAttribute = false; + bool hasDefaultValue = false; + bool ignore = false; + string name = ""; + object value = null; + Type valueType = null; + + + MemberInfo mi = pi.GetGetMethod (); + + if (mi == null) + continue; + + value = pi.GetValue (this, null); + valueType = pi.PropertyType; + name = pi.Name; + + + + object[] att = pi.GetCustomAttributes (false); + + foreach (object o in att) { + XmlAttributeAttribute xaa = o as XmlAttributeAttribute; + if (xaa != null) { + isAttribute = true; + if (string.IsNullOrEmpty (xaa.AttributeName)) + name = pi.Name; + else + name = xaa.AttributeName; + continue; + } + + XmlIgnoreAttribute xia = o as XmlIgnoreAttribute; + if (xia != null) { + ignore = true; + continue; + } + + DefaultValueAttribute dv = o as DefaultValueAttribute; + if (dv != null) { + if (dv.Value.Equals (value)) + hasDefaultValue = true; + if (dv.Value.ToString () == value.ToString ()) + hasDefaultValue = true; + + continue; + } + + + } + + if (hasDefaultValue || ignore || value==null) + continue; + + if (isAttribute) + writer.WriteAttributeString (name, value.ToString ()); + else { + if (valueType.GetInterface ("IXmlSerializable") == null) + continue; + + (pi.GetValue (this, null) as IXmlSerializable).WriteXml (writer); + } + } + foreach (EventInfo ei in this.GetType().GetEvents()) { + FieldInfo fi = this.GetType().GetField(ei.Name, + BindingFlags.NonPublic | + BindingFlags.Instance | + BindingFlags.GetField); + + Delegate dg = (System.Delegate)fi.GetValue (this); + + if (dg == null) + continue; + + foreach (Delegate d in dg.GetInvocationList()) { + if (!d.Method.Name.StartsWith ("<"))//Skipping empty handler, not clear it's trikky + writer.WriteAttributeString (ei.Name, d.Method.Name); + } + } + } + #endregion + + #region ICloneable implementation + public object Clone () + { + Type type = this.GetType (); + GraphicObject result = (GraphicObject)Activator.CreateInstance (type); + result.CurrentInterface = CurrentInterface; + + foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { + if (pi.GetSetMethod () == null) + continue; + + if (pi.GetCustomAttribute<XmlIgnoreAttribute> () != null) + continue; + if (pi.Name == "DataSource") + continue; + + pi.SetValue(result, pi.GetValue(this)); + } + return result; + } + #endregion + /// <summary> + /// full GraphicTree clone with binding definition + /// </summary> + + public override string ToString () + { + string tmp =""; + + if (Parent != null) + tmp = Parent.ToString () + tmp; + #if DEBUG_LAYOUTING + return Name == "unamed" ? tmp + "." + this.GetType ().Name + uid.ToString(): tmp + "." + Name; + #else + return Name == "unamed" ? tmp + "." + this.GetType ().Name : tmp + "." + Name; + #endif + } + } +} diff --git a/src/GraphicObjects/Base/Group.cs b/src/GraphicObjects/Base/Group.cs new file mode 100644 index 00000000..d00449cb --- /dev/null +++ b/src/GraphicObjects/Base/Group.cs @@ -0,0 +1,408 @@ +// +// Group.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Generic; +using System.ComponentModel; +using System.Xml.Serialization; +using Cairo; +using OpenTK.Input; +using System.Diagnostics; +using System.Reflection; + + +namespace Crow +{ + public class Group : GraphicObject, IXmlSerializable + { + #region CTOR + public Group() + : base(){} + #endregion + + #region EVENT HANDLERS + public event EventHandler<EventArgs> ChildrenCleared; + #endregion + + internal GraphicObject largestChild = null; + internal GraphicObject tallestChild = null; + + bool _multiSelect = false; + List<GraphicObject> children = new List<GraphicObject>(); + + public virtual List<GraphicObject> Children { + get { return children; } + } + [XmlAttributeAttribute()][DefaultValue(false)] + public bool MultiSelect + { + get { return _multiSelect; } + set { _multiSelect = value; } + } + public virtual void AddChild(GraphicObject g){ + lock (children) { + g.Parent = this; + Children.Add (g); + } + g.RegisteredLayoutings = LayoutingType.None; + g.RegisterForLayouting (LayoutingType.Sizing | LayoutingType.ArrangeChildren); + g.LayoutChanged += OnChildLayoutChanges; + } + public virtual void RemoveChild(GraphicObject child) + { + child.LayoutChanged -= OnChildLayoutChanges; + //child.Parent = null; + lock (children) + Children.Remove(child); + + if (child == largestChild && Width == Measure.Fit) + searchLargestChild (); + if (child == tallestChild && Height == Measure.Fit) + searchTallestChild (); + + this.RegisterForLayouting (LayoutingType.Sizing | LayoutingType.ArrangeChildren); + } + public virtual void ClearChildren() + { + lock (children) { + while (Children.Count > 0) { + GraphicObject g = Children [Children.Count - 1]; + g.LayoutChanged -= OnChildLayoutChanges; + g.Parent = null; + Children.RemoveAt (Children.Count - 1); + } + } + + resetChildrenMaxSize (); + + this.RegisterForLayouting (LayoutingType.Sizing); + ChildrenCleared.Raise (this, new EventArgs ()); + } + + public void putWidgetOnTop(GraphicObject w) + { + if (Children.Contains(w)) + { + lock (children) { + Children.Remove (w); + Children.Add (w); + } + } + } + public void putWidgetOnBottom(GraphicObject w) + { + if (Children.Contains(w)) + { + lock (children) { + Children.Remove (w); + Children.Insert (0, w); + } + } + } + + #region GraphicObject overrides + public override void OnDataSourceChanged (object sender, DataSourceChangeEventArgs e) + { + base.OnDataSourceChanged (this, e); + lock (children) { + foreach (GraphicObject g in children) + if (g.localDataSourceIsNull & g.localLogicalParentIsNull) + g.OnDataSourceChanged (sender, e); + } + } + public override GraphicObject FindByName (string nameToFind) + { + if (Name == nameToFind) + return this; + GraphicObject tmp = null; + lock (children) { + foreach (GraphicObject w in Children) { + tmp = w.FindByName (nameToFind); + if (tmp != null) + break; + } + } + return tmp; + } + public override bool Contains (GraphicObject goToFind) + { + foreach (GraphicObject w in Children) { + if (w == goToFind) + return true; + if (w.Contains (goToFind)) + return true; + } + return false; + } + protected override int measureRawSize (LayoutingType lt) + { + if (Children.Count > 0) { + if (lt == LayoutingType.Width) { + if (largestChild == null) + searchLargestChild (); + if (largestChild == null) { + //if still null, not possible to determine a width + //because all children are stretched, force first one to fit + Children [0].Width = Measure.Fit; + return -1;//cancel actual sizing to let child computation take place + } + } else { + if (tallestChild == null) + searchTallestChild (); + if (tallestChild == null) { + Children [0].Height = Measure.Fit; + return -1; + } + } + } + return base.measureRawSize (lt); + } + + public override void OnLayoutChanges (LayoutingType layoutType) + { + base.OnLayoutChanges (layoutType); + + //position smaller objects in group when group size is fit + switch (layoutType) { + case LayoutingType.Width: + foreach (GraphicObject c in Children){ + if (c.Width.Units == Unit.Percent) + c.RegisterForLayouting (LayoutingType.Width); + else + c.RegisterForLayouting (LayoutingType.X); + } + break; + case LayoutingType.Height: + foreach (GraphicObject c in Children) { + if (c.Height.Units == Unit.Percent) + c.RegisterForLayouting (LayoutingType.Height); + else + c.RegisterForLayouting (LayoutingType.Y); + } + break; + } + } + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + gr.Save (); + + if (ClipToClientRect) { + //clip to client zone + CairoHelpers.CairoRectangle (gr, ClientRectangle, CornerRadius); + gr.Clip (); + } + + lock (children) { + foreach (GraphicObject g in Children) { + g.Paint (ref gr); + } + } + gr.Restore (); + } + protected override void UpdateCache (Context ctx) + { + Rectangle rb = Slot + Parent.ClientRectangle.Position; + + using (ImageSurface cache = new ImageSurface (bmp, Format.Argb32, Slot.Width, Slot.Height, 4 * Slot.Width)) { + Context gr = new Context (cache); + + if (Clipping.count > 0) { + Clipping.clearAndClip (gr); + base.onDraw (gr); + + //clip to client zone + CairoHelpers.CairoRectangle (gr, ClientRectangle, CornerRadius); + gr.Clip (); + + lock (Children) { + foreach (GraphicObject c in Children) { + if (!c.Visible) + continue; + if (Clipping.intersect (c.Slot + ClientRectangle.Position)) + c.Paint (ref gr); + } + } + + #if DEBUG_CLIP_RECTANGLE + Clipping.stroke (gr, Color.Amaranth.AdjustAlpha (0.8)); + #endif + } + gr.Dispose (); + + ctx.SetSourceSurface (cache, rb.X, rb.Y); + ctx.Paint (); + } + Clipping.Reset(); + } + #endregion + + public virtual void OnChildLayoutChanges (object sender, LayoutingEventArgs arg) + { + GraphicObject g = sender as GraphicObject; + + switch (arg.LayoutType) { + case LayoutingType.Width: + if (Width != Measure.Fit) + return; + if (g.Slot.Width > contentSize.Width) { + largestChild = g; + contentSize.Width = g.Slot.Width; + } else if (g == largestChild) + searchLargestChild (); + + this.RegisterForLayouting (LayoutingType.Width); + break; + case LayoutingType.Height: + if (Height != Measure.Fit) + return; + if (g.Slot.Height > contentSize.Height) { + tallestChild = g; + contentSize.Height = g.Slot.Height; + } else if (g == tallestChild) + searchTallestChild (); + + this.RegisterForLayouting (LayoutingType.Height); + break; + } + } + //TODO: x,y position should be taken in account for computation of width and height + void resetChildrenMaxSize(){ + largestChild = null; + tallestChild = null; + contentSize = 0; + } + void searchLargestChild(){ + #if DEBUG_LAYOUTING + Debug.WriteLine("\tSearch largest child"); + #endif + largestChild = null; + contentSize.Width = 0; + for (int i = 0; i < Children.Count; i++) { + if (!Children [i].Visible) + continue; + if (children [i].RegisteredLayoutings.HasFlag (LayoutingType.Width)) + continue; + if (Children [i].Slot.Width > contentSize.Width) { + contentSize.Width = Children [i].Slot.Width; + largestChild = Children [i]; + } + } + } + void searchTallestChild(){ + #if DEBUG_LAYOUTING + Debug.WriteLine("\tSearch tallest child"); + #endif + tallestChild = null; + contentSize.Height = 0; + for (int i = 0; i < Children.Count; i++) { + if (!Children [i].Visible) + continue; + if (children [i].RegisteredLayoutings.HasFlag (LayoutingType.Height)) + continue; + if (Children [i].Slot.Height > contentSize.Height) { + contentSize.Height = Children [i].Slot.Height; + tallestChild = Children [i]; + } + } + } + + + #region Mouse handling + public override void checkHoverWidget (MouseMoveEventArgs e) + { + if (CurrentInterface.HoverWidget != this) { + CurrentInterface.HoverWidget = this; + onMouseEnter (this, e); + } + for (int i = Children.Count - 1; i >= 0; i--) { + if (Children[i].MouseIsIn(e.Position)) + { + Children[i].checkHoverWidget (e); + return; + } + } + base.checkHoverWidget (e); + } + #endregion + + + #region IXmlSerializable + + public override System.Xml.Schema.XmlSchema GetSchema() + { + return null; + } + public override void ReadXml(System.Xml.XmlReader reader) + { + base.ReadXml(reader); + + using (System.Xml.XmlReader subTree = reader.ReadSubtree()) + { + subTree.Read(); + + while (!subTree.EOF) + { + subTree.Read(); + + if (!subTree.IsStartElement()) + break; + + Type t = Type.GetType("Crow." + subTree.Name); + if (t == null) { + Assembly a = Assembly.GetEntryAssembly (); + foreach (Type expT in a.GetExportedTypes ()) { + if (expT.Name == subTree.Name) { + t = expT; + break; + } + } + } + if (t == null) + throw new Exception (subTree.Name + " type not found"); + GraphicObject go = (GraphicObject)Activator.CreateInstance(t); + (go as IXmlSerializable).ReadXml(subTree); + AddChild(go); + } + } + } + public override void WriteXml(System.Xml.XmlWriter writer) + { + base.WriteXml(writer); + + foreach (GraphicObject go in Children) + { + writer.WriteStartElement(go.GetType().Name); + (go as IXmlSerializable).WriteXml(writer); + writer.WriteEndElement(); + } + } + + #endregion + + } +} diff --git a/src/GraphicObjects/Base/GroupBox.cs b/src/GraphicObjects/Base/GroupBox.cs new file mode 100644 index 00000000..9cabbb51 --- /dev/null +++ b/src/GraphicObjects/Base/GroupBox.cs @@ -0,0 +1,58 @@ +// +// GroupBox.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; + +namespace Crow +{ + public class GroupBox : TemplatedContainer + { + Container _contentContainer; + + #region CTOR + public GroupBox() : base(){} + #endregion + + #region Template overrides + public override GraphicObject Content { + get { + return _contentContainer == null ? null : _contentContainer.Child; + } + set { + _contentContainer.SetChild(value); + } + } + protected override void loadTemplate(GraphicObject template = null) + { + base.loadTemplate (template); + + _contentContainer = this.child.FindByName ("Content") as Container; + } + #endregion + } +} diff --git a/src/GraphicObjects/Base/HorizontalStack.cs b/src/GraphicObjects/Base/HorizontalStack.cs new file mode 100644 index 00000000..620dab22 --- /dev/null +++ b/src/GraphicObjects/Base/HorizontalStack.cs @@ -0,0 +1,48 @@ +// +// HorizontalStack.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Generic; +using System.Linq; +using System.Text; +using System.Xml.Serialization; + +namespace Crow +{ + public class HorizontalStack : GenericStack + { + public HorizontalStack() + : base() + { + } + + [XmlIgnore] + public override Orientation Orientation + { + get { return Orientation.Horizontal; } + } + } +} diff --git a/src/GraphicObjects/Base/Image.cs b/src/GraphicObjects/Base/Image.cs new file mode 100644 index 00000000..6a4c590d --- /dev/null +++ b/src/GraphicObjects/Base/Image.cs @@ -0,0 +1,181 @@ +// +// Image.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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 Cairo; +using System.Xml.Serialization; +using System.ComponentModel; +using System.Diagnostics; + + +namespace Crow +{ + public class Image : GraphicObject + { + Picture _pic; + string _svgSub; + bool scaled, keepProps; + double opacity; + + #region Public properties + [XmlAttributeAttribute][DefaultValue(true)] + public virtual bool Scaled { + get { return scaled; } + set { + if (scaled == value) + return; + scaled = value; + NotifyValueChanged ("Scaled", scaled); + if (_pic == null) + return; + _pic.Scaled = scaled; + RegisterForGraphicUpdate (); + } + } + [XmlAttributeAttribute][DefaultValue(true)] + public virtual bool KeepProportions { + get { return keepProps; } + set { + if (keepProps == value) + return; + keepProps = value; + NotifyValueChanged ("KeepProportions", keepProps); + if (_pic == null) + return; + _pic.KeepProportions = keepProps; + RegisterForGraphicUpdate (); + } + } + [XmlAttributeAttribute] + public string Path { + get { return _pic == null ? "" : _pic.Path; } + set { + if (value == Path) + return; + try { + if (string.IsNullOrEmpty(value)) + Picture = null; + else { + lock(CurrentInterface.LayoutMutex){ + LoadImage (value); + } + } + } catch (Exception ex) { + Debug.WriteLine (ex.Message); + _pic = null; + } + NotifyValueChanged ("Path", Path); + } + } + [XmlAttributeAttribute] + public string SvgSub { + get { return _svgSub; } + set { + if (_svgSub == value) + return; + _svgSub = value; + RegisterForGraphicUpdate (); + } + } + [XmlAttributeAttribute] + public Picture Picture { + get { return _pic; } + set { + if (_pic == value) + return; + _pic = value; + NotifyValueChanged ("Picture", _pic); + RegisterForGraphicUpdate (); + } + } + [XmlAttributeAttribute()][DefaultValue(1.0)] + public virtual double Opacity { + get { return opacity; } + set { + if (opacity == value) + return; + opacity = value; + NotifyValueChanged ("Faded", opacity); + RegisterForRedraw (); + } + } + #endregion + + #region CTOR + public Image () : base() + { + } + #endregion + + #region Image Loading + public void LoadImage (string path) + { + Picture pic; + if (path.EndsWith (".svg", true, System.Globalization.CultureInfo.InvariantCulture)) + pic = new SvgPicture (); + else + pic = new BmpPicture (); + + pic.LoadImage (path); + pic.Scaled = scaled; + pic.KeepProportions = keepProps; + + Picture = pic; + } + #endregion + + #region GraphicObject overrides + protected override int measureRawSize (LayoutingType lt) + { + if (_pic == null) + return 2 * Margin; + //_pic = "#Crow.Images.Icons.IconAlerte.svg"; + //TODO:take scalling in account + if (lt == LayoutingType.Width) + return _pic.Dimensions.Width + 2 * Margin; + else + return _pic.Dimensions.Height + 2 * Margin; + } + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + if (_pic == null) + return; + + _pic.Paint (gr, ClientRectangle, _svgSub); + + if (Opacity<1.0) { + gr.SetSourceRGBA (0.0, 0.0, 0.0, 1.0-Opacity); + gr.Operator = Operator.DestOut; + gr.Rectangle (ClientRectangle); + gr.Fill (); + gr.Operator = Operator.Over; + } + } + #endregion + } +} diff --git a/src/GraphicObjects/Base/Label.cs b/src/GraphicObjects/Base/Label.cs new file mode 100644 index 00000000..64ee9688 --- /dev/null +++ b/src/GraphicObjects/Base/Label.cs @@ -0,0 +1,825 @@ +// +// Label.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Generic; +using System.Linq; +using System.Diagnostics; +using Cairo; +using System.Text.RegularExpressions; +using System.Xml.Serialization; +using System.ComponentModel; + +namespace Crow +{ + public class Label : GraphicObject + { + #region CTOR + public Label() + { + + } + public Label(string _text) + : base() + { + Text = _text; + } + #endregion + + public event EventHandler<TextChangeEventArgs> TextChanged; + + public virtual void OnTextChanged(Object sender, TextChangeEventArgs e) + { + NotifyValueChanged ("Text", Text); + TextChanged.Raise (this, e); + } + //TODO:change protected to private + + #region private and protected fields + string _text = "label"; + Alignment _textAlignment; + bool horizontalStretch; + bool verticalStretch; + bool _selectable; + bool _multiline; + Color selBackground; + Color selForeground; + Point mouseLocalPos = -1;//mouse coord in widget space, filled only when clicked + int _currentCol; //0 based cursor position in string + int _currentLine; + Point _selBegin = -1; //selection start (row,column) + Point _selRelease = -1; //selection end (row,column) + double textCursorPos; //cursor position in cairo units in widget client coord. + double SelStartCursorPos = -1; + double SelEndCursorPos = -1; + bool SelectionInProgress = false; + + protected Rectangle rText; + protected float widthRatio = 1f; + protected float heightRatio = 1f; + protected FontExtents fe; + protected TextExtents te; + #endregion + + [XmlAttributeAttribute][DefaultValue("BlueGray")] + public virtual Color SelectionBackground { + get { return selBackground; } + set { + if (value == selBackground) + return; + selBackground = value; + NotifyValueChanged ("SelectionBackground", selBackground); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute][DefaultValue("White")] + public virtual Color SelectionForeground { + get { return selForeground; } + set { + if (value == selForeground) + return; + selForeground = value; + NotifyValueChanged ("SelectionForeground", selForeground); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute][DefaultValue(Alignment.Left)] + public Alignment TextAlignment + { + get { return _textAlignment; } + set { + if (value == _textAlignment) + return; + _textAlignment = value; + RegisterForRedraw (); + NotifyValueChanged ("TextAlignment", _textAlignment); + } + } + [XmlAttributeAttribute][DefaultValue(false)] + public virtual bool HorizontalStretch { + get { return horizontalStretch; } + set { + if (horizontalStretch == value) + return; + horizontalStretch = value; + RegisterForRedraw (); + NotifyValueChanged ("HorizontalStretch", horizontalStretch); + } + } + [XmlAttributeAttribute][DefaultValue(false)] + public virtual bool VerticalStretch { + get { return verticalStretch; } + set { + if (verticalStretch == value) + return; + verticalStretch = value; + RegisterForRedraw (); + NotifyValueChanged ("VerticalStretch", verticalStretch); + } + } + [XmlAttributeAttribute][DefaultValue("label")] + public string Text + { + get { + return lines == null ? + _text : lines.Aggregate((i, j) => i + Interface.LineBreak + j); + } + set + { + if (string.Equals (value, _text, StringComparison.Ordinal)) + return; + + _text = value; + + if (string.IsNullOrEmpty(_text)) + _text = ""; + + lines = getLines; + + OnTextChanged (this, new TextChangeEventArgs (Text)); + RegisterForGraphicUpdate (); + } + } + [XmlAttributeAttribute][DefaultValue(false)] + public bool Selectable + { + get { return _selectable; } + set + { + if (value == _selectable) + return; + _selectable = value; + NotifyValueChanged ("Selectable", _selectable); + SelBegin = -1; + SelRelease = -1; + RegisterForRedraw (); + } + } + [XmlAttributeAttribute][DefaultValue(false)] + public bool Multiline + { + get { return _multiline; } + set + { + if (value == _multiline) + return; + _multiline = value; + NotifyValueChanged ("Multiline", _multiline); + RegisterForGraphicUpdate(); + } + } + [XmlAttributeAttribute][DefaultValue(0)] + public int CurrentColumn{ + get { return _currentCol; } + set { + if (value == _currentCol) + return; + if (value < 0) + _currentCol = 0; + else if (value > lines [_currentLine].Length) + _currentCol = lines [_currentLine].Length; + else + _currentCol = value; + NotifyValueChanged ("CurrentColumn", _currentCol); + } + } + [XmlAttributeAttribute][DefaultValue(0)] + public int CurrentLine{ + get { return _currentLine; } + set { + if (value == _currentLine) + return; + if (value >= lines.Count) + _currentLine = lines.Count-1; + else if (value < 0) + _currentLine = 0; + else + _currentLine = value; + //force recheck of currentCol for bounding + int cc = _currentCol; + _currentCol = 0; + CurrentColumn = cc; + NotifyValueChanged ("CurrentLine", _currentLine); + } + } + [XmlIgnore]public Point CurrentPosition { + get { return new Point(_currentCol, CurrentLine); } + } + //TODO:using HasFocus for drawing selection cause SelBegin and Release binding not to work + /// <summary> + /// Selection begin position in char units + /// </summary> + [XmlAttributeAttribute][DefaultValue("-1")] + public Point SelBegin { + get { + return _selBegin; + } + set { + if (value == _selBegin) + return; + _selBegin = value; + NotifyValueChanged ("SelBegin", _selBegin); + NotifyValueChanged ("SelectedText", SelectedText); + } + } + [XmlAttributeAttribute][DefaultValue("-1")] + public Point SelRelease { + get { + return _selRelease; + } + set { + if (value == _selRelease) + return; + _selRelease = value; + NotifyValueChanged ("SelRelease", _selRelease); + NotifyValueChanged ("SelectedText", SelectedText); + } + } + /// <summary> + /// return char at CurrentLine, CurrentColumn + /// </summary> + [XmlIgnore]protected Char CurrentChar + { + get { + return lines [CurrentLine] [CurrentColumn]; + } + } + /// <summary> + /// ordered selection start and end positions in char units + /// </summary> + [XmlIgnore]protected Point selectionStart + { + get { + return SelRelease < 0 || SelBegin.Y < SelRelease.Y ? SelBegin : + SelBegin.Y > SelRelease.Y ? SelRelease : + SelBegin.X < SelRelease.X ? SelBegin : SelRelease; + } + } + [XmlIgnore]public Point selectionEnd + { + get { + return SelRelease < 0 || SelBegin.Y > SelRelease.Y ? SelBegin : + SelBegin.Y < SelRelease.Y ? SelRelease : + SelBegin.X > SelRelease.X ? SelBegin : SelRelease; + } + } + [XmlIgnore]public string SelectedText + { + get { + + if (SelRelease < 0 || SelBegin < 0) + return ""; + if (selectionStart.Y == selectionEnd.Y) + return lines [selectionStart.Y].Substring (selectionStart.X, selectionEnd.X - selectionStart.X); + string tmp = ""; + tmp = lines [selectionStart.Y].Substring (selectionStart.X); + for (int l = selectionStart.Y + 1; l < selectionEnd.Y; l++) { + tmp += Interface.LineBreak + lines [l]; + } + tmp += Interface.LineBreak + lines [selectionEnd.Y].Substring (0, selectionEnd.X); + return tmp; + } + } + [XmlIgnore]public bool selectionIsEmpty + { get { return SelRelease < 0; } } + + List<string> lines; + List<string> getLines { + get { + return _multiline ? + Regex.Split (_text, "\r\n|\r|\n|\\\\n").ToList() : + new List<string>(new string[] { _text }); + } + } + /// <summary> + /// Moves cursor one char to the left. + /// </summary> + /// <returns><c>true</c> if move succeed</returns> + public bool MoveLeft(){ + int tmp = _currentCol - 1; + if (tmp < 0) { + if (_currentLine == 0) + return false; + CurrentLine--; + CurrentColumn = int.MaxValue; + } else + CurrentColumn = tmp; + return true; + } + /// <summary> + /// Moves cursor one char to the right. + /// </summary> + /// <returns><c>true</c> if move succeed</returns> + public bool MoveRight(){ + int tmp = _currentCol + 1; + if (tmp > lines [_currentLine].Length){ + if (CurrentLine == lines.Count - 1) + return false; + CurrentLine++; + CurrentColumn = 0; + } else + CurrentColumn = tmp; + return true; + } + public void GotoWordStart(){ + CurrentColumn--; + //skip white spaces + while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0) + CurrentColumn--; + while (char.IsLetterOrDigit (lines [CurrentLine] [CurrentColumn]) && CurrentColumn > 0) + CurrentColumn--; + if (!char.IsLetterOrDigit (this.CurrentChar)) + CurrentColumn++; + } + public void GotoWordEnd(){ + //skip white spaces + if (CurrentColumn >= lines [CurrentLine].Length - 1) + return; + while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < lines [CurrentLine].Length-1) + CurrentColumn++; + while (char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < lines [CurrentLine].Length-1) + CurrentColumn++; + if (char.IsLetterOrDigit (this.CurrentChar)) + CurrentColumn++; + } + public void DeleteChar() + { + if (selectionIsEmpty) { + if (CurrentColumn == 0) { + if (CurrentLine == 0 && lines.Count == 1) + return; + CurrentLine--; + CurrentColumn = lines [CurrentLine].Length; + lines [CurrentLine] += lines [CurrentLine + 1]; + lines.RemoveAt (CurrentLine + 1); + + OnTextChanged (this, new TextChangeEventArgs (Text)); + return; + } + CurrentColumn--; + lines [CurrentLine] = lines [CurrentLine].Remove (CurrentColumn, 1); + } else { + int linesToRemove = selectionEnd.Y - selectionStart.Y + 1; + int l = selectionStart.Y; + + if (linesToRemove > 0) { + lines [l] = lines [l].Remove (selectionStart.X, lines [l].Length - selectionStart.X) + + lines [selectionEnd.Y].Substring (selectionEnd.X, lines [selectionEnd.Y].Length - selectionEnd.X); + l++; + for (int c = 0; c < linesToRemove-1; c++) + lines.RemoveAt (l); + CurrentLine = selectionStart.Y; + CurrentColumn = selectionStart.X; + } else + lines [l] = lines [l].Remove (selectionStart.X, selectionEnd.X - selectionStart.X); + CurrentColumn = selectionStart.X; + SelBegin = -1; + SelRelease = -1; + } + OnTextChanged (this, new TextChangeEventArgs (Text)); + } + /// <summary> + /// Insert new string at caret position, should be sure no line break is inside. + /// </summary> + /// <param name="str">String.</param> + protected void Insert(string str) + { + if (!selectionIsEmpty) + this.DeleteChar (); + if (_multiline) { + string[] strLines = Regex.Split (str, "\r\n|\r|\n|" + @"\\n").ToArray(); + lines [CurrentLine] = lines [CurrentLine].Insert (CurrentColumn, strLines[0]); + CurrentColumn += strLines[0].Length; + for (int i = 1; i < strLines.Length; i++) { + InsertLineBreak (); + lines [CurrentLine] = lines [CurrentLine].Insert (CurrentColumn, strLines[i]); + CurrentColumn += strLines[i].Length; + } + } else { + lines [CurrentLine] = lines [CurrentLine].Insert (CurrentColumn, str); + CurrentColumn += str.Length; + } + OnTextChanged (this, new TextChangeEventArgs (Text)); + } + /// <summary> + /// Insert a line break. + /// </summary> + protected void InsertLineBreak() + { + lines.Insert(CurrentLine + 1, lines[CurrentLine].Substring(CurrentColumn)); + lines [CurrentLine] = lines [CurrentLine].Substring (0, CurrentColumn); + CurrentLine++; + CurrentColumn = 0; + OnTextChanged (this, new TextChangeEventArgs (Text)); + } + + #region GraphicObject overrides + protected override int measureRawSize(LayoutingType lt) + { + if (lines == null) + lines = getLines; + + using (ImageSurface img = new ImageSurface (Format.Argb32, 10, 10)) { + using (Context gr = new Context (img)) { + //Cairo.FontFace cf = gr.GetContextFontFace (); + + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + + + fe = gr.FontExtents; + te = new TextExtents(); + + if (lt == LayoutingType.Height){ + int lc = lines.Count; + //ensure minimal height = text line height + if (lc == 0) + lc = 1; + + return (int)Math.Ceiling(fe.Height * lc) + Margin * 2; + } + try { + foreach (string s in lines) { + string l = s.Replace("\t", new String (' ', Interface.TabSize)); + + TextExtents tmp = gr.TextExtents (l); + + if (tmp.XAdvance > te.XAdvance) + te = tmp; + } + } catch (Exception ex) { + return -1; + } + return (int)Math.Ceiling (te.XAdvance) + Margin * 2; + } + } + } + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + gr.FontOptions = Interface.FontRenderingOptions; + gr.Antialias = Interface.Antialias; + + rText = new Rectangle(new Size( + measureRawSize(LayoutingType.Width), measureRawSize(LayoutingType.Height))); + rText.Width -= 2 * Margin; + rText.Height -= 2 * Margin; + + widthRatio = 1f; + heightRatio = 1f; + + Rectangle cb = ClientRectangle; + + rText.X = cb.X; + rText.Y = cb.Y; + + if (horizontalStretch) { + widthRatio = (float)cb.Width / (float)rText.Width; + if (!verticalStretch) + heightRatio = widthRatio; + } + + if (verticalStretch) { + heightRatio = (float)cb.Height / (float)rText.Height; + if (!horizontalStretch) + widthRatio = heightRatio; + } + + rText.Width = (int)(widthRatio * (float)rText.Width); + rText.Height = (int)(heightRatio * (float)rText.Height); + + switch (TextAlignment) + { + case Alignment.TopLeft: //ok + rText.X = cb.X; + rText.Y = cb.Y; + break; + case Alignment.Top: //ok + rText.Y = cb.Y; + rText.X = cb.X + cb.Width / 2 - rText.Width / 2; + break; + case Alignment.TopRight: //ok + rText.Y = cb.Y; + rText.X = cb.Right - rText.Width; + break; + case Alignment.Left://ok + rText.X = cb.X; + rText.Y = cb.Y + cb.Height / 2 - rText.Height / 2; + break; + case Alignment.Right://ok + rText.X = cb.X + cb.Width - rText.Width; + rText.Y = cb.Y + cb.Height / 2 - rText.Height / 2; + break; + case Alignment.Bottom://ok + rText.X = cb.Width / 2 - rText.Width / 2; + rText.Y = cb.Height - rText.Height; + break; + case Alignment.BottomLeft://ok + rText.X = cb.X; + rText.Y = cb.Bottom - rText.Height; + break; + case Alignment.BottomRight://ok + rText.Y = cb.Bottom - rText.Height; + rText.X = cb.Right - rText.Width; + break; + case Alignment.Center://ok + rText.X = cb.X + cb.Width / 2 - rText.Width / 2; + rText.Y = cb.Y + cb.Height / 2 - rText.Height / 2; + break; + } + + gr.FontMatrix = new Matrix(widthRatio * (float)Font.Size, 0, 0, heightRatio * (float)Font.Size, 0, 0); + fe = gr.FontExtents; + + #region draw text cursor + if (HasFocus && Selectable) + { + if (mouseLocalPos >= 0) + { + computeTextCursor(gr); + + if (SelectionInProgress) + { + if (SelBegin < 0){ + SelBegin = new Point(CurrentColumn, CurrentLine); + SelStartCursorPos = textCursorPos; + SelRelease = -1; + }else{ + SelRelease = new Point(CurrentColumn, CurrentLine); + if (SelRelease == SelBegin) + SelRelease = -1; + else + SelEndCursorPos = textCursorPos; + } + }else + computeTextCursorPosition(gr); + }else + computeTextCursorPosition(gr); + + Foreground.SetAsSource (gr); + gr.LineWidth = 1.0; + gr.MoveTo (0.5 + textCursorPos + rText.X, rText.Y + CurrentLine * fe.Height); + gr.LineTo (0.5 + textCursorPos + rText.X, rText.Y + (CurrentLine + 1) * fe.Height); + gr.Stroke(); + } + #endregion + + //****** debug selection ************* +// if (SelRelease >= 0) { +// new SolidColor(Color.DarkGreen).SetAsSource(gr); +// Rectangle R = new Rectangle ( +// rText.X + (int)SelEndCursorPos - 3, +// rText.Y + (int)(SelRelease.Y * fe.Height), +// 6, +// (int)fe.Height); +// gr.Rectangle (R); +// gr.Fill (); +// } +// if (SelBegin >= 0) { +// new SolidColor(Color.DarkRed).SetAsSource(gr); +// Rectangle R = new Rectangle ( +// rText.X + (int)SelStartCursorPos - 3, +// rText.Y + (int)(SelBegin.Y * fe.Height), +// 6, +// (int)fe.Height); +// gr.Rectangle (R); +// gr.Fill (); +// } + //******************* + + for (int i = 0; i < lines.Count; i++) { + string l = lines [i].Replace ("\t", new String (' ', Interface.TabSize)); + int lineLength = (int)gr.TextExtents (l).XAdvance; + Rectangle lineRect = new Rectangle ( + rText.X, + rText.Y + (int)Math.Ceiling(i * fe.Height), + lineLength, + (int)Math.Ceiling(fe.Height)); + +// if (TextAlignment == Alignment.Center || +// TextAlignment == Alignment.Top || +// TextAlignment == Alignment.Bottom) +// lineRect.X += (rText.Width - lineLength) / 2; +// else if (TextAlignment == Alignment.Right || +// TextAlignment == Alignment.TopRight || +// TextAlignment == Alignment.BottomRight) +// lineRect.X += (rText.Width - lineLength); + if (string.IsNullOrWhiteSpace (l)) + continue; + + Foreground.SetAsSource (gr); + gr.MoveTo (lineRect.X, rText.Y + fe.Ascent + fe.Height * i); + gr.ShowText (l); + gr.Fill (); + + if (Selectable) { + if (SelRelease >= 0 && i >= selectionStart.Y && i <= selectionEnd.Y) { + gr.SetSourceColor (selBackground); + + Rectangle selRect = lineRect; + + int cpStart = (int)SelStartCursorPos, + cpEnd = (int)SelEndCursorPos; + + if (SelBegin.Y > SelRelease.Y) { + cpStart = cpEnd; + cpEnd = (int)SelStartCursorPos; + } + + if (i == selectionStart.Y) { + selRect.Width -= cpStart; + selRect.Left += cpStart; + } + if (i == selectionEnd.Y) + selRect.Width -= (lineLength - cpEnd); + + gr.Rectangle (selRect); + gr.FillPreserve (); + gr.Save (); + gr.Clip (); + gr.SetSourceColor (SelectionForeground); + gr.MoveTo (lineRect.X, rText.Y + fe.Ascent + fe.Height * i); + gr.ShowText (l); + gr.Fill (); + gr.Restore (); + } + } + } + } + #endregion + + #region Mouse handling + void updatemouseLocalPos(Point mpos){ + mouseLocalPos = mpos - ScreenCoordinates(Slot).TopLeft - ClientRectangle.TopLeft; + if (mouseLocalPos.X < 0) + mouseLocalPos.X = 0; + if (mouseLocalPos.Y < 0) + mouseLocalPos.Y = 0; + } + public override void onMouseEnter (object sender, MouseMoveEventArgs e) + { + base.onMouseEnter (sender, e); + if (Selectable) + CurrentInterface.MouseCursor = XCursor.Text; + } + public override void onMouseLeave (object sender, MouseMoveEventArgs e) + { + base.onMouseLeave (sender, e); + CurrentInterface.MouseCursor = XCursor.Default; + } + protected override void onFocused (object sender, EventArgs e) + { + base.onFocused (sender, e); + + if (!_selectable) + return; + SelBegin = new Point(0,0); + SelRelease = new Point (lines.LastOrDefault ().Length, lines.Count-1); + RegisterForRedraw (); + } + protected override void onUnfocused (object sender, EventArgs e) + { + base.onUnfocused (sender, e); + + SelBegin = -1; + SelRelease = -1; + RegisterForRedraw (); + } + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + base.onMouseMove (sender, e); + + if (!(SelectionInProgress && HasFocus && _selectable)) + return; + + updatemouseLocalPos (e.Position); + + RegisterForRedraw(); + } + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + if (this.HasFocus && _selectable){ + updatemouseLocalPos (e.Position); + SelBegin = -1; + SelRelease = -1; + SelectionInProgress = true; + RegisterForRedraw();//TODO:should put it in properties + } + + //done at the end to set 'hasFocus' value after testing it + base.onMouseDown (sender, e); + } + public override void onMouseUp (object sender, MouseButtonEventArgs e) + { + base.onMouseUp (sender, e); + + if (!SelectionInProgress) + return; + + updatemouseLocalPos (e.Position); + SelectionInProgress = false; + RegisterForRedraw (); + } + public override void onMouseDoubleClick (object sender, MouseButtonEventArgs e) + { + base.onMouseDoubleClick (sender, e); + + GotoWordStart (); + SelBegin = CurrentPosition; + GotoWordEnd (); + SelRelease = CurrentPosition; + SelectionInProgress = false; + RegisterForRedraw (); + } + #endregion + + /// <summary> + /// Update Current Column, line and TextCursorPos + /// from mouseLocalPos + /// </summary> + void computeTextCursor(Context gr) + { + TextExtents te; + + double cPos = 0f; + + CurrentLine = (int)(mouseLocalPos.Y / fe.Height); + + //fix cu + if (CurrentLine >= lines.Count) + CurrentLine = lines.Count - 1; + + for (int i = 0; i < lines[CurrentLine].Length; i++) + { + string c = lines [CurrentLine].Substring (i, 1); + if (c == "\t") + c = new string (' ', Interface.TabSize); + + te = gr.TextExtents(c); + + double halfWidth = te.XAdvance / 2; + + if (mouseLocalPos.X <= cPos + halfWidth) + { + CurrentColumn = i; + textCursorPos = cPos; + mouseLocalPos = -1; + return; + } + + cPos += te.XAdvance; + } + CurrentColumn = lines[CurrentLine].Length; + textCursorPos = cPos; + + //reset mouseLocalPos + mouseLocalPos = -1; + } + /// <summary> Computes offsets in cairo units </summary> + void computeTextCursorPosition(Context gr) + { + if (SelBegin >= 0) + SelStartCursorPos = GetXFromTextPointer (gr, SelBegin); + if (SelRelease >= 0) + SelEndCursorPos = GetXFromTextPointer (gr, SelRelease); + textCursorPos = GetXFromTextPointer (gr, new Point(CurrentColumn, CurrentLine)); + } + /// <summary> Compute x offset in cairo unit from text position </summary> + double GetXFromTextPointer(Context gr, Point pos) + { + try { + string l = lines [pos.Y].Substring (0, pos.X). + Replace ("\t", new String (' ', Interface.TabSize)); + return gr.TextExtents (l).XAdvance; + } catch{ + return -1; + } + } + } +} diff --git a/src/GraphicObjects/Base/ListBox.cs b/src/GraphicObjects/Base/ListBox.cs new file mode 100644 index 00000000..f66edf61 --- /dev/null +++ b/src/GraphicObjects/Base/ListBox.cs @@ -0,0 +1,47 @@ +// +// ListBox.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; +using System.IO; +using System.Diagnostics; +using System.Xml; +using System.Collections.Generic; +using System.Threading; + +namespace Crow +{ + public class ListBox : TemplatedGroup + { + #region CTOR + public ListBox () : base() {} + #endregion + + } +} + diff --git a/src/GraphicObjects/Base/Menu.cs b/src/GraphicObjects/Base/Menu.cs new file mode 100644 index 00000000..50494815 --- /dev/null +++ b/src/GraphicObjects/Base/Menu.cs @@ -0,0 +1,76 @@ +// +// Menu.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; + +namespace Crow +{ + public class Menu : TemplatedGroup + { + #region CTOR + public Menu () : base() {} + #endregion + + Orientation orientation; + bool autoOpen = false; + + #region Public properties + [XmlAttributeAttribute][DefaultValue(Orientation.Horizontal)] + public Orientation Orientation { + get { return orientation; } + set { + if (orientation == value) + return; + orientation = value; + NotifyValueChanged ("Orientation", orientation); + } + } + [XmlIgnore]public bool AutomaticOpenning + { + get { return autoOpen; } + set { autoOpen = value; } + } + #endregion + + public override void AddItem (GraphicObject g) + { + base.AddItem (g); + + if (orientation == Orientation.Horizontal) + g.NotifyValueChanged ("PopDirection", Alignment.Bottom); + else + g.NotifyValueChanged ("PopDirection", Alignment.Right); + } + public override void onMouseLeave (object sender, MouseMoveEventArgs e) + { + base.onMouseLeave (sender, e); + AutomaticOpenning = false; + } + } +} + diff --git a/src/GraphicObjects/Base/MenuItem.cs b/src/GraphicObjects/Base/MenuItem.cs new file mode 100644 index 00000000..0d99a2eb --- /dev/null +++ b/src/GraphicObjects/Base/MenuItem.cs @@ -0,0 +1,174 @@ +// +// MenuItem.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; + +namespace Crow +{ + public class MenuItem : Menu + { + #region CTOR + public MenuItem () : base() {} + #endregion + + public event EventHandler Open; + public event EventHandler Close; + + string caption; + Command command; + Picture icon; + bool isOpened; + Measure popWidth, popHeight; + + #region Public properties + [XmlAttributeAttribute][DefaultValue(false)] + public bool IsOpened { + get { return isOpened; } + set { + if (isOpened == value) + return; + isOpened = value; + NotifyValueChanged ("IsOpened", isOpened); + + if (isOpened) { + onOpen (this, null); + (LogicalParent as Menu).AutomaticOpenning = true; + }else + onClose (this, null); + } + } + [XmlAttributeAttribute][DefaultValue(null)] + public virtual Command Command { + get { return command; } + set { + if (command == value) + return; + + if (command != null) { + command.raiseAllValuesChanged (); + command.ValueChanged -= Command_ValueChanged; + } + + command = value; + + if (command != null) { + command.ValueChanged += Command_ValueChanged; + command.raiseAllValuesChanged (); + } + + NotifyValueChanged ("Command", command); + } + } + [XmlAttributeAttribute] + public override bool IsEnabled { + get { return Command == null ? base.IsEnabled : Command.CanExecute; } + set { base.IsEnabled = value; } + } + [XmlAttributeAttribute] + public override string Caption { + get { return Command == null ? base.Caption : Command.Caption; } + set { base.Caption = value; } + } + [XmlAttributeAttribute] + public Picture Icon { + get { return Command == null ? icon : Command.Icon;; } + set { + if (icon == value) + return; + icon = value; + if (command == null) + NotifyValueChanged ("Icon", icon); + } + } + [XmlAttributeAttribute()][DefaultValue("Fit")] + public virtual Measure PopWidth { + get { return popWidth; } + set { + if (popWidth == value) + return; + popWidth = value; + NotifyValueChanged ("PopWidth", popWidth); + } + } + [XmlAttributeAttribute()][DefaultValue("Fit")] + public virtual Measure PopHeight { + get { return popHeight; } + set { + if (popHeight == value) + return; + popHeight = value; + NotifyValueChanged ("PopHeight", popHeight); + } + } + #endregion + + public override void AddItem (GraphicObject g) + { + base.AddItem (g); + g.NotifyValueChanged ("PopDirection", Alignment.Right); + } + + void Command_ValueChanged (object sender, ValueChangeEventArgs e) + { + string mName = e.MemberName; + if (mName == "CanExecute") + mName = "IsEnabled"; + NotifyValueChanged (mName, e.NewValue); + } + void onMI_Click (object sender, MouseButtonEventArgs e) + { + if (command != null) + command.Execute (); + if(!IsOpened) + (LogicalParent as Menu).AutomaticOpenning = false; + } + protected virtual void onOpen (object sender, EventArgs e){ + Open.Raise (this, null); + } + protected virtual void onClose (object sender, EventArgs e){ + Close.Raise (this, null); + } + public override bool MouseIsIn (Point m) + { + return IsEnabled ? base.MouseIsIn (m) || child.MouseIsIn (m) : false; + } + public override void onMouseEnter (object sender, MouseMoveEventArgs e) + { + base.onMouseEnter (sender, e); + if ((LogicalParent as Menu).AutomaticOpenning && items.Children.Count>0) + IsOpened = true; + } + public override void onMouseLeave (object sender, MouseMoveEventArgs e) + { + if (IsOpened) + IsOpened = false; + base.onMouseLeave (this, e); + } + } +} + diff --git a/src/GraphicObjects/Base/MessageBox.cs b/src/GraphicObjects/Base/MessageBox.cs new file mode 100644 index 00000000..d8ce1dae --- /dev/null +++ b/src/GraphicObjects/Base/MessageBox.cs @@ -0,0 +1,150 @@ +// +// MessageBox.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; + +namespace Crow +{ + public class MessageBox : Window + { + public enum Type { + Information, + YesNo, + Alert, + Error + } + public MessageBox (): base(){} + + protected override void loadTemplate (GraphicObject template) + { + base.loadTemplate (template); + NotifyValueChanged ("MsgIcon", "#Crow.Images.Icons.Informations.svg"); + } + string message, okMessage, cancelMessage; + Type msgType = Type.Information; + + public event EventHandler Ok; + public event EventHandler Cancel; + + [XmlAttributeAttribute][DefaultValue("Informations")] + public virtual string Message + { + get { return message; } + set { + if (message == value) + return; + message = value; + NotifyValueChanged ("Message", message); + } + } + [XmlAttributeAttribute][DefaultValue("Ok")] + public virtual string OkMessage + { + get { return okMessage; } + set { + if (okMessage == value) + return; + okMessage = value; + NotifyValueChanged ("OkMessage", okMessage); + } + } + [XmlAttributeAttribute][DefaultValue("Cancel")] + public virtual string CancelMessage + { + get { return cancelMessage; } + set { + if (cancelMessage == value) + return; + cancelMessage = value; + NotifyValueChanged ("CancelMessage", cancelMessage); + } + } + [XmlAttributeAttribute][DefaultValue("Information")] + public virtual Type MsgType + { + get { return msgType; } + set { + if (msgType == value) + return; + msgType = value; + NotifyValueChanged ("MsgType", msgType); + switch (msgType) { + case Type.Information: + NotifyValueChanged ("MsgIcon", "#Crow.Images.Icons.Informations.svg"); + Caption = "Informations"; + OkMessage = "Ok"; + CancelMessage = "Cancel"; + break; + case Type.YesNo: + NotifyValueChanged ("MsgIcon", "#Crow.Icons.question.svg"); + Caption = "Choice"; + OkMessage = "Yes"; + CancelMessage = "No"; + break; + case Type.Alert: + NotifyValueChanged ("MsgIcon", "#Crow.Images.Icons.IconAlerte.svg"); + Caption = "Alert"; + OkMessage = "Ok"; + CancelMessage = "Cancel"; + break; + case Type.Error: + NotifyValueChanged ("MsgIcon", "#Crow.Images.Icons.exit.svg"); + Caption = "Error"; + OkMessage = "Ok"; + CancelMessage = "Cancel"; + break; + } + } + } + void onOkButtonClick (object sender, EventArgs e) + { + Ok.Raise (this, null); + close (); + } + void onCancelButtonClick (object sender, EventArgs e) + { + Cancel.Raise (this, null); + close (); + } + public static MessageBox Show (Type msgBoxType, string message, string okMsg = "", string cancelMsg = ""){ + lock (Interface.CurrentInterface.UpdateMutex) { + MessageBox mb = new MessageBox (); + mb.Initialize (); + mb.CurrentInterface.AddWidget (mb); + mb.MsgType = msgBoxType; + mb.Message = message; + if (!string.IsNullOrEmpty(okMsg)) + mb.OkMessage = okMsg; + if (!string.IsNullOrEmpty(cancelMsg)) + mb.CancelMessage = cancelMsg; + return mb; + } + } + } +} + diff --git a/src/GraphicObjects/Base/NumericControl.cs b/src/GraphicObjects/Base/NumericControl.cs new file mode 100644 index 00000000..9b511831 --- /dev/null +++ b/src/GraphicObjects/Base/NumericControl.cs @@ -0,0 +1,125 @@ +// +// NumericControl.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; + +namespace Crow +{ + public abstract class NumericControl : TemplatedControl + { + #region CTOR + public NumericControl () : base() + { + } + public NumericControl(double minimum, double maximum, double step) + : base() + { + } + #endregion + + #region private fields + double _actualValue, minValue, maxValue, smallStep, bigStep; + #endregion + + #region public properties + [XmlAttributeAttribute()][DefaultValue(0.0)] + public virtual double Minimum { + get { return minValue; } + set { + if (minValue == value) + return; + + minValue = value; + NotifyValueChanged ("Minimum", minValue); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute()][DefaultValue(100.0)] + public virtual double Maximum + { + get { return maxValue; } + set { + if (maxValue == value) + return; + + maxValue = value; + NotifyValueChanged ("Maximum", maxValue); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute()][DefaultValue(1.0)] + public virtual double SmallIncrement + { + get { return smallStep; } + set { + if (smallStep == value) + return; + + smallStep = value; + NotifyValueChanged ("SmallIncrement", smallStep); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute()][DefaultValue(5.0)] + public virtual double LargeIncrement + { + get { return bigStep; } + set { + if (bigStep == value) + return; + + bigStep = value; + NotifyValueChanged ("LargeIncrement", bigStep); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute()][DefaultValue(0.0)] + public double Value + { + get { return _actualValue; } + set + { + if (value == _actualValue) + return; + + if (value < minValue) + _actualValue = minValue; + else if (value > maxValue) + _actualValue = maxValue; + else + _actualValue = value; + + NotifyValueChanged("Value", _actualValue); + RegisterForGraphicUpdate(); + } + } + #endregion + + } +} + diff --git a/src/GraphicObjects/Base/Popper.cs b/src/GraphicObjects/Base/Popper.cs new file mode 100644 index 00000000..d3f2e7c8 --- /dev/null +++ b/src/GraphicObjects/Base/Popper.cs @@ -0,0 +1,245 @@ +// +// Popper.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; +using OpenTK.Input; + +namespace Crow +{ + public class Popper : TemplatedContainer + { + #region CTOR + public Popper() : base() + { + } + #endregion + + bool _isPopped, _canPop; + Alignment popDirection; + GraphicObject _content; + Measure popWidth, popHeight; + + public event EventHandler Popped; + public event EventHandler Unpoped; + + #region Public Properties + [XmlAttributeAttribute()][DefaultValue("Fit")] + public virtual Measure PopWidth { + get { return popWidth; } + set { + if (popWidth == value) + return; + popWidth = value; + NotifyValueChanged ("PopWidth", popWidth); + } + } + [XmlAttributeAttribute()][DefaultValue("Fit")] + public virtual Measure PopHeight { + get { return popHeight; } + set { + if (popHeight == value) + return; + popHeight = value; + NotifyValueChanged ("PopHeight", popHeight); + } + } + [XmlAttributeAttribute()][DefaultValue(false)] + public bool IsPopped + { + get { return _isPopped; } + set + { + if (value == _isPopped) + return; + + _isPopped = value; + NotifyValueChanged ("IsPopped", _isPopped); + + if (_isPopped) + onPop (this, null); + else + onUnpop (this, null); + + } + } + [XmlAttributeAttribute()][DefaultValue(true)] + public bool CanPop + { + get { return _canPop; } + set + { + if (value == _canPop) + return; + + _canPop = value; + NotifyValueChanged ("CanPop", _canPop); + } + } + [XmlAttributeAttribute()][DefaultValue(Alignment.Bottom)] + public virtual Alignment PopDirection { + get { return popDirection; } + set { + if (popDirection == value) + return; + popDirection = value; + NotifyValueChanged ("PopDirection", popDirection); + } + } + #endregion + + public override GraphicObject Content { + get { return _content; } + set { + if (_content != null) { + _content.LogicalParent = null; + _content.LayoutChanged -= _content_LayoutChanged; + } + + _content = value; + + if (_content == null) + return; + + _content.LogicalParent = this; + _content.HorizontalAlignment = HorizontalAlignment.Left; + _content.VerticalAlignment = VerticalAlignment.Top; + _content.LayoutChanged += _content_LayoutChanged; + } + } + void positionContent(LayoutingType lt){ + ILayoutable tc = Content.Parent; + if (tc == null) + return; + Rectangle r = this.ScreenCoordinates (this.Slot); + if (lt == LayoutingType.X) { + if (popDirection.HasFlag (Alignment.Right)) { + if (r.Right + Content.Slot.Width > tc.ClientRectangle.Right) + Content.Left = r.Left - Content.Slot.Width; + else + Content.Left = r.Right; + } else if (popDirection.HasFlag (Alignment.Left)) { + if (r.Left - Content.Slot.Width < tc.ClientRectangle.Left) + Content.Left = r.Right; + else + Content.Left = r.Left - Content.Slot.Width; + } else { + if (Content.Slot.Width < tc.ClientRectangle.Width) { + if (r.Left + Content.Slot.Width > tc.ClientRectangle.Right) + Content.Left = tc.ClientRectangle.Right - Content.Slot.Width; + else + Content.Left = r.Left; + } else + Content.Left = 0; + } + }else if (lt == LayoutingType.Y) { + if (Content.Slot.Height < tc.ClientRectangle.Height) { + if (PopDirection.HasFlag (Alignment.Bottom)) { + if (r.Bottom + Content.Slot.Height > tc.ClientRectangle.Bottom) + Content.Top = r.Top - Content.Slot.Height; + else + Content.Top = r.Bottom; + } else if (PopDirection.HasFlag (Alignment.Top)) { + if (r.Top - Content.Slot.Height < tc.ClientRectangle.Top) + Content.Top = r.Bottom; + else + Content.Top = r.Top - Content.Slot.Height; + } else + Content.Top = r.Top; + }else + Content.Top = 0; + } + } + protected void _content_LayoutChanged (object sender, LayoutingEventArgs e) + { + if (e.LayoutType.HasFlag (LayoutingType.Width)) + positionContent (LayoutingType.X); + if (e.LayoutType.HasFlag(LayoutingType.Height)) + positionContent (LayoutingType.Y); + } + + #region GraphicObject overrides + public override void onMouseClick (object sender, MouseButtonEventArgs e) + { + if (_canPop) + IsPopped = !IsPopped; + base.onMouseClick (this, e); + } + public override void onMouseLeave (object sender, MouseMoveEventArgs e) + { + base.onMouseLeave (this, e); + IsPopped = false; + } + public override bool MouseIsIn (Point m) + { + bool isInContent = false; + if (Content != null) { + if (Content.Parent != null) + isInContent = Content.MouseIsIn (m); + } + return base.MouseIsIn (m) || isInContent; + } + public override void checkHoverWidget (MouseMoveEventArgs e) + { + if (CurrentInterface.HoverWidget != this) { + CurrentInterface.HoverWidget = this; + onMouseEnter (this, e); + } + if (Content != null){ + if (Content.Parent != null) { + if (Content.MouseIsIn (e.Position)) { + Content.checkHoverWidget (e); + return; + } + } + } + base.checkHoverWidget (e); + } + #endregion + + public virtual void onPop(object sender, EventArgs e) + { + if (Content != null) { + Content.Visible = true; + if (Content.Parent == null) + CurrentInterface.AddWidget (Content, true); + if (Content.LogicalParent != this) + Content.LogicalParent = this; + CurrentInterface.PutOnTop (Content, true); + _content_LayoutChanged (this, new LayoutingEventArgs (LayoutingType.Sizing)); + } + Popped.Raise (this, e); + } + public virtual void onUnpop(object sender, EventArgs e) + { + if (Content != null) { + Content.Visible = false; + } + Unpoped.Raise (this, e); + } + } +} diff --git a/src/GraphicObjects/Base/PrivateContainer.cs b/src/GraphicObjects/Base/PrivateContainer.cs new file mode 100644 index 00000000..65eacabb --- /dev/null +++ b/src/GraphicObjects/Base/PrivateContainer.cs @@ -0,0 +1,207 @@ +// +// PrivateContainer.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; +using Cairo; + +namespace Crow +{ + /// <summary> + /// Implement drawing and layouting for a single child, but + /// does not implement IXmlSerialisation to allow reuse of container + /// behaviour for widgets that have other xml hierarchy: example + /// TemplatedControl may have 3 children (template,templateItem,content) but + /// behave exactely as a container for layouting and drawing + /// </summary> + public class PrivateContainer : GraphicObject + { + #region CTOR + public PrivateContainer() + : base() + { + } + #endregion + + protected GraphicObject child; + + internal virtual void SetChild(GraphicObject _child) + { + + if (child != null) { + contentSize = new Size (0, 0); + child.LayoutChanged -= OnChildLayoutChanges; + child.Parent = null; + this.RegisterForGraphicUpdate (); + } + + child = _child as GraphicObject; + + if (child != null) { + child.Parent = this; + child.LayoutChanged += OnChildLayoutChanges; + contentSize = child.Slot.Size; + child.RegisteredLayoutings = LayoutingType.None; + child.RegisterForLayouting (LayoutingType.Sizing); + } + } + + #region GraphicObject Overrides + + public override GraphicObject FindByName (string nameToFind) + { + if (Name == nameToFind) + return this; + + return child == null ? null : child.FindByName (nameToFind); + } + public override bool Contains (GraphicObject goToFind) + { + return child == goToFind ? true : + child == null ? false : child.Contains(goToFind); + } + public override void OnDataSourceChanged (object sender, DataSourceChangeEventArgs e) + { + base.OnDataSourceChanged (this, e); + if (child != null) + if (child.localDataSourceIsNull & child.localLogicalParentIsNull) + child.OnDataSourceChanged (sender, e); + } + public override bool UpdateLayout (LayoutingType layoutType) + { + if (child != null) { + //force sizing to fit if sizing on children and child has stretched size + switch (layoutType) { + case LayoutingType.Width: + if (Width == Measure.Fit && child.Width.Units == Unit.Percent) + child.Width = Measure.Fit; + break; + case LayoutingType.Height: + if (Height == Measure.Fit && child.Height.Units == Unit.Percent) + child.Height = Measure.Fit; + break; + } + } + return base.UpdateLayout (layoutType); + } + public override void OnLayoutChanges (LayoutingType layoutType) + { + base.OnLayoutChanges (layoutType); + + if (child == null) + return; + + LayoutingType ltChild = LayoutingType.None; + + if (layoutType == LayoutingType.Width) { + if (child.Width.Units == Unit.Percent) { + ltChild |= LayoutingType.Width; + if (child.Width.Value < 100 && child.Left == 0) + ltChild |= LayoutingType.X; + } else if (child.Left == 0) + ltChild |= LayoutingType.X; + } else if (layoutType == LayoutingType.Height) { + if (child.Height.Units == Unit.Percent) { + ltChild |= LayoutingType.Height; + if (child.Height.Value < 100 && child.Top == 0) + ltChild |= LayoutingType.Y; + } else if (child.Top == 0) + ltChild |= LayoutingType.Y; + } + if (ltChild == LayoutingType.None) + return; + child.RegisterForLayouting (ltChild); + } + public virtual void OnChildLayoutChanges (object sender, LayoutingEventArgs arg) + { + GraphicObject g = sender as GraphicObject; + + if (arg.LayoutType == LayoutingType.Width) { + if (Width != Measure.Fit) + return; + contentSize.Width = g.Slot.Width; + this.RegisterForLayouting (LayoutingType.Width); + }else if (arg.LayoutType == LayoutingType.Height){ + if (Height != Measure.Fit) + return; + contentSize.Height = g.Slot.Height; + this.RegisterForLayouting (LayoutingType.Height); + } + } + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + gr.Save (); + + if (ClipToClientRect) { + //clip to client zone + CairoHelpers.CairoRectangle (gr, ClientRectangle, CornerRadius); + gr.Clip (); + } + + if (child != null) { + if (child.Visible) + child.Paint (ref gr); + } + gr.Restore (); + } + protected override void UpdateCache (Context ctx) + { + Rectangle rb = Slot + Parent.ClientRectangle.Position; + + using (ImageSurface cache = new ImageSurface (bmp, Format.Argb32, Slot.Width, Slot.Height, 4 * Slot.Width)) { + Context gr = new Context (cache); + + if (Clipping.count > 0) { + Clipping.clearAndClip (gr); + + onDraw (gr); + } + + gr.Dispose (); + + ctx.SetSourceSurface (cache, rb.X, rb.Y); + ctx.Paint (); + } + Clipping.Reset(); + } + #endregion + + #region Mouse handling + public override void checkHoverWidget (MouseMoveEventArgs e) + { + base.checkHoverWidget (e); + if (child != null) + if (child.MouseIsIn (e.Position)) + child.checkHoverWidget (e); + } + #endregion + + } +} + diff --git a/src/GraphicObjects/Base/ProgressBar.cs b/src/GraphicObjects/Base/ProgressBar.cs new file mode 100644 index 00000000..34366ca7 --- /dev/null +++ b/src/GraphicObjects/Base/ProgressBar.cs @@ -0,0 +1,67 @@ +// +// ProgressBar.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Generic; +using System.Linq; +using System.Text; +using Cairo; +using System.Diagnostics; +using System.Xml.Serialization; +using System.ComponentModel; + +namespace Crow +{ + + public class ProgressBar : NumericControl + { + #region CTOR + public ProgressBar() : base(){} + #endregion + + protected override void loadTemplate (GraphicObject template) + { + + } + + #region GraphicObject overrides + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + if (Maximum == 0) + return; + + Rectangle rBack = ClientRectangle; + rBack.Width = (int)((double)rBack.Width / Maximum * Value); + Foreground.SetAsSource (gr, rBack); + + CairoHelpers.CairoRectangle(gr,rBack,CornerRadius); + gr.Fill(); + } + #endregion + } +} diff --git a/src/GraphicObjects/Base/RadioButton.cs b/src/GraphicObjects/Base/RadioButton.cs new file mode 100644 index 00000000..993ead59 --- /dev/null +++ b/src/GraphicObjects/Base/RadioButton.cs @@ -0,0 +1,82 @@ +// +// RadioButton.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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 System.Xml.Serialization; + +namespace Crow +{ + public class RadioButton : TemplatedControl + { + bool isChecked; + + #region CTOR + public RadioButton() : base(){} + #endregion + + public event EventHandler Checked; + public event EventHandler Unchecked; + + #region GraphicObject overrides + public override void onMouseClick (object sender, MouseButtonEventArgs e) + { + Group pg = Parent as Group; + if (pg != null) { + for (int i = 0; i < pg.Children.Count; i++) { + RadioButton c = pg.Children [i] as RadioButton; + if (c == null) + continue; + c.IsChecked = (c == this); + } + } else + IsChecked = !IsChecked; + + base.onMouseClick (sender, e); + } + #endregion + + [XmlAttributeAttribute()][DefaultValue(false)] + public bool IsChecked + { + get { return isChecked; } + set + { + if (isChecked == value) + return; + + isChecked = value; + + NotifyValueChanged ("IsChecked", value); + + if (isChecked) + Checked.Raise (this, null); + else + Unchecked.Raise (this, null); + } + } + } +} diff --git a/src/GraphicObjects/Base/ScrollBar.cs b/src/GraphicObjects/Base/ScrollBar.cs new file mode 100644 index 00000000..91ce50ef --- /dev/null +++ b/src/GraphicObjects/Base/ScrollBar.cs @@ -0,0 +1,68 @@ +// +// ScrollBar.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; +using OpenTK.Input; + +namespace Crow +{ + public class ScrollBar : NumericControl + { + Orientation _orientation; + + #region CTOR + public ScrollBar() : base() {} + #endregion + + [XmlAttributeAttribute()][DefaultValue(Orientation.Vertical)] + public virtual Orientation Orientation + { + get { return _orientation; } + set { + if (_orientation == value) + return; + _orientation = value; + NotifyValueChanged ("Orientation", _orientation); + RegisterForGraphicUpdate (); + } + } + public void onScrollBack (object sender, MouseButtonEventArgs e) + { + Value -= SmallIncrement; + } + public void onScrollForth (object sender, MouseButtonEventArgs e) + { + Value += SmallIncrement; + } + + public void onSliderValueChange(object sender, ValueChangeEventArgs e){ + if (e.MemberName == "Value") + Value = Convert.ToDouble(e.NewValue); + } + } +} diff --git a/src/GraphicObjects/Base/Scroller.cs b/src/GraphicObjects/Base/Scroller.cs new file mode 100644 index 00000000..640fcfe8 --- /dev/null +++ b/src/GraphicObjects/Base/Scroller.cs @@ -0,0 +1,239 @@ +// +// Scroller.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; +using System.Diagnostics; +using Cairo; + +namespace Crow +{ + public class Scroller : Container + { + bool _verticalScrolling; + bool _horizontalScrolling; + bool _scrollbarVisible; + double _scrollX = 0.0; + double _scrollY = 0.0; + int scrollSpeed; + + public event EventHandler<ScrollingEventArgs> Scrolled; + + #region public properties + [XmlAttributeAttribute][DefaultValue(true)] + public bool VerticalScrolling { + get { return _verticalScrolling; } + set { _verticalScrolling = value; } + } + + [XmlAttributeAttribute][DefaultValue(false)] + public bool HorizontalScrolling { + get { return _horizontalScrolling; } + set { _horizontalScrolling = value; } + } + [XmlAttributeAttribute][DefaultValue(true)] + public bool ScrollbarVisible { + get { return _scrollbarVisible; } + set { _scrollbarVisible = value; } + } + [XmlAttributeAttribute][DefaultValue(0.0)] + public double ScrollX { + get { + return _scrollX; + } + set { + if (_scrollX == value) + return; + if (value < 0.0) + _scrollX = 0.0; + else if (value > Child.Slot.Width - ClientRectangle.Width) + _scrollX = Math.Max(0.0, Child.Slot.Width - ClientRectangle.Width); + else + _scrollX = value; + NotifyValueChanged("ScrollX", _scrollX); + RegisterForRedraw (); + Scrolled.Raise (this, new ScrollingEventArgs (Orientation.Horizontal)); + } + } + [XmlAttributeAttribute][DefaultValue(0.0)] + public double ScrollY { + get { + return _scrollY; + } + set { + if (_scrollY == value) + return; + if (value < 0.0) + _scrollY = 0.0; + else if (value > Child.Slot.Height - ClientRectangle.Height) + _scrollY = Math.Max(0.0,Child.Slot.Height - ClientRectangle.Height); + else + _scrollY = value; + NotifyValueChanged("ScrollY", _scrollY); + RegisterForRedraw (); + Scrolled.Raise (this, new ScrollingEventArgs (Orientation.Vertical)); + } + } + + [XmlIgnore] + public int MaximumScroll { + get { + try { + return VerticalScrolling ? + Math.Max(Child.Slot.Height - ClientRectangle.Height,0) : + Math.Max(Child.Slot.Width - ClientRectangle.Width,0); + } catch { + return 0; + } + } + } + + [XmlAttributeAttribute][DefaultValue(30)] + public int ScrollSpeed { + get { return scrollSpeed; } + set { + scrollSpeed = value; + NotifyValueChanged("ScrollSpeed", scrollSpeed); + } + } + #endregion + + public Scroller() + : base(){} + + #region GraphicObject Overrides + public override void OnLayoutChanges (LayoutingType layoutType) + { + base.OnLayoutChanges (layoutType); + + NotifyValueChanged("MaximumScroll", MaximumScroll); + } + void OnChildLayoutChanges (object sender, LayoutingEventArgs arg) + { + //Debug.WriteLine ("scroller childLayoutChanges"); + int maxScroll = MaximumScroll; + //Debug.WriteLine ("maxscroll={0}", maxScroll); + if (_verticalScrolling) { + if (arg.LayoutType == LayoutingType.Height) { + if (maxScroll < ScrollY) { + //Debug.WriteLine ("scrolly={0} maxscroll={1}", ScrollY, maxScroll); + ScrollY = maxScroll; + } + NotifyValueChanged("MaximumScroll", maxScroll); + } + } else if (arg.LayoutType == LayoutingType.Width) { + if (maxScroll < ScrollX) { + //Debug.WriteLine ("scrolly={0} maxscroll={1}", ScrollY, maxScroll); + ScrollX = maxScroll; + } + NotifyValueChanged("MaximumScroll", maxScroll); + } + } + void onChildListCleared(object sender, EventArgs e){ + ScrollY = 0; + ScrollX = 0; + } + public override void SetChild (GraphicObject _child) + { + GraphicObject c = child as GraphicObject; + Group g = child as Group; + if (c != null) { + c.LayoutChanged -= OnChildLayoutChanges; + if (g != null) + g.ChildrenCleared -= onChildListCleared; + } + c = _child as GraphicObject; + g = _child as Group; + if (c != null) { + c.LayoutChanged += OnChildLayoutChanges; + if (g != null) + g.ChildrenCleared += onChildListCleared; + } + base.SetChild (_child); + } + public override Rectangle ScreenCoordinates (Rectangle r) + { + return base.ScreenCoordinates (r) - new Point((int)ScrollX,(int)ScrollY); + } + protected override void onDraw (Context gr) + { + Rectangle rBack = new Rectangle (Slot.Size); + + Background.SetAsSource (gr, rBack); + CairoHelpers.CairoRectangle(gr,rBack, CornerRadius); + gr.Fill (); + + gr.Save (); + if (ClipToClientRect) { + //clip to scrolled client zone + CairoHelpers.CairoRectangle (gr, ClientRectangle, CornerRadius); + gr.Clip (); + } + + gr.Translate (-ScrollX, -ScrollY); + if (child != null) + child.Paint (ref gr); + gr.Restore (); + } + + #region Mouse handling + internal Point savedMousePos; + public override bool MouseIsIn (Point m) + { + return Visible ? base.ScreenCoordinates(Slot).ContainsOrIsEqual (m) : false; + } + public override void checkHoverWidget (MouseMoveEventArgs e) + { + savedMousePos = e.Position; + Point m = e.Position - new Point ((int)ScrollX, (int)ScrollY); + base.checkHoverWidget (new MouseMoveEventArgs(m.X,m.Y,e.XDelta,e.YDelta)); + } + public override void onMouseWheel (object sender, MouseWheelEventArgs e) + { + if (Child == null) + return; + + if (VerticalScrolling ) + ScrollY -= e.Delta * ScrollSpeed; + if (HorizontalScrolling ) + ScrollX -= e.Delta * ScrollSpeed; + } + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + savedMousePos.X += e.XDelta; + savedMousePos.Y += e.YDelta; + base.onMouseMove (sender, new MouseMoveEventArgs(savedMousePos.X,savedMousePos.Y,e.XDelta,e.YDelta)); + } + public override void RegisterClip (Rectangle clip) + { + base.RegisterClip (clip - new Point((int)ScrollX,(int)ScrollY)); + } + #endregion + + #endregion + } +} diff --git a/src/GraphicObjects/Base/Slider.cs b/src/GraphicObjects/Base/Slider.cs new file mode 100644 index 00000000..36f7e731 --- /dev/null +++ b/src/GraphicObjects/Base/Slider.cs @@ -0,0 +1,219 @@ +// +// Slider.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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 Cairo; +using System.Xml.Serialization; +using System.ComponentModel; +using System.Diagnostics; + +namespace Crow +{ + public class Slider : NumericControl + { + #region CTOR + public Slider() : base() + {} + public Slider(double minimum, double maximum, double step) + : base(minimum,maximum,step) + { + } + #endregion + + #region implemented abstract members of TemplatedControl + + protected override void loadTemplate (GraphicObject template = null) + { + + } + + #endregion + + #region private fields + Rectangle cursor; + int _cursorSize; + Fill _cursorColor; + Orientation _orientation; + bool holdCursor = false; + #endregion + + protected double unity; + + #region Public properties + [XmlAttributeAttribute()][DefaultValue("vgradient|0:White|0,1:LightGray|0,9:LightGray|1:DimGray")] + public virtual Fill CursorColor { + get { return _cursorColor; } + set { + if (_cursorColor == value) + return; + _cursorColor = value; + RegisterForRedraw (); + NotifyValueChanged ("CursorColor", _cursorColor); + } + } + [XmlAttributeAttribute()][DefaultValue(20)] + public virtual int CursorSize { + get { return _cursorSize; } + set { + if (_cursorSize == value) + return; + _cursorSize = value; + RegisterForGraphicUpdate (); + NotifyValueChanged ("CursorSize", _cursorSize); + } + } + [XmlAttributeAttribute()][DefaultValue(Orientation.Horizontal)] + public virtual Orientation Orientation + { + get { return _orientation; } + set { + if (_orientation == value) + return; + _orientation = value; + + RegisterForLayouting (LayoutingType.All); + NotifyValueChanged ("Orientation", _orientation); + } + } + #endregion + + [XmlAttributeAttribute()][DefaultValue(10.0)] + public override double Maximum { + get { return base.Maximum; } + set { + if (value == base.Maximum) + return; + base.Maximum = value; + LargeIncrement = base.Maximum / 10.0; + SmallIncrement = LargeIncrement / 5.0; + } + } + + #region GraphicObject Overrides + protected override void onDraw (Context gr) + { + base.onDraw (gr); + if (Maximum <= 0) + return; + + computeCursorPosition (); + + Rectangle r = ClientRectangle; + PointD pStart; + PointD pEnd; + if (_orientation == Orientation.Horizontal) { + pStart = r.TopLeft + new Point (_cursorSize / 2, r.Height / 2); + pEnd = r.TopRight + new Point (-_cursorSize / 2, r.Height / 2); + } else { + pStart = r.TopLeft + new Point (r.Width / 2, _cursorSize / 2); + pEnd = r.BottomLeft + new Point (r.Width / 2,- _cursorSize / 2); + } + + DrawGraduations (gr, pStart,pEnd); + + DrawCursor (gr, cursor); + } + #endregion + + protected virtual void DrawGraduations(Context gr, PointD pStart, PointD pEnd) + { + Foreground.SetAsSource (gr); + + gr.LineWidth = 1; + gr.MoveTo(pStart); + gr.LineTo(pEnd); + + gr.Stroke(); + + } + protected virtual void DrawCursor(Context gr, Rectangle _cursor) + { + CursorColor.SetAsSource (gr, _cursor); + CairoHelpers.CairoRectangle (gr, _cursor, CornerRadius); + gr.Fill(); + } + + void computeCursorPosition() + { + Rectangle r = ClientRectangle; + PointD p1; + + if (_orientation == Orientation.Horizontal) { + cursor = new Rectangle (new Size (_cursorSize, (int)(r.Height))); + p1 = r.TopLeft + new Point (_cursorSize / 2, r.Height / 2); + unity = (double)(r.Width - _cursorSize) / (Maximum - Minimum); + cursor.TopLeft = new Point (r.Left + (int)(Value * unity), + (int)(p1.Y - cursor.Height / 2)); + } else { + cursor = new Rectangle (new Size ((int)(r.Width), _cursorSize)); + p1 = r.TopLeft + new Point (r.Width / 2, _cursorSize / 2); + unity = (double)(r.Height - _cursorSize) / (Maximum - Minimum); + cursor.TopLeft = new Point ((int)(p1.X - r.Width / 2), + r.Top + (int)(Value * unity)); + } + cursor.Inflate (-1); + } + + #region mouse handling + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + base.onMouseDown (sender, e); + + Rectangle cursInScreenCoord = ScreenCoordinates (cursor + Slot.Position); + if (cursInScreenCoord.ContainsOrIsEqual (e.Position)) + holdCursor = true; + else if (_orientation == Orientation.Horizontal) { + if (e.Position.X < cursInScreenCoord.Left) + Value -= LargeIncrement; + else + Value += LargeIncrement; + } else { + if (e.Position.Y < cursInScreenCoord.Top) + Value -= LargeIncrement; + else + Value += LargeIncrement; + } + } + public override void onMouseUp (object sender,MouseButtonEventArgs e) + { + base.onMouseUp (sender, e); + + holdCursor = false; + } + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + if (holdCursor) { + if (_orientation == Orientation.Horizontal) + Value += (double)e.XDelta / unity; + else + Value += (double)e.YDelta / unity; + } + + base.onMouseMove (sender, e); + } + #endregion + } +} diff --git a/src/GraphicObjects/Base/Spinner.cs b/src/GraphicObjects/Base/Spinner.cs new file mode 100644 index 00000000..1bf93a37 --- /dev/null +++ b/src/GraphicObjects/Base/Spinner.cs @@ -0,0 +1,55 @@ +// +// Spinner.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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; + +namespace Crow +{ + public class Spinner : NumericControl + { + #region CTOR + public Spinner () : base() + { + } + public Spinner (double minimum, double maximum, double step) : + base (minimum, maximum, step) + { + + } + #endregion + + void onUp (object sender, MouseButtonEventArgs e) + { + Value += this.SmallIncrement; + } + void onDown (object sender, MouseButtonEventArgs e) + { + Value -= this.SmallIncrement; + } + + } +} + diff --git a/src/GraphicObjects/Base/Splitter.cs b/src/GraphicObjects/Base/Splitter.cs new file mode 100644 index 00000000..2f5dd15b --- /dev/null +++ b/src/GraphicObjects/Base/Splitter.cs @@ -0,0 +1,218 @@ +// +// Splitter.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; + +namespace Crow +{ + public class Splitter : GraphicObject + { + #region CTOR + public Splitter (): base(){} + #endregion + + int thickness; + + [XmlAttributeAttribute][DefaultValue(1)] + public virtual int Thickness { + get { return thickness; } + set { + if (thickness == value) + return; + thickness = value; + NotifyValueChanged ("Thickness", thickness); + RegisterForLayouting (LayoutingType.Sizing); + RegisterForGraphicUpdate (); + } + } + + Unit u1, u2; + int init1 = -1, init2 = -1, delta = 0, min1, min2, max1 , max2; + GraphicObject go1 = null, go2 = null; + + void initSplit(Measure m1, int size1, Measure m2, int size2){ + if (m1 != Measure.Stretched) { + init1 = size1; + u1 = m1.Units; + } + if (m2 != Measure.Stretched) { + init2 = size2; + u2 = m2.Units; + } + } + void convertSizeInPix(GraphicObject g1){ + + } + + #region GraphicObject override + public override ILayoutable Parent { + get { return base.Parent; } + set { + if (value != null) { + GenericStack gs = value as GenericStack; + if (gs == null) + throw new Exception ("Splitter may only be chil of stack"); + + } + base.Parent = value; + } + } + public override void onMouseEnter (object sender, MouseMoveEventArgs e) + { + base.onMouseEnter (sender, e); + if ((Parent as GenericStack).Orientation == Orientation.Horizontal) + CurrentInterface.MouseCursor = XCursor.H; + else + CurrentInterface.MouseCursor = XCursor.V; + } + public override void onMouseLeave (object sender, MouseMoveEventArgs e) + { + base.onMouseLeave (sender, e); + CurrentInterface.MouseCursor = XCursor.Default; + } + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + base.onMouseDown (sender, e); + go1 = go2 = null; + init1 = init2 = -1; + delta = 0; + + GenericStack gs = Parent as GenericStack; + int ptrSplit = gs.Children.IndexOf (this); + if (ptrSplit == 0 || ptrSplit == gs.Children.Count - 1) + return; + + go1 = gs.Children [ptrSplit - 1]; + go2 = gs.Children [ptrSplit + 1]; + + if (gs.Orientation == Orientation.Horizontal) { + initSplit (go1.Width, go1.Slot.Width, go2.Width, go2.Slot.Width); + min1 = go1.MinimumSize.Width; + min2 = go2.MinimumSize.Width; + max1 = go1.MaximumSize.Width; + max2 = go2.MaximumSize.Width; + if (init1 >= 0) + go1.Width = init1; + if (init2 >= 0) + go2.Width = init2; + } else { + initSplit (go1.Height, go1.Slot.Height, go2.Height, go2.Slot.Height); + min1 = go1.MinimumSize.Height; + min2 = go2.MinimumSize.Height; + max1 = go1.MaximumSize.Height; + max2 = go2.MaximumSize.Height; + if (init1 >= 0) + go1.Height = init1; + if (init2 >= 0) + go2.Height = init2; + } + } + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + base.onMouseMove (sender, e); + + if (!IsActive) + return; + + GenericStack gs = Parent as GenericStack; + int newDelta = delta, size1 = init1 , size2 = init2; + if (gs.Orientation == Orientation.Horizontal) { + newDelta -= e.XDelta; + if (size1 < 0) + size1 = go1.Slot.Width + delta; + if (size2 < 0) + size2 = go2.Slot.Width - delta; + } else { + newDelta -= e.YDelta; + if (size1 < 0) + size1 = go1.Slot.Height + delta; + if (size2 < 0) + size2 = go2.Slot.Height - delta; + } + + if (size1 - newDelta < min1 || (max1 > 0 && size1 - newDelta > max1) || + size2 + newDelta < min2 || (max2 > 0 && size2 + newDelta > max2)) + return; + + delta = newDelta; + + if (gs.Orientation == Orientation.Horizontal) { + if (init1 >= 0) + go1.Width = init1 - delta; + if (init2 >= 0) + go2.Width = init2 + delta; + } else { + if (init1 >= 0) + go1.Height = init1 - delta; + if (init2 >= 0) + go2.Height = init2 + delta; + } + } + public override void onMouseUp (object sender, MouseButtonEventArgs e) + { + base.onMouseUp (sender, e); + + GenericStack gs = Parent as GenericStack; + + if (init1 >= 0 && u1 == Unit.Percent) { + if (gs.Orientation == Orientation.Horizontal) + go1.Width = new Measure ((int)Math.Ceiling ( + go1.Width.Value * 100.0 / (double)gs.Slot.Width), Unit.Percent); + else + go1.Height = new Measure ((int)Math.Ceiling ( + go1.Height.Value * 100.0 / (double)gs.Slot.Height), Unit.Percent); + } + if (init2 >= 0 && u2 == Unit.Percent) { + if (gs.Orientation == Orientation.Horizontal) + go2.Width = new Measure ((int)Math.Floor ( + go2.Width.Value * 100.0 / (double)gs.Slot.Width), Unit.Percent); + else + go2.Height = new Measure ((int)Math.Floor ( + go2.Height.Value * 100.0 / (double)gs.Slot.Height), Unit.Percent); + } + } + public override bool UpdateLayout (LayoutingType layoutType) + { + GenericStack gs = Parent as GenericStack; + if (layoutType == LayoutingType.Width){ + if (gs.Orientation == Orientation.Horizontal) + Width = thickness; + else + Width = Measure.Stretched; + } else if (layoutType == LayoutingType.Height){ + if (gs.Orientation == Orientation.Vertical) + Height = thickness; + else + Height = Measure.Stretched; + } + return base.UpdateLayout (layoutType); + } + #endregion + } +} + diff --git a/src/GraphicObjects/Base/TabItem.cs b/src/GraphicObjects/Base/TabItem.cs new file mode 100644 index 00000000..7dac5472 --- /dev/null +++ b/src/GraphicObjects/Base/TabItem.cs @@ -0,0 +1,210 @@ +// +// TabItem.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; +using System.Diagnostics; + +namespace Crow +{ + public class TabItem : TemplatedContainer + { + #region CTOR + public TabItem () : base() {} + #endregion + + #region Private fields + string caption; + Container _contentContainer; + GraphicObject _tabTitle; + int tabOffset; + bool isSelected; + Measure tabThickness; + #endregion + + #region TemplatedControl overrides + public override GraphicObject Content { + get { + return _contentContainer == null ? null : _contentContainer.Child; + } + set { + if (Content != null) { + Content.LogicalParent = null; + _contentContainer.SetChild (null); + } + _contentContainer.SetChild(value); + if (value != null) + value.LogicalParent = this; + } + } + protected override void loadTemplate(GraphicObject template = null) + { + base.loadTemplate (template); + + _contentContainer = this.child.FindByName ("Content") as Container; + _tabTitle = this.child.FindByName ("TabTitle"); + } + internal GraphicObject TabTitle { get { return _tabTitle; }} + #endregion + + [XmlAttributeAttribute][DefaultValue("18")] + public virtual Measure TabThickness { + get { return tabThickness; } + set { + if (tabThickness == value) + return; + tabThickness = value; + NotifyValueChanged ("TabThickness", tabThickness); + RegisterForGraphicUpdate (); + } + } + [XmlAttributeAttribute][DefaultValue(0)] + public virtual int TabOffset { + get { return tabOffset; } + set { + if (tabOffset == value) + return; + tabOffset = value; + NotifyValueChanged ("TabOffset", tabOffset); + + RegisterForLayouting (LayoutingType.X); + RegisterForGraphicUpdate (); + } + } + + [XmlAttributeAttribute][DefaultValue(false)] + public virtual bool IsSelected { + get { return isSelected; } + set { + if (isSelected == value) + return; + isSelected = value; + NotifyValueChanged ("IsSelected", isSelected); + } + } + protected override void onDraw (Cairo.Context gr) + { + gr.Save (); + + int spacing = (Parent as TabView).Spacing; + + gr.MoveTo (0.5, TabTitle.Slot.Bottom-0.5); + gr.LineTo (TabTitle.Slot.Left - spacing, TabTitle.Slot.Bottom-0.5); + gr.CurveTo ( + TabTitle.Slot.Left - spacing / 2, TabTitle.Slot.Bottom-0.5, + TabTitle.Slot.Left - spacing / 2, 0.5, + TabTitle.Slot.Left, 0.5); + gr.LineTo (TabTitle.Slot.Right, 0.5); + gr.CurveTo ( + TabTitle.Slot.Right + spacing / 2, 0.5, + TabTitle.Slot.Right + spacing / 2, TabTitle.Slot.Bottom-0.5, + TabTitle.Slot.Right + spacing, TabTitle.Slot.Bottom-0.5); + gr.LineTo (Slot.Width-0.5, TabTitle.Slot.Bottom-0.5); + + + gr.LineTo (Slot.Width-0.5, Slot.Height-0.5); + gr.LineTo (0.5, Slot.Height-0.5); + gr.ClosePath (); + gr.LineWidth = 2; + Foreground.SetAsSource (gr); + gr.StrokePreserve (); + + gr.Clip (); + base.onDraw (gr); + gr.Restore (); + } + + #region Mouse Handling + public override bool MouseIsIn (Point m) + { + if (!Visible) + return false; + + bool mouseIsInTitle = TabTitle.ScreenCoordinates (TabTitle.Slot).ContainsOrIsEqual (m); + if (!IsSelected) + return mouseIsInTitle; + + return _contentContainer.ScreenCoordinates (_contentContainer.Slot).ContainsOrIsEqual (m) + || mouseIsInTitle; + } + bool holdCursor = false; + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + base.onMouseDown (sender, e); + holdCursor = true; + } + public override void onMouseUp (object sender, MouseButtonEventArgs e) + { + base.onMouseUp (sender, e); + holdCursor = false; + (Parent as TabView).UpdateLayout (LayoutingType.ArrangeChildren); + } + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + base.onMouseMove (sender, e); + + if (!(HasFocus&&holdCursor)) + return; + TabView tv = Parent as TabView; + TabItem previous = null, next = null; + int tmp = TabOffset + e.XDelta; + if (tmp < tv.Spacing) + TabOffset = tv.Spacing; + else if (tmp > Parent.getSlot ().Width - TabTitle.Slot.Width - tv.Spacing) + TabOffset = Parent.getSlot ().Width - TabTitle.Slot.Width - tv.Spacing; + else{ + int idx = tv.Children.IndexOf (this); + if (idx > 0 && e.XDelta < 0) { + previous = tv.Children [idx - 1] as TabItem; + + if (tmp < previous.TabOffset + previous.TabTitle.Slot.Width / 2) { + tv.Children.RemoveAt (idx); + tv.Children.Insert (idx - 1, this); + tv.SelectedTab = idx - 1; + tv.UpdateLayout (LayoutingType.ArrangeChildren); + } + + }else if (idx < tv.Children.Count - 1 && e.XDelta > 0) { + next = tv.Children [idx + 1] as TabItem; + if (tmp > next.TabOffset - next.TabTitle.Slot.Width / 2){ + tv.Children.RemoveAt (idx); + tv.Children.Insert (idx + 1, this); + tv.SelectedTab = idx + 1; + tv.UpdateLayout (LayoutingType.ArrangeChildren); + } + } + TabOffset = tmp; + } + } + public void butCloseTabClick (object sender, MouseButtonEventArgs e){ + (Parent as TabView).RemoveChild(this); + } + #endregion + + } +} + diff --git a/src/GraphicObjects/Base/TabView.cs b/src/GraphicObjects/Base/TabView.cs new file mode 100644 index 00000000..ecf38f10 --- /dev/null +++ b/src/GraphicObjects/Base/TabView.cs @@ -0,0 +1,210 @@ +// +// TabView.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; +using Cairo; +using System.Diagnostics; + +namespace Crow +{ + public class TabView : Group + { + #region CTOR + public TabView () : base() {} + #endregion + + #region Private fields + int _spacing; + Measure tabThickness; + Orientation _orientation; + int selectedTab = 0; + #endregion + + + #region public properties + [XmlAttributeAttribute()][DefaultValue(Orientation.Horizontal)] + public virtual Orientation Orientation + { + get { return _orientation; } + set { + if (_orientation == value) + return; + _orientation = value; + NotifyValueChanged ("Orientation", _orientation); + if (_orientation == Orientation.Horizontal) + NotifyValueChanged ("TabOrientation", Orientation.Vertical); + else + NotifyValueChanged ("TabOrientation", Orientation.Horizontal); + } + } + [XmlAttributeAttribute()][DefaultValue(16)] + public int Spacing + { + get { return _spacing; } + set { + if (_spacing == value) + return; + _spacing = value; + NotifyValueChanged ("Spacing", Spacing); + } + } + [XmlAttributeAttribute()][DefaultValue(0)] + public virtual int SelectedTab { + get { return selectedTab; } + set { + if (selectedTab < Children.Count && SelectedTab >= 0) + (Children [selectedTab] as TabItem).IsSelected = false; + + selectedTab = value; + + if (selectedTab < Children.Count && SelectedTab >= 0) + (Children [selectedTab] as TabItem).IsSelected = true; + + NotifyValueChanged ("SelectedTab", selectedTab); + RegisterForRedraw (); + } + } + #endregion + + public override void AddChild (GraphicObject child) + { + TabItem ti = child as TabItem; + if (ti == null) + throw new Exception ("TabView control accept only TabItem as child."); + + ti.MouseDown += Ti_MouseDown; + + if (Children.Count == 0) { + ti.IsSelected = true; + SelectedTab = 0; + } + + base.AddChild (child); + } + public override void RemoveChild (GraphicObject child) + { + base.RemoveChild (child); + if (selectedTab > Children.Count - 1) + SelectedTab--; + else + SelectedTab = selectedTab; + } + public override bool ArrangeChildren { get { return true; } } + public override bool UpdateLayout (LayoutingType layoutType) + { + RegisteredLayoutings &= (~layoutType); + + if (layoutType == LayoutingType.ArrangeChildren) { + int curOffset = Spacing; + for (int i = 0; i < Children.Count; i++) { + if (!Children [i].Visible) + continue; + TabItem ti = Children [i] as TabItem; + ti.TabOffset = curOffset; + if (Orientation == Orientation.Horizontal) { + if (ti.TabTitle.RegisteredLayoutings.HasFlag (LayoutingType.Width)) + return false; + curOffset += ti.TabTitle.Slot.Width + Spacing; + } else { + if (ti.TabTitle.RegisteredLayoutings.HasFlag (LayoutingType.Height)) + return false; + curOffset += ti.TabTitle.Slot.Height + Spacing; + } + } + + //if no layouting remains in queue for item, registre for redraw + if (RegisteredLayoutings == LayoutingType.None && IsDirty) + CurrentInterface.EnqueueForRepaint (this); + + return true; + } + + return base.UpdateLayout(layoutType); + } + protected override void onDraw (Context gr) + { + Rectangle rBack = new Rectangle (Slot.Size); + + Background.SetAsSource (gr, rBack); + CairoHelpers.CairoRectangle(gr,rBack, CornerRadius); + gr.Fill (); + + gr.Save (); + + if (ClipToClientRect) { + //clip to client zone + CairoHelpers.CairoRectangle (gr, ClientRectangle, CornerRadius); + gr.Clip (); + } + + for (int i = 0; i < Children.Count; i++) { + if (i == SelectedTab) + continue; + Children [i].Paint (ref gr); + } + + if (SelectedTab < Children.Count && SelectedTab >= 0) + Children [SelectedTab].Paint (ref gr); + + gr.Restore (); + } + + #region Mouse handling + public override void checkHoverWidget (MouseMoveEventArgs e) + { + if (CurrentInterface.HoverWidget != this) { + CurrentInterface.HoverWidget = this; + onMouseEnter (this, e); + } + + if (SelectedTab > Children.Count - 1) + return; + + if (((Children[SelectedTab] as TabItem).Content.Parent as GraphicObject).MouseIsIn(e.Position)) + { + Children[SelectedTab].checkHoverWidget (e); + return; + } + for (int i = Children.Count - 1; i >= 0; i--) { + TabItem ti = Children [i] as TabItem; + if (ti.TabTitle.MouseIsIn(e.Position)) + { + Children[i].checkHoverWidget (e); + return; + } + } + } + #endregion + + void Ti_MouseDown (object sender, MouseButtonEventArgs e) + { + SelectedTab = Children.IndexOf (sender as GraphicObject); + } + } +} + diff --git a/src/GraphicObjects/Base/TemplatedContainer.cs b/src/GraphicObjects/Base/TemplatedContainer.cs new file mode 100644 index 00000000..1ab8cf7d --- /dev/null +++ b/src/GraphicObjects/Base/TemplatedContainer.cs @@ -0,0 +1,128 @@ +// +// TemplatedContainer.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.Xml; +using System.Reflection; + +namespace Crow +{ + public abstract class TemplatedContainer : TemplatedControl + { + #region CTOR + public TemplatedContainer () : base(){} + #endregion + + [XmlAttributeAttribute]public virtual GraphicObject Content{ get; set;} + + #region GraphicObject overrides + public override GraphicObject FindByName (string nameToFind) + { + if (Name == nameToFind) + return this; + + return Content == null ? null : Content.FindByName (nameToFind); + } + public override bool Contains (GraphicObject goToFind) + { + if (Content == null) + return base.Contains (goToFind); + + if (Content == goToFind) + return true; + return Content.Contains (goToFind); + } + #endregion + + #region IXmlSerialisation Overrides + public override void ReadXml(System.Xml.XmlReader reader) + { + using (System.Xml.XmlReader subTree = reader.ReadSubtree ()) { + subTree.Read (); + string tmp = subTree.ReadOuterXml (); + + //seek for template tag + using (XmlReader xr = new XmlTextReader (tmp, XmlNodeType.Element, null)) { + xr.Read (); + base.ReadXml (xr); + } + //process content + using (XmlReader xr = new XmlTextReader (tmp, XmlNodeType.Element, null)) { + xr.Read (); //skip current node + + while (!xr.EOF) { + xr.Read (); //read first child + + if (!xr.IsStartElement ()) + continue; + + if (xr.Name == "Template"){ + xr.Skip (); + if (!xr.IsStartElement ()) + continue; + } + + Type t = Type.GetType ("Crow." + xr.Name); + if (t == null) { + Assembly a = Assembly.GetEntryAssembly (); + foreach (Type expT in a.GetExportedTypes ()) { + if (expT.Name == xr.Name) { + t = expT; + break; + } + } + } + if (t == null) + throw new Exception (xr.Name + " type not found"); + + GraphicObject go = (GraphicObject)Activator.CreateInstance (t); + + (go as IXmlSerializable).ReadXml (xr); + + Content = go; + + xr.Read (); //closing tag + } + + } + } + } + public override void WriteXml(System.Xml.XmlWriter writer) + { + base.WriteXml(writer); + + if (Content == null) + return; + //TODO: if template is not the default one, we have to save it + writer.WriteStartElement(Content.GetType().Name); + (Content as IXmlSerializable).WriteXml(writer); + writer.WriteEndElement(); + } + #endregion + } +} + diff --git a/src/GraphicObjects/Base/TemplatedControl.cs b/src/GraphicObjects/Base/TemplatedControl.cs new file mode 100644 index 00000000..082d0426 --- /dev/null +++ b/src/GraphicObjects/Base/TemplatedControl.cs @@ -0,0 +1,210 @@ +// +// TemplatedControl.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; +using System.IO; +using System.Xml; +using System.Diagnostics; +using System.Linq; +using System.Collections.Generic; +using System.Text; +using System.Reflection; + +namespace Crow +{ + public abstract class TemplatedControl : PrivateContainer, IXmlSerializable + { + #region CTOR + public TemplatedControl () : base() + { + } + #endregion + + string _template; + string caption; + + [XmlAttributeAttribute][DefaultValue(null)] + public string Template { + get { return _template; } + set { + if (Template == value) + return; + _template = value; + + if (string.IsNullOrEmpty(_template)) + loadTemplate (); + else + loadTemplate (CurrentInterface.Load (_template)); + } + } + [XmlAttributeAttribute()][DefaultValue("Templated Control")] + public virtual string Caption { + get { return caption; } + set { + if (caption == value) + return; + caption = value; + NotifyValueChanged ("Caption", caption); + } + } + #region GraphicObject overrides + public override void Initialize () + { + loadTemplate (); + base.Initialize (); + } + public override GraphicObject FindByName (string nameToFind) + { + //prevent name searching in template + return nameToFind == this.Name ? this : null; + } + protected override void onDraw (Cairo.Context gr) + { + //onDraw is overrided to prevent default drawing of background, template top container + //may have a binding to root background or a fixed one. + //this allow applying root background to random template's component + gr.Save (); + + if (ClipToClientRect) { + //clip to client zone + CairoHelpers.CairoRectangle (gr, ClientRectangle, CornerRadius); + gr.Clip (); + } + + if (child != null) + child.Paint (ref gr); + gr.Restore (); + } + #endregion + + protected virtual void loadTemplate(GraphicObject template = null) + { + if (template == null) { + if (!Interface.DefaultTemplates.ContainsKey (this.GetType ().FullName)) + throw new Exception (string.Format ("No default template found for '{0}'", this.GetType ().FullName)); + this.SetChild (CurrentInterface.Load (Interface.DefaultTemplates[this.GetType ().FullName])); + }else + this.SetChild (template); + } + + //TODO:IXmlSerializable is not used anymore + #region IXmlSerializable + public override System.Xml.Schema.XmlSchema GetSchema(){ return null; } + public override void ReadXml(System.Xml.XmlReader reader) + { + //Template could be either an attribute containing path or expressed inlined + //as a Template Element + using (System.Xml.XmlReader subTree = reader.ReadSubtree()) + { + subTree.Read (); + + string template = reader.GetAttribute ("Template"); + string tmp = subTree.ReadOuterXml (); + + //Load template from path set as attribute in templated control + if (string.IsNullOrEmpty (template)) { + //seek for template tag first + using (XmlReader xr = new XmlTextReader (tmp, XmlNodeType.Element, null)) { + //load template first if inlined + + xr.Read (); //read first child + xr.Read (); //skip root node + + while (!xr.EOF) { + if (!xr.IsStartElement ()) { + xr.Read (); + continue; + } + if (xr.Name == "ItemTemplate") { + string dataType = "default", datas = "", itemTmp; + while (xr.MoveToNextAttribute ()) { + if (xr.Name == "DataType") + dataType = xr.Value; + else if (xr.Name == "Data") + datas = xr.Value; + } + xr.MoveToElement (); + itemTmp = xr.ReadInnerXml (); + +// if (ItemTemplates == null) +// ItemTemplates = new Dictionary<string, ItemTemplate> (); +// +// using (IMLReader iTmp = new IMLReader (null, itemTmp)) { +// ItemTemplates [dataType] = +// new ItemTemplate (iTmp.RootType, iTmp.GetLoader (), dataType, datas); +// } +// if (!string.IsNullOrEmpty (datas)) +// ItemTemplates [dataType].CreateExpandDelegate(this); + + continue; + } + if (xr.Name == "Template") { + xr.Read (); + + Type t = Type.GetType ("Crow." + xr.Name); + if (t == null) { + Assembly a = Assembly.GetEntryAssembly (); + foreach (Type expT in a.GetExportedTypes ()) { + if (expT.Name == xr.Name) { + t = expT; + break; + } + } + } + GraphicObject go = (GraphicObject)Activator.CreateInstance (t); + (go as IXmlSerializable).ReadXml (xr); + + loadTemplate (go); + continue; + } + xr.ReadInnerXml (); + } + } + } else + loadTemplate (CurrentInterface.Load (template)); + + //if no template found, load default one + if (this.child == null) + loadTemplate (); + + //normal xml read + using (XmlReader xr = new XmlTextReader (tmp, XmlNodeType.Element, null)) { + xr.Read (); + base.ReadXml(xr); + } + } + } + public override void WriteXml(System.Xml.XmlWriter writer) + { + //TODO: + throw new NotImplementedException(); + } + #endregion + } +} + diff --git a/src/GraphicObjects/Base/TemplatedGroup.cs b/src/GraphicObjects/Base/TemplatedGroup.cs new file mode 100644 index 00000000..e4447168 --- /dev/null +++ b/src/GraphicObjects/Base/TemplatedGroup.cs @@ -0,0 +1,489 @@ +// +// TemplatedGroup.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.Xml; +using System.Reflection; +using System.Collections.Generic; +using System.ComponentModel; +using System.Collections; +using System.Threading; +using System.Linq; + +namespace Crow +{ + public abstract class TemplatedGroup : TemplatedControl + { + #region CTOR + public TemplatedGroup () : base(){} + #endregion + + protected Group items; + string _itemTemplate, _dataTest; + + #region events + public event EventHandler<SelectionChangeEventArgs> SelectedItemChanged; + public event EventHandler Loaded; + #endregion + + IList data; + int _selectedIndex = -1; + Color selBackground, selForeground; + + int itemPerPage = 50; + CrowThread loadingThread = null; + volatile bool cancelLoading = false; + + bool isPaged = false; + + #region Templating + //TODO: dont instantiate ItemTemplates if not used + //but then i should test if null in msil gen + public Dictionary<string, ItemTemplate> ItemTemplates = new Dictionary<string, Crow.ItemTemplate>(); + + /// <summary> + /// Default item template + /// </summary> + [XmlAttributeAttribute][DefaultValue("#Crow.Templates.ItemTemplate.goml")] + public string ItemTemplate { + get { return _itemTemplate; } + set { + if (value == _itemTemplate) + return; + + _itemTemplate = value; + + //TODO:reload list with new template? + NotifyValueChanged("ItemTemplate", _itemTemplate); + } + } + protected override void loadTemplate(GraphicObject template = null) + { + base.loadTemplate (template); + + items = this.child.FindByName ("ItemsContainer") as Group; + if (items == null) + throw new Exception ("TemplatedGroup template Must contain a Group named 'ItemsContainer'"); + if (items.Children.Count == 0) + NotifyValueChanged ("HasChildren", false); + else + NotifyValueChanged ("HasChildren", true); + } + /// <summary> + /// Use to define condition on Data item for selecting among ItemTemplates. + /// Default value is 'TypeOf' for selecting Template depending on Type of Data. + /// Other possible values are properties of Data + /// </summary> + /// <value>The data property test.</value> + [XmlAttributeAttribute][DefaultValue("TypeOf")] + public string DataTest { + get { return _dataTest; } + set { + if (value == _dataTest) + return; + + _dataTest = value; + + NotifyValueChanged("DataTest", _dataTest); + } + } + #endregion + + public virtual List<GraphicObject> Items{ + get { + return isPaged ? items.Children.SelectMany(x => (x as Group).Children).ToList() + : items.Children; + } + } + [XmlAttributeAttribute][DefaultValue(-1)]public int SelectedIndex{ + get { return _selectedIndex; } + set { + if (value == _selectedIndex) + return; + + if (_selectedIndex >= 0 && Items.Count > _selectedIndex) { + Items[_selectedIndex].Foreground = Color.Transparent; + Items[_selectedIndex].Background = Color.Transparent; + } + + _selectedIndex = value; + + if (_selectedIndex >= 0 && Items.Count > _selectedIndex) { + Items[_selectedIndex].Foreground = SelectionForeground; + Items[_selectedIndex].Background = SelectionBackground; + } + + NotifyValueChanged ("SelectedIndex", _selectedIndex); + NotifyValueChanged ("SelectedItem", SelectedItem); + SelectedItemChanged.Raise (this, new SelectionChangeEventArgs (SelectedItem)); + } + } + [XmlIgnore]public virtual object SelectedItem{ + get { return data == null ? null : _selectedIndex < 0 ? null : data[_selectedIndex]; } + } + [XmlIgnore]public bool HasItems { + get { return Items.Count > 0; } + } + [XmlAttributeAttribute]public IList Data { + get { return data; } + set { + if (value == data) + return; + + cancelLoadingThread (); + + data = value; + + NotifyValueChanged ("Data", data); + + lock (CurrentInterface.LayoutMutex) + ClearItems (); + + if (data == null) + return; + + loadingThread = new CrowThread (this, loading); + loadingThread.Finished += (object sender, EventArgs e) => (sender as TemplatedGroup).Loaded.Raise (sender, e); + loadingThread.Start (); + + NotifyValueChanged ("SelectedIndex", _selectedIndex); + NotifyValueChanged ("SelectedItem", SelectedItem); + SelectedItemChanged.Raise (this, new SelectionChangeEventArgs (SelectedItem)); + NotifyValueChanged ("HasItems", HasItems); + } + } + + [XmlAttributeAttribute][DefaultValue("SteelBlue")] + public virtual Color SelectionBackground { + get { return selBackground; } + set { + if (value == selBackground) + return; + selBackground = value; + NotifyValueChanged ("SelectionBackground", selBackground); + RegisterForRedraw (); + } + } + [XmlAttributeAttribute][DefaultValue("White")] + public virtual Color SelectionForeground { + get { return selForeground; } + set { + if (value == selForeground) + return; + selForeground = value; + NotifyValueChanged ("SelectionForeground", selForeground); + RegisterForRedraw (); + } + } + + protected void raiseSelectedItemChanged(){ + SelectedItemChanged.Raise (this, new SelectionChangeEventArgs (SelectedItem)); + } + + + public virtual void AddItem(GraphicObject g){ + items.AddChild (g); + g.LogicalParent = this; + NotifyValueChanged ("HasChildren", true); + } + public virtual void RemoveItem(GraphicObject g) + { + g.LogicalParent = null; + items.RemoveChild (g); + if (items.Children.Count == 0) + NotifyValueChanged ("HasChildren", false); + } + + public virtual void ClearItems() + { + _selectedIndex = -1; + NotifyValueChanged ("SelectedIndex", _selectedIndex); + NotifyValueChanged ("SelectedItem", null); + + items.ClearChildren (); + NotifyValueChanged ("HasChildren", false); + } + + + #region GraphicObject overrides + public override GraphicObject FindByName (string nameToFind) + { + if (Name == nameToFind) + return this; + + foreach (GraphicObject w in Items) { + GraphicObject r = w.FindByName (nameToFind); + if (r != null) + return r; + } + return null; + } + public override bool Contains (GraphicObject goToFind) + { + foreach (GraphicObject w in Items) { + if (w == goToFind) + return true; + if (w.Contains (goToFind)) + return true; + } + return base.Contains(goToFind); + } +// public override void ClearBinding () +// { +// if (items != null) +// items.ClearBinding (); +// +// base.ClearBinding (); +// } +// public override void ResolveBindings () +// { +// base.ResolveBindings (); +// if (items != null) +// items.ResolveBindings (); +// } + #endregion + + #region IXmlSerialisation Overrides + public override void ReadXml(System.Xml.XmlReader reader) + { + using (System.Xml.XmlReader subTree = reader.ReadSubtree ()) { + subTree.Read (); + string tmp = subTree.ReadOuterXml (); + + //seek for template tag + using (XmlReader xr = new XmlTextReader (tmp, XmlNodeType.Element, null)) { + xr.Read (); + base.ReadXml (xr); + } + //process content + using (XmlReader xr = new XmlTextReader (tmp, XmlNodeType.Element, null)) { + xr.Read (); //skip current node + + while (!xr.EOF) { + xr.Read (); //read first child + + if (!xr.IsStartElement ()) + continue; + + if (xr.Name == "Template" || Name == "ItemTemplate"){ + xr.Skip (); + if (!xr.IsStartElement ()) + continue; + } + + Type t = Type.GetType ("Crow." + xr.Name); + if (t == null) { + Assembly a = Assembly.GetEntryAssembly (); + foreach (Type expT in a.GetExportedTypes ()) { + if (expT.Name == xr.Name) { + t = expT; + break; + } + } + } + if (t == null) + throw new Exception (xr.Name + " type not found"); + + GraphicObject go = (GraphicObject)Activator.CreateInstance (t); + + (go as IXmlSerializable).ReadXml (xr); + + AddItem (go); + + xr.Read (); //closing tag + } + + } + } + } + public override void WriteXml(System.Xml.XmlWriter writer) + { + throw new NotImplementedException (); + } + #endregion + + void loading(){ + if (ItemTemplates == null) + ItemTemplates = new Dictionary<string, ItemTemplate> (); + if (!ItemTemplates.ContainsKey ("default")) + ItemTemplates ["default"] = Interface.GetItemTemplate (ItemTemplate); + + for (int i = 1; i <= (data.Count / itemPerPage) + 1; i++) { + if (cancelLoading) + return; + loadPage (i); + Thread.Sleep (1); + } + } + void cancelLoadingThread(){ + if (loadingThread != null) + loadingThread.Cancel (); + } + void loadPage(int pageNum) + { + #if DEBUG_LOAD + Stopwatch loadingTime = new Stopwatch (); + loadingTime.Start (); + #endif + + Group page; + if (typeof(Wrapper).IsAssignableFrom (items.GetType ())) { + page = items; + itemPerPage = int.MaxValue; + } else if (typeof(GenericStack).IsAssignableFrom (items.GetType ())) { + GenericStack gs = new GenericStack (); + gs.CurrentInterface = items.CurrentInterface; + gs.Initialize (); + gs.Orientation = (items as GenericStack).Orientation; + gs.Width = items.Width; + gs.Height = items.Height; + gs.VerticalAlignment = items.VerticalAlignment; + gs.HorizontalAlignment = items.HorizontalAlignment; + page = gs; + page.Name = "page" + pageNum; + isPaged = true; + } else { + page = Activator.CreateInstance (items.GetType ()) as Group; + page.Name = "page" + pageNum; + isPaged = true; + } + + for (int i = (pageNum - 1) * itemPerPage; i < pageNum * itemPerPage; i++) { + if (i >= data.Count) + break; + if (cancelLoading) + return; + + loadItem (i, page); + } + + if (page == items) + return; + lock (CurrentInterface.LayoutMutex) + items.AddChild (page); + + #if DEBUG_LOAD + loadingTime.Stop (); + Debug.WriteLine("Listbox {2} Loading: {0} ticks \t, {1} ms", + loadingTime.ElapsedTicks, + loadingTime.ElapsedMilliseconds, this.ToString()); + #endif + } + string getItempKey(Type dataType, object o){ + try { + return dataType.GetProperty (_dataTest).GetGetMethod ().Invoke (o, null).ToString(); + } catch (Exception ex) { + return dataType.FullName; + } + } + protected void loadItem(int i, Group page){ + if (data [i] == null)//TODO:surely a threading sync problem + return; + GraphicObject g = null; + ItemTemplate iTemp = null; + Type dataType = data [i].GetType (); + string itempKey = dataType.FullName; + + if (_dataTest != "TypeOf") + itempKey = getItempKey (dataType, data [i]); + + if (ItemTemplates.ContainsKey (itempKey)) + iTemp = ItemTemplates [itempKey]; + else { + foreach (string it in ItemTemplates.Keys) { + Type t = Type.GetType (it); + if (t == null) { + Assembly a = Assembly.GetEntryAssembly (); + foreach (Type expT in a.GetExportedTypes ()) { + if (expT.Name == it) { + t = expT; + break; + } + } + } + if (t == null) + continue; + if (t.IsAssignableFrom (dataType)) {//TODO:types could be cached + iTemp = ItemTemplates [it]; + break; + } + } + if (iTemp == null) + iTemp = ItemTemplates ["default"]; + } + + lock (CurrentInterface.LayoutMutex) { + g = iTemp.CreateInstance(CurrentInterface); + page.AddChild (g); + //g.LogicalParent = this; + registerItemClick (g); + } + + if (iTemp.Expand != null && g is Expandable) { + (g as Expandable).Expand += iTemp.Expand; + (g as Expandable).GetIsExpandable = iTemp.HasSubItems; + } + + g.DataSource = data [i]; + } + protected virtual void registerItemClick(GraphicObject g){ + g.MouseClick += itemClick; + } +// protected void _list_LayoutChanged (object sender, LayoutingEventArgs e) +// { +// #if DEBUG_LAYOUTING +// Debug.WriteLine("list_LayoutChanged"); +// #endif +// if (_gsList.Orientation == Orientation.Horizontal) { +// if (e.LayoutType == LayoutingType.Width) +// _gsList.Width = approxSize; +// } else if (e.LayoutType == LayoutingType.Height) +// _gsList.Height = approxSize; +// } + int approxSize + { + get { + if (data == null) + return -1; + GenericStack page1 = items.FindByName ("page1") as GenericStack; + if (page1 == null) + return -1; + + return page1.Orientation == Orientation.Horizontal ? + data.Count < itemPerPage ? + -1: + (int)Math.Ceiling ((double)page1.Slot.Width / (double)itemPerPage * (double)(data.Count+1)): + data.Count < itemPerPage ? + -1: + (int)Math.Ceiling ((double)page1.Slot.Height / (double)itemPerPage * (double)(data.Count+1)); + } + } + internal virtual void itemClick(object sender, MouseButtonEventArgs e){ + SelectedIndex = data.IndexOf((sender as GraphicObject).DataSource); + } + } +} diff --git a/src/GraphicObjects/Base/TextBox.cs b/src/GraphicObjects/Base/TextBox.cs new file mode 100644 index 00000000..0cbee9f8 --- /dev/null +++ b/src/GraphicObjects/Base/TextBox.cs @@ -0,0 +1,228 @@ +// +// TextBox.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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 Cairo; +using System.Diagnostics; +using System.Xml.Serialization; + +namespace Crow +{ + public class TextBox : Label + { + #region CTOR + public TextBox() + { } + public TextBox(string _initialValue) + : base(_initialValue) + { + + } + #endregion + + #region GraphicObject overrides + [XmlIgnore]public override bool HasFocus //trigger update when lost focus to errase text beam + { + get + { + return base.HasFocus; + } + set + { + base.HasFocus = value; + RegisterForRedraw(); + } + } + + protected override void onDraw (Context gr) + { + base.onDraw (gr); + FontExtents fe = gr.FontExtents; + } + #endregion + + #region Keyboard handling + public override void onKeyDown (object sender, KeyboardKeyEventArgs e) + { + base.onKeyDown (sender, e); + + Key key = e.Key; + + switch (key) + { + case Key.Back: + if (CurrentPosition == 0) + return; + this.DeleteChar(); + break; + case Key.Clear: + break; + case Key.Delete: + if (selectionIsEmpty) { + if (!MoveRight ()) + return; + }else if (e.Shift) + CurrentInterface.Clipboard = this.SelectedText; + this.DeleteChar (); + break; + case Key.Enter: + case Key.KeypadEnter: + if (!selectionIsEmpty) + this.DeleteChar (); + if (Multiline) + this.InsertLineBreak (); + else + OnTextChanged(this,new TextChangeEventArgs(Text)); + break; + case Key.Escape: + Text = ""; + CurrentColumn = 0; + SelRelease = -1; + break; + case Key.Home: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = new Point (CurrentColumn, CurrentLine); + if (e.Control) + CurrentLine = 0; + CurrentColumn = 0; + SelRelease = new Point (CurrentColumn, CurrentLine); + break; + } + SelRelease = -1; + if (e.Control) + CurrentLine = 0; + CurrentColumn = 0; + break; + case Key.End: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + if (e.Control) + CurrentLine = int.MaxValue; + CurrentColumn = int.MaxValue; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + if (e.Control) + CurrentLine = int.MaxValue; + CurrentColumn = int.MaxValue; + break; + case Key.Insert: + if (e.Shift) + this.Insert (CurrentInterface.Clipboard); + else if (e.Control && !selectionIsEmpty) + CurrentInterface.Clipboard = this.SelectedText; + break; + case Key.Left: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = new Point(CurrentColumn, CurrentLine); + if (e.Control) + GotoWordStart (); + else if (!MoveLeft ()) + return; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + if (e.Control) + GotoWordStart (); + else + MoveLeft(); + break; + case Key.Right: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + if (e.Control) + GotoWordEnd (); + else if (!MoveRight ()) + return; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + if (e.Control) + GotoWordEnd (); + else + MoveRight (); + break; + case Key.Up: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + CurrentLine--; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + CurrentLine--; + break; + case Key.Down: + if (e.Shift) { + if (selectionIsEmpty) + SelBegin = CurrentPosition; + CurrentLine++; + SelRelease = CurrentPosition; + break; + } + SelRelease = -1; + CurrentLine++; + break; + case Key.Menu: + break; + case Key.NumLock: + break; + case Key.PageDown: + break; + case Key.PageUp: + break; + case Key.RWin: + break; + case Key.Tab: + this.Insert ("\t"); + break; + default: + break; + } + RegisterForGraphicUpdate(); + } + public override void onKeyPress (object sender, KeyPressEventArgs e) + { + base.onKeyPress (sender, e); + + this.Insert (e.KeyChar.ToString()); + + SelRelease = -1; + SelBegin = new Point(CurrentColumn, SelBegin.Y); + + RegisterForGraphicUpdate(); + } + #endregion + } +} diff --git a/src/GraphicObjects/Base/TextRun.cs b/src/GraphicObjects/Base/TextRun.cs new file mode 100644 index 00000000..a312ca1e --- /dev/null +++ b/src/GraphicObjects/Base/TextRun.cs @@ -0,0 +1,315 @@ +// +// TextRun.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Generic; +using System.Linq; +using System.Text; +using System.Diagnostics; +using Cairo; +using System.Text.RegularExpressions; +using System.Xml.Serialization; +using System.ComponentModel; + +namespace Crow +{ + public class TextRun : GraphicObject + { + #region CTOR + public TextRun () + { + + } + public TextRun (string _text) + : base () + { + Text = _text; + } + #endregion + + //TODO:change protected to private + + #region private and protected fields + protected string _text = "label"; + Alignment _textAlignment = Alignment.Left; + bool horizontalStretch = false; + bool verticalStretch = false; + bool _multiline; + bool wordWrap; + protected Rectangle rText; + protected float widthRatio = 1f; + protected float heightRatio = 1f; + protected FontExtents fe; + protected TextExtents te; + #endregion + + + [XmlAttributeAttribute ()] + [DefaultValue (Alignment.Left)] + public Alignment TextAlignment { + get { return _textAlignment; } + set { _textAlignment = value; } + } + [XmlAttributeAttribute ()] + [DefaultValue (false)] + public virtual bool HorizontalStretch { + get { return horizontalStretch; } + set { + if (horizontalStretch == value) + return; + horizontalStretch = value; + RegisterForRedraw (); + NotifyValueChanged ("HorizontalStretch", horizontalStretch); + } + } + [XmlAttributeAttribute ()] + [DefaultValue (false)] + public virtual bool VerticalStretch { + get { return verticalStretch; } + set { + if (verticalStretch == value) + return; + verticalStretch = value; + RegisterForRedraw (); + NotifyValueChanged ("VerticalStretch", verticalStretch); + } + } + [XmlAttributeAttribute ()] + [DefaultValue ("label")] + public string Text { + get { + return lines == null ? + _text : lines.Aggregate ((i, j) => i + Interface.LineBreak + j); + } + set { + if (_text == value) + return; + + RegisterForGraphicUpdate (); + + _text = value; + + if (string.IsNullOrEmpty (_text)) + _text = ""; + + lines = getLines; + } + } + [XmlAttributeAttribute ()] + [DefaultValue (false)] + public bool Multiline { + get { return _multiline; } + set { + _multiline = value; + RegisterForGraphicUpdate (); + } + } + [XmlAttributeAttribute ()] + [DefaultValue (false)] + public bool WordWrap { + get { + return wordWrap; + } + set { + if (wordWrap == value) + return; + wordWrap = value; + RegisterForGraphicUpdate (); + } + } + + List<string> lines; + List<string> getLines { + get { + return _multiline ? + Regex.Split (_text, "\r\n|\r|\n").ToList () : + new List<string> (new string [] { _text }); + } + } + + #region GraphicObject overrides + protected override int measureRawSize (LayoutingType lt) + { + if (lines == null) + lines = getLines; + + using (ImageSurface img = new ImageSurface (Format.Argb32, 10, 10)) { + using (Context gr = new Context (img)) { + //Cairo.FontFace cf = gr.GetContextFontFace (); + + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + + + fe = gr.FontExtents; + te = new TextExtents (); + + if (lt == LayoutingType.Height) { + int lc = lines.Count; + //ensure minimal height = text line height + if (lc == 0) + lc = 1; + + return (int)(fe.Height * lc) + Margin * 2; + } + + foreach (string s in lines) { + string l = s.Replace("\t", new String (' ', Interface.TabSize)); + TextExtents tmp = gr.TextExtents (l); + if (tmp.XAdvance > te.XAdvance) + te = tmp; + } + return (int)Math.Ceiling (te.XAdvance) + Margin * 2; + } + } + } + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + gr.FontOptions = Interface.FontRenderingOptions; + gr.Antialias = Interface.Antialias; + + rText = new Rectangle (new Size ( + measureRawSize (LayoutingType.Width), measureRawSize (LayoutingType.Height))); + rText.Width -= 2 * Margin; + rText.Height -= 2 * Margin; + + widthRatio = 1f; + heightRatio = 1f; + + Rectangle cb = ClientRectangle; + + //ignore text alignment if size to content = true + //or if text size is larger than client bounds + if (Width < 0 || Height < 0 || rText.Width > cb.Width) { + rText.X = cb.X; + rText.Y = cb.Y; + } else { + if (horizontalStretch) { + widthRatio = (float)cb.Width / rText.Width; + if (!verticalStretch) + heightRatio = widthRatio; + } + if (verticalStretch) { + heightRatio = (float)cb.Height / rText.Height; + if (!horizontalStretch) + widthRatio = heightRatio; + } + + rText.Width = (int)(widthRatio * cb.Width); + rText.Height = (int)(heightRatio * cb.Height); + + switch (TextAlignment) { + case Alignment.TopLeft: //ok + rText.X = cb.X; + rText.Y = cb.Y; + break; + case Alignment.Top: //ok + rText.Y = cb.Y; + rText.X = cb.X + cb.Width / 2 - rText.Width / 2; + break; + case Alignment.TopRight: //ok + rText.Y = cb.Y; + rText.X = cb.Right - rText.Width; + break; + case Alignment.Left://ok + rText.X = cb.X; + rText.Y = cb.Y + cb.Height / 2 - rText.Height / 2; + break; + case Alignment.Right://ok + rText.X = cb.X + cb.Width - rText.Width; + rText.Y = cb.Y + cb.Height / 2 - rText.Height / 2; + break; + case Alignment.Bottom://ok + rText.X = cb.Width / 2 - rText.Width / 2; + rText.Y = cb.Height - rText.Height; + break; + case Alignment.BottomLeft://ok + rText.X = cb.X; + rText.Y = cb.Bottom - rText.Height; + break; + case Alignment.BottomRight://ok + rText.Y = cb.Bottom - rText.Height; + rText.X = cb.Right - rText.Width; + break; + case Alignment.Center://ok + rText.X = cb.X + cb.Width / 2 - rText.Width / 2; + rText.Y = cb.Y + cb.Height / 2 - rText.Height / 2; + break; + } + } + + gr.FontMatrix = new Matrix (widthRatio * Font.Size, 0, 0, heightRatio * Font.Size, 0, 0); + + + int curLineCount = 0; + for (int i = 0; i < lines.Count; i++) { + string l = lines [i].Replace ("\t", new String (' ', Interface.TabSize)); + List<string> wl = new List<string> (); + int lineLength = (int)gr.TextExtents (l).XAdvance; + + if (wordWrap && lineLength > cb.Width) { + string tmpLine = ""; + int curChar = 0; + while (curChar < l.Length) { + tmpLine += l [curChar]; + if ((int)gr.TextExtents (tmpLine).XAdvance > cb.Width) { + tmpLine = tmpLine.Remove (tmpLine.Length - 1); + wl.Add (tmpLine); + tmpLine = ""; + continue; + } + curChar++; + } + wl.Add (tmpLine); + } else + wl.Add (l); + + foreach (string ll in wl) { + lineLength = (int)gr.TextExtents (ll).XAdvance; + + + if (string.IsNullOrWhiteSpace (ll)) { + curLineCount++; + continue; + } + + Foreground.SetAsSource (gr); + gr.MoveTo (rText.X, rText.Y + fe.Ascent + fe.Height * curLineCount); + + gr.ShowText (ll); + gr.Fill (); + + curLineCount++; + } + } + } + #endregion + } +} diff --git a/src/GraphicObjects/Base/TreeView.cs b/src/GraphicObjects/Base/TreeView.cs new file mode 100644 index 00000000..6549f148 --- /dev/null +++ b/src/GraphicObjects/Base/TreeView.cs @@ -0,0 +1,120 @@ +// +// TreeView.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.Diagnostics; +using System.ComponentModel; + +namespace Crow +{ + //treeview expect expandable child (or not) + //if their are expandable, some functions and events are added + public class TreeView : TemplatedGroup + { + GraphicObject selectedItemContainer = null; + bool isRoot; + + #region CTOR + public TreeView () : base() + { + } + #endregion + + [XmlAttributeAttribute()][DefaultValue(false)] + public virtual bool IsRoot { + get { return isRoot; } + set { + if (isRoot == value) + return; + isRoot = value; + NotifyValueChanged ("IsRoot", isRoot); + } + } + [XmlIgnore]public override object SelectedItem { + get { + return selectedItemContainer == null ? + "" : selectedItemContainer.DataSource; + } + } + + protected override void registerItemClick (GraphicObject g) + { + //register ItemClick on the Root node + TreeView tv = this as TreeView; + while (!tv.IsRoot) { + ILayoutable tmp = tv.Parent; + while (!(tmp is TreeView)) { + tmp = tmp.Parent; + } + tv = tmp as TreeView; + } + g.MouseClick += tv.itemClick; + } + internal override void itemClick (object sender, MouseButtonEventArgs e) + { + GraphicObject tmp = sender as GraphicObject; + if (!tmp.HasFocus) + return; + if (selectedItemContainer != null) { + selectedItemContainer.Foreground = Color.Transparent; + selectedItemContainer.Background = Color.Transparent; + } + selectedItemContainer = tmp; + selectedItemContainer.Foreground = SelectionForeground; + selectedItemContainer.Background = SelectionBackground; + NotifyValueChanged ("SelectedItem", SelectedItem); + raiseSelectedItemChanged (); + } + + void onExpandAll_MouseClick (object sender, MouseButtonEventArgs e) + { + ExpandAll (); + } + + public void ExpandAll(){ + foreach (Group grp in items.Children) { + foreach (GraphicObject go in grp.Children) { + Expandable exp = go as Expandable; + if (exp == null) + continue; + TreeView subTV = exp.FindByName ("List") as TreeView; + if (subTV == null) + continue; + EventHandler handler = null; + handler = delegate(object sender, EventArgs e) { + TreeView tv = sender as TreeView; + tv.Loaded -= handler; + tv.ExpandAll (); + }; + subTV.Loaded += handler; + exp.IsExpanded = true; + } + } + } + } +} + diff --git a/src/GraphicObjects/Base/VerticalStack.cs b/src/GraphicObjects/Base/VerticalStack.cs new file mode 100644 index 00000000..d5d0236b --- /dev/null +++ b/src/GraphicObjects/Base/VerticalStack.cs @@ -0,0 +1,49 @@ +// +// VerticalStack.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Generic; +using System.Linq; +using System.Text; + +namespace Crow +{ + public class VerticalStack : GenericStack + { + public VerticalStack() + : base() + { + } + + [System.Xml.Serialization.XmlIgnore] + public override Orientation Orientation + { + get { return Orientation.Vertical; } + } + + + } +} diff --git a/src/GraphicObjects/Base/Window.cs b/src/GraphicObjects/Base/Window.cs new file mode 100644 index 00000000..e2820528 --- /dev/null +++ b/src/GraphicObjects/Base/Window.cs @@ -0,0 +1,399 @@ +// +// Window.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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.Xml.Serialization; +using System.ComponentModel; +using System.Diagnostics; + +namespace Crow +{ + public class Window : TemplatedContainer + { + enum Direction + { + None, + N, + S, + E, + W, + NW, + NE, + SW, + SE, + } + + string _icon; + bool _resizable; + bool _movable; + bool hoverBorder = false; + bool alwaysOnTop = false; + + Rectangle savedBounds; + bool _minimized = false; + + Container _contentContainer; + Direction currentDirection = Direction.None; + + #region Events + public event EventHandler Closing; + public event EventHandler Maximized; + public event EventHandler Unmaximized; + public event EventHandler Minimize; + #endregion + + #region CTOR + public Window () : base() { + + } + #endregion + + #region TemplatedContainer overrides + public override GraphicObject Content { + get { return _contentContainer == null ? null : _contentContainer.Child; } + set { _contentContainer.SetChild(value); } + } + protected override void loadTemplate(GraphicObject template = null) + { + base.loadTemplate (template); + _contentContainer = this.child.FindByName ("Content") as Container; + + NotifyValueChanged ("ShowNormal", false); + NotifyValueChanged ("ShowMinimize", true); + NotifyValueChanged ("ShowMaximize", true); + } + #endregion + + #region public properties + [XmlAttributeAttribute][DefaultValue("#Crow.Images.Icons.crow.png")] + public string Icon { + get { return _icon; } + set { + if (_icon == value) + return; + _icon = value; + NotifyValueChanged ("Icon", _icon); + } + } + [XmlAttributeAttribute][DefaultValue(true)] + public bool Resizable { + get { + return _resizable; + } + set { + if (_resizable == value) + return; + _resizable = value; + NotifyValueChanged ("Resizable", _resizable); + } + } + [XmlAttributeAttribute][DefaultValue(true)] + public bool Movable { + get { + return _movable; + } + set { + if (_movable == value) + return; + _movable = value; + NotifyValueChanged ("Movable", _movable); + } + } + [XmlAttributeAttribute][DefaultValue(false)] + public bool IsMinimized { + get { return _minimized; } + set{ + if (value == IsMinimized) + return; + + _minimized = value; + _contentContainer.Visible = !_minimized; + + NotifyValueChanged ("IsMinimized", _minimized); + } + } + [XmlIgnore]public bool IsMaximized { + get { return Width == Measure.Stretched & Height == Measure.Stretched & !_minimized; } + } + [XmlIgnore]public bool IsNormal { + get { return !(IsMaximized|_minimized); } + } + [XmlAttributeAttribute][DefaultValue(false)] + public bool AlwaysOnTop { + get { + return alwaysOnTop; + } + set { + if (alwaysOnTop == value) + return; + alwaysOnTop = value; + if (alwaysOnTop) { + CurrentInterface.PutOnTop (this); + CurrentInterface.TopWindows++; + }else + CurrentInterface.TopWindows--; + NotifyValueChanged ("AlwaysOnTop", alwaysOnTop); + } + } +// [XmlAttributeAttribute()][DefaultValue(WindowState.Normal)] +// public virtual WindowState State { +// get { return _state; } +// set { +// if (_state == value) +// return; +// _state = value; +// NotifyValueChanged ("State", _state); +// NotifyValueChanged ("IsNormal", IsNormal); +// NotifyValueChanged ("IsMaximized", IsMaximized); +// NotifyValueChanged ("IsMinimized", IsMinimized); +// NotifyValueChanged ("IsNotMinimized", IsNotMinimized); +// } +// } + #endregion + + #region GraphicObject Overrides + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + base.onMouseMove (sender, e); + + Interface otkgw = CurrentInterface; + + if (!hoverBorder) { + currentDirection = Direction.None; + CurrentInterface.MouseCursor = XCursor.Default; + return; + } + + if (this.HasFocus && _movable) { + if (e.Mouse.IsButtonDown (MouseButton.Left)) { + int currentLeft = this.Left; + int currentTop = this.Top; + int currentWidth, currentHeight; + + if (currentLeft == 0) { + currentLeft = this.Slot.Left; + this.Left = currentLeft; + } + if (currentTop == 0) { + currentTop = this.Slot.Top; + this.Top = currentTop; + } + if (this.Width.IsFixed) + currentWidth = this.Width; + else + currentWidth = this.Slot.Width; + + if (this.Height.IsFixed) + currentHeight = this.Height; + else + currentHeight = this.Slot.Height; + + switch (currentDirection) { + case Direction.None: + this.Left = currentLeft + e.XDelta; + this.Top = currentTop + e.YDelta; + break; + case Direction.N: + this.Height = currentHeight - e.YDelta; + if (this.Height == currentHeight - e.YDelta) + this.Top = currentTop + e.YDelta; + break; + case Direction.S: + this.Height = currentHeight + e.YDelta; + break; + case Direction.W: + this.Width = currentWidth - e.XDelta; + if (this.Width == currentWidth - e.XDelta) + this.Left = currentLeft + e.XDelta; + break; + case Direction.E: + this.Width = currentWidth + e.XDelta; + break; + case Direction.NW: + this.Height = currentHeight - e.YDelta; + if (this.Height == currentHeight - e.YDelta) + this.Top = currentTop + e.YDelta; + this.Width = currentWidth - e.XDelta; + if (this.Width == currentWidth - e.XDelta) + this.Left = currentLeft + e.XDelta; + break; + case Direction.NE: + this.Height = currentHeight - e.YDelta; + if (this.Height == currentHeight - e.YDelta) + this.Top = currentTop + e.YDelta; + this.Width = currentWidth + e.XDelta; + break; + case Direction.SW: + this.Width = currentWidth - e.XDelta; + if (this.Width == currentWidth - e.XDelta) + this.Left = currentLeft + e.XDelta; + this.Height = currentHeight + e.YDelta; + break; + case Direction.SE: + this.Height = currentHeight + e.YDelta; + this.Width = currentWidth + e.XDelta; + break; + } + return; + } + } + if (Resizable) { + Direction lastDir = currentDirection; + + if (Math.Abs (e.Position.Y - this.Slot.Y) < Interface.BorderThreshold) { + if (Math.Abs (e.Position.X - this.Slot.X) < Interface.BorderThreshold) + currentDirection = Direction.NW; + else if (Math.Abs (e.Position.X - this.Slot.Right) < Interface.BorderThreshold) + currentDirection = Direction.NE; + else + currentDirection = Direction.N; + } else if (Math.Abs (e.Position.Y - this.Slot.Bottom) < Interface.BorderThreshold) { + if (Math.Abs (e.Position.X - this.Slot.X) < Interface.BorderThreshold) + currentDirection = Direction.SW; + else if (Math.Abs (e.Position.X - this.Slot.Right) < Interface.BorderThreshold) + currentDirection = Direction.SE; + else + currentDirection = Direction.S; + } else if (Math.Abs (e.Position.X - this.Slot.X) < Interface.BorderThreshold) + currentDirection = Direction.W; + else if (Math.Abs (e.Position.X - this.Slot.Right) < Interface.BorderThreshold) + currentDirection = Direction.E; + else + currentDirection = Direction.None; + + if (currentDirection != lastDir) { + switch (currentDirection) { + case Direction.None: + otkgw.MouseCursor = XCursor.Default; + break; + case Direction.N: + otkgw.MouseCursor = XCursor.V; + break; + case Direction.S: + otkgw.MouseCursor = XCursor.V; + break; + case Direction.E: + otkgw.MouseCursor = XCursor.H; + break; + case Direction.W: + otkgw.MouseCursor = XCursor.H; + break; + case Direction.NW: + otkgw.MouseCursor = XCursor.NW; + break; + case Direction.NE: + otkgw.MouseCursor = XCursor.NE; + break; + case Direction.SW: + otkgw.MouseCursor = XCursor.SW; + break; + case Direction.SE: + otkgw.MouseCursor = XCursor.SE; + break; + } + } + } + } + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + base.onMouseDown (sender, e); + } + #endregion + + protected void onMaximized (object sender, EventArgs e){ + lock (CurrentInterface.LayoutMutex) { + if (!IsMinimized) + savedBounds = this.LastPaintedSlot; + this.Left = this.Top = 0; + this.RegisterForLayouting (LayoutingType.Positioning); + this.Width = this.Height = Measure.Stretched; + IsMinimized = false; + Resizable = false; + NotifyValueChanged ("ShowNormal", true); + NotifyValueChanged ("ShowMinimize", true); + NotifyValueChanged ("ShowMaximize", false); + } + + Maximized.Raise (sender, e); + + + + } + protected void onUnmaximized (object sender, EventArgs e){ + lock (CurrentInterface.LayoutMutex) { + this.Left = savedBounds.Left; + this.Top = savedBounds.Top; + this.Width = savedBounds.Width; + this.Height = savedBounds.Height; + IsMinimized = false; + Resizable = true; + NotifyValueChanged ("ShowNormal", false); + NotifyValueChanged ("ShowMinimize", true); + NotifyValueChanged ("ShowMaximize", true); + } + + Unmaximized.Raise (sender, e); + } + protected void onMinimized (object sender, EventArgs e){ + lock (CurrentInterface.LayoutMutex) { + if (IsNormal) + savedBounds = this.LastPaintedSlot; + Width = 200; + Height = 20; + Resizable = false; + IsMinimized = true; + NotifyValueChanged ("ShowNormal", true); + NotifyValueChanged ("ShowMinimize", false); + NotifyValueChanged ("ShowMaximize", true); + } + + Minimize.Raise (sender, e); + } + protected void onBorderMouseLeave (object sender, MouseMoveEventArgs e) + { + hoverBorder = false; + currentDirection = Direction.None; + CurrentInterface.MouseCursor = XCursor.Default; + } + protected void onBorderMouseEnter (object sender, MouseMoveEventArgs e) + { + hoverBorder = true; + } + + + protected void butQuitPress (object sender, MouseButtonEventArgs e) + { + CurrentInterface.MouseCursor = XCursor.Default; + close (); + } + + protected void close(){ + Closing.Raise (this, null); + CurrentInterface.DeleteWidget (this); + } + } +} + diff --git a/src/GraphicObjects/Base/Wrapper.cs b/src/GraphicObjects/Base/Wrapper.cs new file mode 100644 index 00000000..8b282ebb --- /dev/null +++ b/src/GraphicObjects/Base/Wrapper.cs @@ -0,0 +1,233 @@ +// +// Wrapper.cs +// +// Author: +// Jean-Philippe Bruyère <jp.bruyere@hotmail.com> +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// 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; + +namespace Crow +{ + public class Wrapper : GenericStack + { + public Wrapper () : base() + {} + + #region Group Overrides + public override void ChildrenLayoutingConstraints (ref LayoutingType layoutType) + { + layoutType &= (~LayoutingType.Positioning); + } + public override void ComputeChildrenPositions() + { + int dx = 0; + int dy = 0; + + if (Orientation == Orientation.Vertical) { + int tallestChild = 0; + foreach (GraphicObject c in Children) { + if (!c.Visible) + continue; + if (dx + c.Slot.Width > ClientRectangle.Width) { + dx = 0; + dy += tallestChild + Spacing; + c.Slot.X = dx; + c.Slot.Y = dy; + tallestChild = c.Slot.Height; + } else { + if (tallestChild < c.Slot.Height) + tallestChild = c.Slot.Height; + c.Slot.X = dx; + c.Slot.Y = dy; + } + dx += c.Slot.Width + Spacing; + } + } else { + int largestChild = 0; + foreach (GraphicObject c in Children) { + if (!c.Visible) + continue; + if (dy + c.Slot.Height > ClientRectangle.Height) { + dy = 0; + dx += largestChild + Spacing; + c.Slot.X = dx; + c.Slot.Y = dy; + largestChild = c.Slot.Width; + } else { + if (largestChild < c.Slot.Width) + largestChild = c.Slot.Width; + c.Slot.X = dx; + c.Slot.Y = dy; + } + dy += c.Slot.Height + Spacing; + } + } + IsDirty = true; + } + public override void OnChildLayoutChanges (object sender, LayoutingEventArgs arg) + { + //children can't stretch in a wrapper + GraphicObject go = sender as GraphicObject; + //System.Diagnostics.Debug.WriteLine ("wrapper child layout change: " + go.LastSlots.ToString() + " => " + go.Slot.ToString()); + switch (arg.LayoutType) { + case LayoutingType.Width: + if (Orientation == Orientation.Horizontal && go.Width.Units == Unit.Percent){ + go.Width = Measure.Fit; + return; + } + this.RegisterForLayouting (LayoutingType.Width); + break; + case LayoutingType.Height: + if (Orientation == Orientation.Vertical && go.Height.Units == Unit.Percent) { + go.Height = Measure.Fit; + return; + } + this.RegisterForLayouting (LayoutingType.Height); + break; + default: + return; + } + this.RegisterForLayouting (LayoutingType.ArrangeChildren); + } + #endregion + + #region GraphicObject Overrides + protected override int measureRawSize (LayoutingType lt) + { + int tmp = 0; + //Wrapper can't fit in the opposite direction of the wrapper, this func is called only if Fit + if (lt == LayoutingType.Width) { + if (Orientation == Orientation.Vertical) { + Width = Measure.Stretched; + return -1; + } else if (RegisteredLayoutings.HasFlag (LayoutingType.Height)) + return -1; + else { + int dy = 0; + int largestChild = 0; + lock (Children) { + foreach (GraphicObject c in Children) { + if (!c.Visible) + continue; + if (c.Height.Units == Unit.Percent && + c.RegisteredLayoutings.HasFlag (LayoutingType.Height)) + return -1; + if (dy + c.Slot.Height > ClientRectangle.Height) { + dy = 0; + tmp += largestChild + Spacing; + largestChild = c.Slot.Width; + } else if (largestChild < c.Slot.Width) + largestChild = c.Slot.Width; + + dy += c.Slot.Height + Spacing; + } + if (dy == 0) + tmp -= Spacing; + return tmp + largestChild + 2 * Margin; + } + } + } else if (Orientation == Orientation.Horizontal) { + Height = Measure.Stretched; + return -1; + } else if (RegisteredLayoutings.HasFlag (LayoutingType.Width)) + return -1; + else { + int dx = 0; + int tallestChild = 0; + lock (Children) { + foreach (GraphicObject c in Children) { + if (!c.Visible) + continue; + if (c.Width.Units == Unit.Percent && + c.RegisteredLayoutings.HasFlag (LayoutingType.Width)) + return -1; + if (dx + c.Slot.Width > ClientRectangle.Width) { + dx = 0; + tmp += tallestChild + Spacing; + tallestChild = c.Slot.Height; + } else if (tallestChild < c.Slot.Height) + tallestChild = c.Slot.Height; + + dx += c.Slot.Width + Spacing; + } + if (dx == 0) + tmp -= Spacing; + return tmp + tallestChild + 2 * Margin; + } + } + } + + public override bool UpdateLayout (LayoutingType layoutType) + { + RegisteredLayoutings &= (~layoutType); + + if (layoutType == LayoutingType.ArrangeChildren) { + if ((RegisteredLayoutings & LayoutingType.Sizing) != 0) + return false; + + ComputeChildrenPositions (); + + //if no layouting remains in queue for item, registre for redraw + if (RegisteredLayoutings == LayoutingType.None && IsDirty) + CurrentInterface.EnqueueForRepaint (this); + + return true; + } + + return base.UpdateLayout(layoutType); + } + public override void OnLayoutChanges (LayoutingType layoutType) + { + #if DEBUG_LAYOUTING + CurrentInterface.currentLQI.Slot = LastSlots; + CurrentInterface.currentLQI.Slot = Slot; + #endif + switch (layoutType) { + case LayoutingType.Width: + foreach (GraphicObject c in Children) { + if (c.Width.Units == Unit.Percent) + c.RegisterForLayouting (LayoutingType.Width); + } + if (Height == Measure.Fit) + RegisterForLayouting (LayoutingType.Height); + RegisterForLayouting (LayoutingType.X); + break; + case LayoutingType.Height: + foreach (GraphicObject c in Children) { + if (c.Height.Units == Unit.Percent) + c.RegisterForLayouting (LayoutingType.Height); + } + if (Width == Measure.Fit) + RegisterForLayouting (LayoutingType.Width); + RegisterForLayouting (LayoutingType.Y); + break; + default: + return; + } + RegisterForLayouting (LayoutingType.ArrangeChildren); + raiseLayoutChanged (new LayoutingEventArgs (layoutType)); + } + #endregion + } +} + |