diff options
-rw-r--r-- | Testing/GtkTestRunner.csproj | 1 | ||||
-rw-r--r-- | Testing/Tests/InternalChildrenTests.cs | 211 | ||||
-rw-r--r-- | Xwt.Mac/Xwt.Mac/ViewBackend.cs | 4 | ||||
-rw-r--r-- | Xwt/Xwt.Backends/BackendHost.cs | 2 | ||||
-rw-r--r-- | Xwt/Xwt.Backends/ExtensionMethods.cs | 5 | ||||
-rw-r--r-- | Xwt/Xwt/Canvas.cs | 15 | ||||
-rw-r--r-- | Xwt/Xwt/FrameBox.cs | 9 | ||||
-rw-r--r-- | Xwt/Xwt/Widget.cs | 132 |
8 files changed, 341 insertions, 38 deletions
diff --git a/Testing/GtkTestRunner.csproj b/Testing/GtkTestRunner.csproj index 62ff5717..8e060068 100644 --- a/Testing/GtkTestRunner.csproj +++ b/Testing/GtkTestRunner.csproj @@ -106,6 +106,7 @@ <Compile Include="Tests\WidgetTests.cs" /> <Compile Include="Tests\WindowTests.cs" /> <Compile Include="Tests\XwtTest.cs" /> + <Compile Include="Tests\InternalChildrenTests.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup> diff --git a/Testing/Tests/InternalChildrenTests.cs b/Testing/Tests/InternalChildrenTests.cs new file mode 100644 index 00000000..9a6a7a73 --- /dev/null +++ b/Testing/Tests/InternalChildrenTests.cs @@ -0,0 +1,211 @@ +// +// InternalChildrenTests.cs +// +// Author: +// Lluis Sanchez <lluis@xamarin.com> +// +// Copyright (c) 2013 Xamarin Inc. +// +// 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.Linq; +using NUnit.Framework; + +namespace Xwt +{ + public class InternalChildrenTests + { + [Test] + public void ChildrenExcludesInternal () + { + MyContainer co = new MyContainer (); + var c1 = new Label ("hi1"); + var c2 = new Label ("hi2"); + Assert.AreEqual (0, co.Surface.Children.Count ()); + co.Add (c1); + Assert.AreEqual (1, co.Surface.Children.Count ()); + co.AddInner (c2); + Assert.AreEqual (2, co.Surface.Children.Count ()); + Assert.IsTrue (co.Surface.Children.Contains (c1)); + Assert.IsTrue (co.Surface.Children.Contains (c2)); + } + + [Test] + public void ParentIsSet () + { + MyContainer co = new MyContainer (); + var c1 = new Label ("hi1"); + var c2 = new Label ("hi2"); + co.Add (c1); + co.AddInner (c2); + Assert.AreSame (co, c1.Parent); + Assert.AreSame (co, c2.Parent); + } + + [Test] + [ExpectedException (typeof(InvalidOperationException))] + public void InvalidAdd1 () + { + MyContainer co = new MyContainer (); + var c1 = new Label ("hi1"); + co.InternalAdd (c1); + } + + [Test] + [ExpectedException (typeof(InvalidOperationException))] + public void InvalidAdd2 () + { + MyContainer co = new MyContainer (); + var c1 = new Label ("hi1"); + HBox b = new HBox (); + b.PackStart (c1); + co.InternalAdd (c1); + } + + [Test] + [ExpectedException (typeof(InvalidOperationException))] + public void InvalidAdd3 () + { + MyContainer co = new MyContainer (); + var c1 = new Label ("hi1"); + HBox b = new HBox (); + b.PackStart (c1); + co.Add (c1); + } + + [Test] + public void Remove () + { + MyContainer co = new MyContainer (); + var c1 = new Label ("hi1"); + var c2 = new Label ("hi2"); + co.Add (c1); + co.AddInner (c2); + Assert.AreEqual (2, co.Surface.Children.Count ()); + co.RemoveInner (c2); + Assert.AreEqual (1, co.Surface.Children.Count ()); + Assert.IsNull (c2.Parent); + co.Remove (c1); + Assert.AreEqual (0, co.Surface.Children.Count ()); + Assert.IsNull (c1.Parent); + } + + [Test] + [ExpectedException (typeof(InvalidOperationException))] + public void InvalidRemove () + { + MyContainer co = new MyContainer (); + var c1 = new Label ("hi1"); + co.Add (c1); + co.WrongRemove (c1); + } + + [Test] + public void InternalRemoveAdd () + { + // The a child can be removed from internal containers and still + // be a child of the container + + MyContainer co = new MyContainer (); + Assert.AreEqual (0, co.Surface.Children.Count ()); + + var c1 = new Label ("hi1"); + co.Add (c1); + Assert.AreEqual (1, co.Surface.Children.Count ()); + + co.InternalRemove (c1); + Assert.AreEqual (1, co.Surface.Children.Count ()); + Assert.AreSame (co, c1.Parent); + + co.InternalAdd (c1); + Assert.AreEqual (1, co.Surface.Children.Count ()); + Assert.AreSame (co, c1.Parent); + } + + [Test] + [ExpectedException (typeof(InvalidOperationException))] + public void CantSetIternalBeforeAdding () + { + new MyContainerWrong1 (); + } + } + + class MyContainer: Widget + { + internal HBox box = new HBox (); + internal VBox inner = new VBox (); + + public MyContainer () + { + Content = SetInternalChild (box); + box.PackStart (SetInternalChild (inner)); + } + + public void Add (Widget w) + { + RegisterChild (w); + box.PackStart (w); + } + + public void AddInner (Widget w) + { + RegisterChild (w); + inner.PackStart (w); + } + + public void Remove (Widget w) + { + box.Remove (w); + UnregisterChild (w); + } + + public void RemoveInner (Widget w) + { + inner.Remove (w); + UnregisterChild (w); + } + + public void WrongRemove (Widget w) + { + UnregisterChild (w); + box.Remove (w); + } + + public void InternalRemove (Widget w) + { + box.Remove (w); + } + + public void InternalAdd (Widget w) + { + box.PackStart (w); + } + } + + class MyContainerWrong1: Widget + { + public MyContainerWrong1 () + { + HBox box = new HBox (); + Content = box; + SetInternalChild (box); + } + } +} + diff --git a/Xwt.Mac/Xwt.Mac/ViewBackend.cs b/Xwt.Mac/Xwt.Mac/ViewBackend.cs index 443fc25b..62bf29ad 100644 --- a/Xwt.Mac/Xwt.Mac/ViewBackend.cs +++ b/Xwt.Mac/Xwt.Mac/ViewBackend.cs @@ -327,8 +327,8 @@ namespace Xwt.Mac public static void ReplaceSubview (NSView oldChild, NSView newChild) { var vo = oldChild as IViewObject; - if (vo != null && vo.Backend.Frontend.Parent != null) { - var ba = vo.Backend.Frontend.Parent.GetBackend () as ViewBackend; + if (vo != null && vo.Backend.Frontend.GetInternalParent () != null) { + var ba = vo.Backend.Frontend.GetInternalParent ().GetBackend () as ViewBackend; if (ba != null) { ba.ReplaceChild (oldChild, newChild); return; diff --git a/Xwt/Xwt.Backends/BackendHost.cs b/Xwt/Xwt.Backends/BackendHost.cs index d7a07433..368b0912 100644 --- a/Xwt/Xwt.Backends/BackendHost.cs +++ b/Xwt/Xwt.Backends/BackendHost.cs @@ -99,7 +99,7 @@ namespace Xwt.Backends return EngineBackend.CreateBackendForFrontend (Parent.GetType ()); } - public void EnsureBackendLoaded () + internal void EnsureBackendLoaded () { if (backend == null) LoadBackend (); diff --git a/Xwt/Xwt.Backends/ExtensionMethods.cs b/Xwt/Xwt.Backends/ExtensionMethods.cs index c24a5708..bba9ba4c 100644 --- a/Xwt/Xwt.Backends/ExtensionMethods.cs +++ b/Xwt/Xwt.Backends/ExtensionMethods.cs @@ -70,6 +70,11 @@ namespace Xwt.Backends return 0; } } + + public static Widget GetInternalParent (this Widget widget) + { + return widget.InternalParent; + } } } diff --git a/Xwt/Xwt/Canvas.cs b/Xwt/Xwt/Canvas.cs index 8bd6e512..1956dddc 100644 --- a/Xwt/Xwt/Canvas.cs +++ b/Xwt/Xwt/Canvas.cs @@ -115,8 +115,8 @@ namespace Xwt positions [widget] = bounds; var bk = (IWidgetBackend)Widget.GetBackend (widget); - Backend.AddChild (bk, bounds); RegisterChild (widget); + Backend.AddChild (bk, bounds); } /// <summary> @@ -128,12 +128,9 @@ namespace Xwt /// <exception cref="System.ArgumentException">If the widget is not a child of this canvas</exception> public void RemoveChild (Widget widget) { - if (positions == null || widget.Parent != this) - throw new ArgumentException ("Widget is not a child of the canvas"); - + UnregisterChild (widget); positions.Remove (widget); Backend.RemoveChild ((IWidgetBackend)Widget.GetBackend (widget)); - UnregisterChild (widget); } /// <summary> @@ -148,7 +145,7 @@ namespace Xwt /// <exception cref="System.ArgumentException">If the widget is not a child of this canvas</exception> public void SetChildBounds (Widget widget, Rectangle bounds) { - if (positions == null || widget.Parent != this) + if (positions == null || !positions.ContainsKey (widget)) throw new ArgumentException ("Widget is not a child of the canvas"); positions [widget] = bounds; @@ -178,11 +175,9 @@ namespace Xwt public Rectangle GetChildBounds (Widget widget) { Rectangle rect; - if (positions == null || widget.Parent != this) - throw new ArgumentException ("Widget is not a child of the canvas"); - if (positions.TryGetValue (widget, out rect)) + if (positions != null && positions.TryGetValue (widget, out rect)) return rect; - return Rectangle.Zero; + throw new ArgumentException ("Widget is not a child of the canvas"); } protected override BackendHost CreateBackendHost () diff --git a/Xwt/Xwt/FrameBox.cs b/Xwt/Xwt/FrameBox.cs index 0f9c3e7d..26aad8fd 100644 --- a/Xwt/Xwt/FrameBox.cs +++ b/Xwt/Xwt/FrameBox.cs @@ -126,7 +126,8 @@ namespace Xwt public FrameBox () { - base.Content = canvas = new FrameCanvas (); + canvas = SetInternalChild (new FrameCanvas ()); + base.Content = canvas; } public FrameBox (Widget content): this () @@ -231,7 +232,11 @@ namespace Xwt [DefaultValue (null)] public new Widget Content { get { return canvas.Child; } - set { canvas.Child = value; } + set { + UnregisterChild (canvas.Child); + RegisterChild (value); + canvas.Child = value; + } } } } diff --git a/Xwt/Xwt/Widget.cs b/Xwt/Xwt/Widget.cs index f009d62d..f63ec40a 100644 --- a/Xwt/Xwt/Widget.cs +++ b/Xwt/Xwt/Widget.cs @@ -283,8 +283,10 @@ namespace Xwt if (disposing) { if (BackendHost.BackendCreated) Backend.Dispose (); - if (children != null) - children.ForEach (c => c.Dispose ()); + if (children != null) { + foreach (var c in DirectChildren) + c.Dispose (); + } } } @@ -467,7 +469,15 @@ namespace Xwt [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public Widget Parent { get; private set; } - + + internal Widget InternalParent { get; private set; } + + bool IsInternalChild { + get { return ExternalParent != null; } + } + + Widget ExternalParent { get; set; } + [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public IWidgetSurface Surface { get { return this; } @@ -1019,7 +1029,7 @@ namespace Xwt OnReallocate (); if (children != null && !BackendHost.EngineBackend.HandlesSizeNegotiation) { - foreach (Widget w in children) { + foreach (Widget w in DirectChildren) { if (w.Visible) w.Surface.Reallocate (); } @@ -1053,7 +1063,7 @@ namespace Xwt { OnChildPreferredSizeChanged (); - if (Parent != null && resizeRequestQueue.Contains (Parent)) { + if (InternalParent != null && resizeRequestQueue.Contains (InternalParent)) { // Size for this widget will be checked when checking the parent ResetCachedSizes (); return; @@ -1095,8 +1105,8 @@ namespace Xwt internal void OnPlacementChanged () { - if (Parent != null) - Parent.OnChildPlacementChanged (this); + if (InternalParent != null) + InternalParent.OnChildPlacementChanged (this); else if (parentWindow is Window) ((Window)parentWindow).OnChildPlacementChanged (this); } @@ -1126,8 +1136,8 @@ namespace Xwt void NotifySizeChangeToParent () { - if (Parent != null) { - QueueForSizeCheck (Parent); + if (InternalParent != null) { + QueueForSizeCheck (InternalParent); QueueDelayedResizeRequest (); } else if (parentWindow is Window) { @@ -1220,16 +1230,16 @@ namespace Xwt int Depth { get { - if (Parent != null) - return Parent.Depth + 1; + if (InternalParent != null) + return InternalParent.Depth + 1; return 0; } } string GetWidgetDesc () { - if (Parent != null) { - int i = Parent.Surface.Children.ToList ().IndexOf (this); + if (InternalParent != null) { + int i = InternalParent.Surface.Children.ToList ().IndexOf (this); return this + " [" + GetHashCode() + "] (" + i + ")"; } else @@ -1243,21 +1253,57 @@ namespace Xwt IEnumerable<Widget> IWidgetSurface.Children { get { - return (IEnumerable<Widget>)children ?? (IEnumerable<Widget>) emptyList; + return ExternalChildren; } } + IEnumerable<Widget> DirectChildren { + get { return children != null ? children.Where (c => c.InternalParent == this) : emptyList; } + } + + IEnumerable<Widget> ExternalChildren { + get { return children != null ? children.Where (c => !c.IsInternalChild) : emptyList; } + } + + Widget FindExternalParent () + { + if (IsInternalChild && Parent != null) + return Parent.FindExternalParent (); + else + return this; + } + protected void RegisterChild (Widget w) { - if (w.Parent != null) - throw new InvalidOperationException ("Widget is already a child of another widget"); + if (w == null) + return; + if (w.Surface.ToolkitEngine != Surface.ToolkitEngine) throw new InvalidOperationException ("Widget belongs to a different toolkit"); + + var wback = w.Backend as XwtWidgetBackend; + + if (IsInternalChild && !w.IsInternalChild) { + if (w.Parent == null) + throw new InvalidOperationException ("Widget must be registered as a child widget of " + FindExternalParent ()); + if (w.Parent != ExternalParent) + throw new InvalidOperationException ("Widget is already a child of a widget of type " + w.Parent.GetType ()); + w.InternalParent = this; + if (wback != null) + wback.InternalParent = this; + } else { + if (w.Parent != null) + throw new InvalidOperationException ("Widget is already a child of a widget of type " + w.Parent.GetType ()); + w.Parent = this; + w.InternalParent = this; + if (wback != null) { + wback.Parent = this; + wback.InternalParent = this; + } + } + if (children == null) children = new List<Widget> (); - w.Parent = this; - if (w.Backend is XwtWidgetBackend) - ((XwtWidgetBackend)w.Backend).Parent = this; children.Add (w); // Make sure the widget is queued for reallocation @@ -1266,11 +1312,51 @@ namespace Xwt protected void UnregisterChild (Widget w) { - if (children == null || !children.Remove (w)) + if (w == null) + return; + + int i; + if (children == null || (i = children.IndexOf (w)) == -1) throw new InvalidOperationException ("Widget is not a child of this widget"); - w.Parent = null; - if (w.Backend is XwtWidgetBackend) - ((XwtWidgetBackend)w.Backend).Parent = null; + + var wback = w.Backend as XwtWidgetBackend; + + if (w.Parent == this) { + if (w.InternalParent != this) + throw new InvalidOperationException ("Child widget must be removed from internal container before unregistering it"); + w.Parent = null; + w.InternalParent = null; + } else { + w.InternalParent = w.Parent; + } + + children.RemoveAt (i); + + if (wback != null) { + wback.Parent = w.Parent; + wback.InternalParent = w.InternalParent; + } + } + + /// <summary> + /// Flags a widget as an internal child of a container + /// </summary> + /// <param name="child">A widget</param> + /// <remarks> + /// This method must must be called before the child widget is added to any container. + /// Internal children of a widget are not returned in the Children list of the widget, and they + /// are not included in the Parent hierarchy chain. + /// </remarks> + protected T SetInternalChild<T> (T child) where T:Widget + { + if (child.ExternalParent == this) + return child; + if (child.ExternalParent != null) + throw new InvalidOperationException ("Widget is already an internal child of widget " + child.ExternalParent); + if (child.Parent != null) + throw new InvalidOperationException ("Widget must be flagged as internal child before being added to a container"); + child.ExternalParent = this; + return child; } void IAnimatable.BatchBegin () |