diff options
author | Eric Maupin <me@ermau.com> | 2018-12-14 01:13:09 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-14 01:13:09 +0300 |
commit | 24f851b9c3abe3cfe86182d2c9151b4ec61c883f (patch) | |
tree | 05d69b3a3beaeea69be4e97359fe8d6ad61e4460 | |
parent | cd248d8fa5306f3ab05fabc72d6e869dd616ba9a (diff) | |
parent | 6ae9f0fbd0a55b6bf9b927b1ae9bb38ed3e0741b (diff) |
Merge pull request #462 from xamarin/ermau-low-freq
Support low-frequency properties
16 files changed, 1769 insertions, 148 deletions
diff --git a/Xamarin.PropertyEditing.Mac/PropertyTableDataSource.cs b/Xamarin.PropertyEditing.Mac/PropertyTableDataSource.cs index 2334a6e..da40034 100644 --- a/Xamarin.PropertyEditing.Mac/PropertyTableDataSource.cs +++ b/Xamarin.PropertyEditing.Mac/PropertyTableDataSource.cs @@ -30,12 +30,14 @@ namespace Xamarin.PropertyEditing.Mac var childCount = 0; if (this.vm.ArrangeMode == PropertyArrangeMode.Name) - childCount = Filtering ? this.vm.ArrangedEditors[0].Count : this.vm.ArrangedEditors[0].Count + 1; + childCount = Filtering ? this.vm.ArrangedEditors[0].Editors.Count : this.vm.ArrangedEditors[0].Editors.Count + 1; else { if (item == null) childCount = Filtering ? this.vm.ArrangedEditors.Count : this.vm.ArrangedEditors.Count + 1; - else - childCount = ((IGroupingList<string, EditorViewModel>)((NSObjectFacade)item).Target).Count; + else { + var group = (PanelGroupViewModel)((NSObjectFacade)item).Target; + childCount = group.Editors.Count + group.UncommonEditors.Count; + } } return childCount; @@ -50,12 +52,19 @@ namespace Xamarin.PropertyEditing.Mac element = null; else { if (this.vm.ArrangeMode == PropertyArrangeMode.Name) - element = Filtering ? this.vm.ArrangedEditors[0][(int)childIndex] : this.vm.ArrangedEditors[0][(int)childIndex - 1]; + element = Filtering ? this.vm.ArrangedEditors[0].Editors[(int)childIndex] : this.vm.ArrangedEditors[0].Editors[(int)childIndex - 1]; else { if (item == null) element = Filtering ? this.vm.ArrangedEditors[(int)childIndex] : this.vm.ArrangedEditors[(int)childIndex - 1]; else { - element = ((IGroupingList<string, EditorViewModel>)((NSObjectFacade)item).Target)[(int)childIndex]; + var group = (PanelGroupViewModel)((NSObjectFacade)item).Target; + var list = group.Editors; + if (childIndex >= list.Count) { + childIndex -= list.Count; + list = group.UncommonEditors; + } + + element = list[(int)childIndex]; } } } @@ -68,13 +77,13 @@ namespace Xamarin.PropertyEditing.Mac if (this.vm.ArrangeMode == PropertyArrangeMode.Name) return false; - return ((NSObjectFacade)item).Target is IGroupingList<string, EditorViewModel>; + return ((NSObjectFacade)item).Target is PanelGroupViewModel; } public NSObject GetFacade (object element) { NSObject facade; - if (element is IGrouping<string, PropertyViewModel>) { + if (element is PanelGroupViewModel) { if (!this.groupFacades.TryGetValue (element, out facade)) { this.groupFacades[element] = facade = new NSObjectFacade (element); } diff --git a/Xamarin.PropertyEditing.Mac/PropertyTableDelegate.cs b/Xamarin.PropertyEditing.Mac/PropertyTableDelegate.cs index 471d1d4..7a0209a 100644 --- a/Xamarin.PropertyEditing.Mac/PropertyTableDelegate.cs +++ b/Xamarin.PropertyEditing.Mac/PropertyTableDelegate.cs @@ -24,12 +24,12 @@ namespace Xamarin.PropertyEditing.Mac if (!String.IsNullOrWhiteSpace (this.dataSource.DataContext.FilterText)) { outlineView.ExpandItem (null, true); } else { - foreach (IGrouping<string, EditorViewModel> g in this.dataSource.DataContext.ArrangedEditors) { + foreach (PanelGroupViewModel g in this.dataSource.DataContext.ArrangedEditors) { NSObject item; if (!this.dataSource.TryGetFacade (g, out item)) continue; - if (this.dataSource.DataContext.GetIsExpanded (g.Key)) + if (this.dataSource.DataContext.GetIsExpanded (g.Category)) outlineView.ExpandItem (item); else outlineView.CollapseItem (item); @@ -41,7 +41,8 @@ namespace Xamarin.PropertyEditing.Mac public override NSView GetView (NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item) { EditorViewModel evm; - IGroupingList<string, EditorViewModel> group; + PropertyViewModel vm; + PanelGroupViewModel group; string cellIdentifier; GetVMGroupCellItendifiterFromFacade (item, out evm, out group, out cellIdentifier); @@ -53,9 +54,9 @@ namespace Xamarin.PropertyEditing.Mac }; } - labelContainer.StringValue = group.Key; + labelContainer.StringValue = group.Category; - if (this.dataSource.DataContext.GetIsExpanded (group.Key)) { + if (this.dataSource.DataContext.GetIsExpanded (group.Category)) { SynchronizationContext.Current.Post (s => { outlineView.ExpandItem (item); }, null); @@ -103,7 +104,7 @@ namespace Xamarin.PropertyEditing.Mac public override bool ShouldSelectItem (NSOutlineView outlineView, NSObject item) { - return (!(item is NSObjectFacade) || !(((NSObjectFacade)item).Target is IGroupingList<string, EditorViewModel>)); + return (!(item is NSObjectFacade) || !(((NSObjectFacade)item).Target is PanelGroupViewModel)); } public override void ItemDidExpand (NSNotification notification) @@ -112,9 +113,9 @@ namespace Xamarin.PropertyEditing.Mac return; NSObjectFacade facade = notification.UserInfo.Values[0] as NSObjectFacade; - var group = facade.Target as IGroupingList<string, EditorViewModel>; + var group = facade.Target as PanelGroupViewModel; if (group != null) - this.dataSource.DataContext.SetIsExpanded (group.Key, isExpanded: true); + this.dataSource.DataContext.SetIsExpanded (group.Category, isExpanded: true); } public override void ItemDidCollapse (NSNotification notification) @@ -123,15 +124,15 @@ namespace Xamarin.PropertyEditing.Mac return; NSObjectFacade facade = notification.UserInfo.Values[0] as NSObjectFacade; - var group = facade.Target as IGroupingList<string, EditorViewModel>; + var group = facade.Target as PanelGroupViewModel; if (group != null) - this.dataSource.DataContext.SetIsExpanded (group.Key, isExpanded: false); + this.dataSource.DataContext.SetIsExpanded (group.Category, isExpanded: false); } public override nfloat GetRowHeight (NSOutlineView outlineView, NSObject item) { EditorViewModel vm; - IGroupingList<string, EditorViewModel> group; + PanelGroupViewModel group; string cellIdentifier; GetVMGroupCellItendifiterFromFacade (item, out vm, out group, out cellIdentifier); @@ -211,14 +212,14 @@ namespace Xamarin.PropertyEditing.Mac return new PanelHeaderEditorControl (); } - private void GetVMGroupCellItendifiterFromFacade (NSObject item, out EditorViewModel vm, out IGroupingList<string, EditorViewModel> group, out string cellIdentifier) + private void GetVMGroupCellItendifiterFromFacade (NSObject item, out EditorViewModel vm, out PanelGroupViewModel group, out string cellIdentifier) { var facade = (NSObjectFacade)item; vm = facade.Target as EditorViewModel; - group = facade.Target as IGroupingList<string, EditorViewModel>; + group = facade.Target as PanelGroupViewModel; cellIdentifier = facade.Target == null ? nameof (PanelHeaderEditorControl) - : (group == null) ? vm.GetType ().FullName : group.Key; + : (group == null) ? vm.GetType ().FullName : group.Category; } } } diff --git a/Xamarin.PropertyEditing.Tests/MockControls/MockControl.cs b/Xamarin.PropertyEditing.Tests/MockControls/MockControl.cs index 07859ef..66795a8 100644 --- a/Xamarin.PropertyEditing.Tests/MockControls/MockControl.cs +++ b/Xamarin.PropertyEditing.Tests/MockControls/MockControl.cs @@ -15,7 +15,7 @@ namespace Xamarin.PropertyEditing.Tests.MockControls bool canWrite = true, bool flag = false, IEnumerable<Type> converterTypes = null, string description = null, bool constrained = true, ValueSources valueSources = ValueSources.Local | ValueSources.Default | ValueSources.Binding, - IReadOnlyList<InputMode> inputModes = null, PropertyVariationOption[] options = null) + IReadOnlyList<InputMode> inputModes = null, PropertyVariationOption[] options = null, bool isUncommon = false) { IPropertyInfo propertyInfo; if (typeof(T).IsEnum) { @@ -26,7 +26,7 @@ namespace Xamarin.PropertyEditing.Tests.MockControls } else if (inputModes != null) { propertyInfo = new MockPropertyInfoWithInputTypes<T> (name, inputModes, description, category, canWrite, converterTypes, valueSources, options); } else { - propertyInfo = new MockPropertyInfo<T> (name, description, category, canWrite, converterTypes, valueSources, options); + propertyInfo = new MockPropertyInfo<T> (name, description, category, canWrite, converterTypes, valueSources, options, isUncommon); } AddProperty<T> (propertyInfo); diff --git a/Xamarin.PropertyEditing.Tests/MockControls/MockSampleControl.cs b/Xamarin.PropertyEditing.Tests/MockControls/MockSampleControl.cs index c724cc1..aa0f609 100644 --- a/Xamarin.PropertyEditing.Tests/MockControls/MockSampleControl.cs +++ b/Xamarin.PropertyEditing.Tests/MockControls/MockSampleControl.cs @@ -30,8 +30,8 @@ namespace Xamarin.PropertyEditing.Tests.MockControls }); AddProperty<FlagsNoValues> ("FlagsNoValues", ReadWrite, canWrite: true, flag: true); AddProperty<FlagsWithValues> ("FlagsWithValues", ReadWrite, canWrite: true, flag: true); - AddProperty<CommonPoint> ("Point", ReadWrite); - AddProperty<CommonSize> ("Size", ReadWrite); + AddProperty<CommonPoint> ("Point", ReadWrite, isUncommon: true); + AddProperty<CommonSize> ("Size", ReadWrite, isUncommon: true); AddProperty<CommonRectangle> ("Rectangle", ReadWrite); AddProperty<CommonRatio> ("Ratio", ReadWrite); AddProperty<CommonThickness> ("Thickness", ReadWrite); diff --git a/Xamarin.PropertyEditing.Tests/MockPropertyInfo/MockBrushPropertyInfo.cs b/Xamarin.PropertyEditing.Tests/MockPropertyInfo/MockBrushPropertyInfo.cs index cdc8697..74ad06e 100644 --- a/Xamarin.PropertyEditing.Tests/MockPropertyInfo/MockBrushPropertyInfo.cs +++ b/Xamarin.PropertyEditing.Tests/MockPropertyInfo/MockBrushPropertyInfo.cs @@ -35,6 +35,8 @@ namespace Xamarin.PropertyEditing.Tests.MockPropertyInfo public bool CanWrite { get; } + public bool IsUncommon { get; } + public ValueSources ValueSources { get; } public IReadOnlyList<PropertyVariationOption> Variations { get; } diff --git a/Xamarin.PropertyEditing.Tests/MockPropertyInfo/MockPropertyInfo.cs b/Xamarin.PropertyEditing.Tests/MockPropertyInfo/MockPropertyInfo.cs index 9961161..9a3ea41 100644 --- a/Xamarin.PropertyEditing.Tests/MockPropertyInfo/MockPropertyInfo.cs +++ b/Xamarin.PropertyEditing.Tests/MockPropertyInfo/MockPropertyInfo.cs @@ -23,12 +23,13 @@ namespace Xamarin.PropertyEditing.Tests.MockPropertyInfo public class MockPropertyInfo<T> : IPropertyInfo, IPropertyConverter, IEquatable<MockPropertyInfo<T>> { - public MockPropertyInfo (string name, string description = null, string category = null, bool canWrite = true, IEnumerable<Type> converterTypes = null, ValueSources valueSources = ValueSources.Local | ValueSources.Default, PropertyVariationOption[] options = null) + public MockPropertyInfo (string name, string description = null, string category = null, bool canWrite = true, IEnumerable<Type> converterTypes = null, ValueSources valueSources = ValueSources.Local | ValueSources.Default, PropertyVariationOption[] options = null, bool isUncommon = false) { Name = name; Description = description; Category = category; CanWrite = canWrite; + IsUncommon = isUncommon; ValueSources = valueSources; if (converterTypes != null) { this.typeConverters = converterTypes @@ -52,6 +53,7 @@ namespace Xamarin.PropertyEditing.Tests.MockPropertyInfo public string Category { get; } public bool CanWrite { get; } + public bool IsUncommon { get; } public ValueSources ValueSources { get; } static readonly PropertyVariationOption[] EmptyVariationOptions = new PropertyVariationOption[0]; diff --git a/Xamarin.PropertyEditing.Tests/OrderedDictionaryTests.cs b/Xamarin.PropertyEditing.Tests/OrderedDictionaryTests.cs new file mode 100644 index 0000000..12726a9 --- /dev/null +++ b/Xamarin.PropertyEditing.Tests/OrderedDictionaryTests.cs @@ -0,0 +1,1351 @@ +// +// OrderedDictionaryTest.cs +// +// Author: +// Eric Maupin <me@ermau.com> +// +// Copyright (c) 2009 Eric Maupin (http://www.ermau.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; + +namespace Cadenza.Collections.Tests +{ + [TestFixture] + public class OrderedDictionaryTest + { + [Test] + public void Ctor_DictNull () + { + Assert.Throws<ArgumentNullException> (() => { + Dictionary<string, string> foo = null; + new OrderedDictionary<string, string> (foo); + }); + } + + [Test] + public void Ctor_CapacityOutOfRange () + { + Assert.Throws<ArgumentOutOfRangeException> (() => new OrderedDictionary<string, string> (-1)); + } + + [Test] + public void Ctor_CapacityOutOfRangeWithEquality () + { + Assert.Throws<ArgumentOutOfRangeException> (() => new OrderedDictionary<string, string> (-1, null)); + } + + [Test] + public void KeyIndexer () + { + var dict = new OrderedDictionary<string, string> { { "foo", "bar" }, { "baz", "monkeys" } }; + Assert.AreEqual ("bar", dict["foo"]); + Assert.AreEqual ("monkeys", dict["baz"]); + } + + [Test] + public void KeyIndexer_KeyNotFound () + { + var dict = new OrderedDictionary<string, string> + { { "foo", "bar" }, { "baz", "monkeys" } }; + + Assert.Throws<KeyNotFoundException> (() => dict["wee"].ToString ()); + } + + [Test] + public void KeyIndexerSet () + { + var dict = new OrderedDictionary<uint, int> (); + dict[(uint)1] = 1; + dict[(uint)2] = 2; + dict[(uint)3] = 3; + dict.Remove (2); + dict[(uint)4] = 4; + + Assert.AreEqual (1, dict[(int)0]); + Assert.AreEqual (3, dict[(int)1]); + Assert.AreEqual (4, dict[(int)2]); + } + + [Test] + public void KeyIndexerGet_KeyNull () + { + var dict = new OrderedDictionary<string, string> (); + Assert.Throws<ArgumentNullException> (() => dict[null].ToString ()); + } + + [Test] + public void KeyIndexerSet_KeyNull () + { + var dict = new OrderedDictionary<string, string> (); + + Assert.Throws<ArgumentNullException> (() => dict[null] = "foo"); + } + + [Test] + public void IndexIndexer () + { + var dict = new OrderedDictionary<uint, int> (); + dict.Add (1, 1); + dict.Add (2, 2); + dict.Add (3, 3); + dict.Remove (2); + dict.Add (4, 4); + + Assert.AreEqual (1, dict[(int)0]); + Assert.AreEqual (3, dict[(int)1]); + Assert.AreEqual (4, dict[(int)2]); + } + + [Test] + public void IndexIndexerSet_New () + { + var dict = new OrderedDictionary<string, string> (); + dict.Add ("A", "B"); + dict.Add ("C", "D"); + + var list = (IList<KeyValuePair<string, string>>)dict; + list[1] = new KeyValuePair<string, string> ("E", "F"); + + Assert.That (list.Count, Is.EqualTo (2)); + Assert.That (dict["E"], Is.EqualTo ("F")); + Assert.That (dict.ContainsKey ("C"), Is.False); + } + + [Test] + public void IndexIndexerSet_Existing () + { + var dict = new OrderedDictionary<string, string> (); + dict.Add ("A", "B"); + dict.Add ("C", "D"); + + var list = (IList<KeyValuePair<string, string>>)dict; + list[1] = new KeyValuePair<string, string> ("C", "F"); + + Assert.That (list.Count, Is.EqualTo (2)); + Assert.That (dict["C"], Is.EqualTo ("F")); + } + + [Test] + [Description ("You can't duplicate a key by entering it via indexed set.")] + public void IndexIndexerSet_ReplicatedKey () + { + var dict = new OrderedDictionary<string, string> (); + dict.Add ("A", "B"); + dict.Add ("C", "D"); + + var list = (IList<KeyValuePair<string, string>>) dict; + Assert.That (() => list[0] = new KeyValuePair<string, string> ("C", "E"), Throws.ArgumentException); + } + + [Test] + public void Indexer_IndexOutOfRangeLower () + { + var dict = new OrderedDictionary<string, string> (); + Assert.Throws<ArgumentOutOfRangeException> (() => dict[-1].ToString ()); + } + + [Test] + public void Indexer_IndexOutOfRangeUpper () + { + var dict = new OrderedDictionary<string, string> + { { "foo", "bar" }, { "baz", "monkeys" } }; + + Assert.Throws<ArgumentOutOfRangeException> (() => dict[2].ToString ()); + } + + [Test] + public void EnumerableOrder () + { + var dict = new OrderedDictionary<uint, int> (); + dict.Add (1, 1); + dict.Add (2, 2); + dict.Add (3, 3); + dict.Remove (2); + dict.Add (4, 4); + + using (var enumerator = dict.GetEnumerator ()) { + Assert.IsTrue (enumerator.MoveNext ()); + Assert.AreEqual (1, enumerator.Current.Value); + Assert.IsTrue (enumerator.MoveNext ()); + Assert.AreEqual (3, enumerator.Current.Value); + Assert.IsTrue (enumerator.MoveNext ()); + Assert.AreEqual (4, enumerator.Current.Value); + } + } + + [Test] + public void Values_EnumerableOrder () + { + var dict = new OrderedDictionary<uint, int> (); + dict.Add (1, 1); + dict.Add (2, 2); + dict.Add (3, 3); + dict.Remove (2); + dict.Add (4, 4); + + Assert.AreEqual (1, dict.Values.ElementAt (0)); + Assert.AreEqual (3, dict.Values.ElementAt (1)); + Assert.AreEqual (4, dict.Values.ElementAt (2)); + } + + [Test] + public void CopyTo () + { + var dict = new OrderedDictionary<uint, int> (); + dict.Add (1, 1); + dict.Add (2, 2); + dict.Add (3, 3); + dict.Remove (2); + dict.Add (4, 4); + + KeyValuePair<uint, int>[] a = new KeyValuePair<uint, int>[13]; + + ((ICollection<KeyValuePair<uint, int>>)dict).CopyTo (a, 10); + + for (int i = 0; i < 10; ++i) { + if (i < 10) + Assert.AreEqual (default (KeyValuePair<uint, int>), a[i]); + } + + Assert.AreEqual (1, a[10].Value); + Assert.AreEqual (3, a[11].Value); + Assert.AreEqual (4, a[12].Value); + } + + [Test] + public void CopyTo_NullArray () + { + var dict = new OrderedDictionary<string, string> (); + KeyValuePair<string, string>[] a = null; + + Assert.Throws<ArgumentNullException> ( + () => ((ICollection<KeyValuePair<string, string>>) dict).CopyTo (a, 0)); + } + + [Test] + public void CopyTo_ArrayTooSmall () + { + var dict = new OrderedDictionary<string, string> (); + for (int i = 0; i < 1000; ++i) + dict.Add (i.ToString (), (i + 1).ToString ()); + + KeyValuePair<string, string>[] a = new KeyValuePair<string, string>[1]; + Assert.Throws<ArgumentException> (() => ((ICollection<KeyValuePair<string, string>>) dict).CopyTo (a, 0)); + } + + [Test] + public void CopyTo_IndexOutOfRange () + { + var dict = new OrderedDictionary<string, string> (); + + Assert.Throws<ArgumentOutOfRangeException> (() => + ((ICollection<KeyValuePair<string, string>>) dict).CopyTo (new KeyValuePair<string, string>[10], -1)); + } + + [Test] + public void Values_CopyTo () + { + var dict = new OrderedDictionary<uint, int> (); + dict.Add (1, 1); + dict.Add (2, 2); + dict.Add (3, 3); + dict.Remove (2); + dict.Add (4, 4); + + int[] a = new int[13]; + + dict.Values.CopyTo (a, 10); + + for (int i = 0; i < 10; ++i) { + if (i < 10) + Assert.AreEqual (default (int), a[i]); + } + + Assert.AreEqual (1, a[10]); + Assert.AreEqual (3, a[11]); + Assert.AreEqual (4, a[12]); + } + + [Test] + public void ValuesCopyTo_NullArray () + { + var dict = new OrderedDictionary<string, string> (); + string[] a = null; + + Assert.Throws<ArgumentNullException> (() => dict.Values.CopyTo (a, 0)); + } + + [Test] + public void ValuesCopyTo_ArrayTooSmall () + { + var dict = new OrderedDictionary<string, string> (); + for (int i = 0; i < 1000; ++i) + dict.Add (i.ToString (), (i + 1).ToString ()); + + Assert.Throws<ArgumentException> (() => dict.Values.CopyTo (new string[1], 0)); + } + + [Test] + public void ValuesCopyTo_IndexOutOfRange () + { + var dict = new OrderedDictionary<string, string> (); + + Assert.Throws<ArgumentOutOfRangeException> (() => dict.Values.CopyTo (new string[1], -1)); + } + + [Test] + public void IsReadOnly () + { + Assert.IsFalse (((ICollection<KeyValuePair<int, int>>)new OrderedDictionary<int, int> ()).IsReadOnly); + } + + [Test] + public void Values_IsReadOnly () + { + Assert.IsTrue (new OrderedDictionary<int, int> ().Values.IsReadOnly); + } + + [Test] + public void Clear () + { + var dict = new OrderedDictionary<int, int> { { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 5 } }; + + dict.Clear (); + + Assert.AreEqual (0, dict.Count); + Assert.AreEqual (0, dict.Values.Count); + Assert.IsFalse (dict.ContainsKey (1)); + Assert.IsFalse (dict.ContainsValue (2)); + } + + [Test] + public void Values_Clear () + { + var dict = new OrderedDictionary<int, int> (); + + Assert.Throws<NotSupportedException> (dict.Values.Clear); + } + + [Test] + public void Add () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("1", 2); + dict.Add ("2", 3); + + Assert.AreEqual (dict[0], 2); + Assert.AreEqual (dict[1], 3); + } + + [Test] + public void Add_KeyNull () + { + var dict = new OrderedDictionary<string, int> (); + Assert.Throws<ArgumentNullException> (() => dict.Add (null, 1)); + } + + [Test] + public void Add_KeyExists () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + + Assert.Throws<ArgumentException> (() => dict.Add ("foo", 1)); + } + + [Test] + public void KVP_Add () + { + var dict = new OrderedDictionary<uint, int> (); + ((ICollection<KeyValuePair<uint, int>>)dict).Add (new KeyValuePair<uint, int> (1, 1)); + ((ICollection<KeyValuePair<uint, int>>)dict).Add (new KeyValuePair<uint, int> (2, 2)); + ((ICollection<KeyValuePair<uint, int>>)dict).Add (new KeyValuePair<uint, int> (3, 3)); + ((ICollection<KeyValuePair<uint, int>>)dict).Remove (new KeyValuePair<uint, int> (2, 2)); + ((ICollection<KeyValuePair<uint, int>>)dict).Add (new KeyValuePair<uint, int> (4, 4)); + + Assert.AreEqual (3, dict.Count); + Assert.AreEqual (dict[0], 1); + Assert.AreEqual (dict[1], 3); + Assert.AreEqual (dict[2], 4); + } + + [Test] + public void Values_Add () + { + var dict = new OrderedDictionary<string, int> (); + Assert.Throws<NotSupportedException> (() => dict.Values.Add (1)); + } + + [Test] + public void Insert () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("1", 2); + dict.Add ("3", 4); + + dict.Insert (1, "2", 3); + + Assert.AreEqual (dict[0], 2); + Assert.AreEqual (dict[1], 3); + Assert.AreEqual (dict[2], 4); + } + + [Test] + public void Insert_KeyExists () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("1", 2); + dict.Add ("3", 4); + + Assert.Throws<ArgumentException> (() => dict.Insert (1, "3", 3)); + } + + [Test] + public void KVP_Insert () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("1", 2); + dict.Add ("3", 4); + + ((IList<KeyValuePair<string, int>>)dict).Insert (1, + new KeyValuePair<string, int> ("2", 3)); + + Assert.AreEqual (dict[0], 2); + Assert.AreEqual (dict[1], 3); + Assert.AreEqual (dict[2], 4); + } + + [Test] + public void Values_Insert () + { + var dict = new OrderedDictionary<string, int> (); + Assert.Throws<NotSupportedException> (() => ((IList<int>) dict.Values).Insert (1, 1)); + } + + [Test] + public void Remove () + { + var dict = new OrderedDictionary<string, int> { { "1", 2 }, { "2", 3 }, { "3", 4 } }; + + Assert.IsTrue (dict.Remove ("2")); + Assert.IsFalse (dict.ContainsKey ("2")); + Assert.IsFalse (dict.Values.Contains (3)); + Assert.AreEqual (dict[1], 4); + + Assert.IsFalse (dict.Remove ("2")); + } + + [Test] + public void Remove_KeyNull () + { + var dict = new OrderedDictionary<string, int> (); + Assert.Throws<ArgumentNullException> (() => dict.Remove (null)); + } + + [Test] + public void KVP_Remove () + { + var dict = new OrderedDictionary<string, string> (); + dict.Add ("foo", "bar"); + + var kvp = new KeyValuePair<string, string> ("foo", "bar"); + Assert.IsTrue (((ICollection<KeyValuePair<string, string>>)dict).Remove (kvp)); + Assert.AreEqual (0, dict.Count); + } + + [Test] + public void Values_Remove () + { + var dict = new OrderedDictionary<string, int> (); + Assert.Throws<NotSupportedException> (() => dict.Values.Remove (1)); + } + + [Test] + public void ContainsKey () + { + var dict = new OrderedDictionary<string, int> { { "1", 2 }, { "2", 3 }, { "3", 4 } }; + + Assert.IsFalse (dict.ContainsKey ("0")); + Assert.IsTrue (dict.ContainsKey ("1")); + Assert.IsTrue (dict.ContainsKey ("2")); + Assert.IsTrue (dict.ContainsKey ("3")); + Assert.IsFalse (dict.ContainsKey ("4")); + } + + [Test] + public void ContainsKey_KeyNull () + { + var dict = new OrderedDictionary<string, int> (); + Assert.Throws<ArgumentNullException> (() => dict.ContainsKey (null)); + } + + [Test] + public void ContainsValue () + { + var dict = new OrderedDictionary<string, int> { { "1", 2 }, { "2", 3 }, { "3", 4 } }; + + Assert.IsFalse (dict.ContainsValue (1)); + Assert.IsTrue (dict.ContainsValue (2)); + Assert.IsTrue (dict.ContainsValue (3)); + Assert.IsTrue (dict.ContainsValue (4)); + Assert.IsFalse (dict.ContainsValue (5)); + } + + [Test] + public void KVP_Contains () + { + var dict = new OrderedDictionary<string, int> { { "1", 2 }, { "2", 3 }, { "3", 4 } }; + var co = (ICollection<KeyValuePair<string, int>>)dict; + + Assert.IsFalse (co.Contains (new KeyValuePair<string, int> ("0", 1))); + Assert.IsTrue (co.Contains (new KeyValuePair<string, int> ("1", 2))); + Assert.IsTrue (co.Contains (new KeyValuePair<string, int> ("2", 3))); + Assert.IsTrue (co.Contains (new KeyValuePair<string, int> ("3", 4))); + Assert.IsFalse (co.Contains (new KeyValuePair<string, int> ("4", 5))); + } + + [Test] + public void Values_Contains () + { + var dict = new OrderedDictionary<string, int> { { "1", 2 }, { "2", 3 }, { "3", 4 } }; + + Assert.IsFalse (dict.Values.Contains (1)); + Assert.IsTrue (dict.Values.Contains (2)); + Assert.IsTrue (dict.Values.Contains (3)); + Assert.IsTrue (dict.Values.Contains (4)); + Assert.IsFalse (dict.Values.Contains (5)); + } + + [Test] + public void Count () + { + var dict = new OrderedDictionary<string, int> { { "1", 2 }, { "2", 3 }, { "3", 4 } }; + + Assert.AreEqual (3, dict.Count); + + dict.Add ("4", 5); + dict.Add ("5", 6); + + Assert.AreEqual (5, dict.Count); + + dict.Clear (); + + Assert.AreEqual (0, dict.Count); + } + + [Test] + public void Values_Count () + { + var dict = new OrderedDictionary<string, int> { { "1", 2 }, { "2", 3 }, { "3", 4 } }; + + Assert.AreEqual (3, dict.Values.Count); + + dict.Add ("4", 5); + dict.Add ("5", 6); + + Assert.AreEqual (5, dict.Values.Count); + + dict.Clear (); + + Assert.AreEqual (0, dict.Values.Count); + } + + [Test] + public void TryGetValue_NullKey () + { + var dict = new OrderedDictionary<string, int> (); + + int i; + Assert.Throws<ArgumentNullException> (() => dict.TryGetValue (null, out i)); + } + + [Test] + public void TryGetValue () + { + var dict = new OrderedDictionary<string, int> { { "1", 2 }, { "2", 3 }, { "3", 4 } }; + + int v; + Assert.IsTrue (dict.TryGetValue ("1", out v)); + Assert.AreEqual (2, v); + } + + [Test] + public void TryGetValue_NotFound () + { + var dict = new OrderedDictionary<string, int> { { "1", 2 }, { "2", 3 }, { "3", 4 } }; + + int v; + Assert.IsFalse (dict.TryGetValue ("4", out v)); + } + + [Test] + public void RemoveAt () + { + var dict = new OrderedDictionary<uint, int> (); + dict.Add (1, 1); + dict.Add (2, 2); + dict.Add (3, 3); + dict.Remove (2); + dict.Add (4, 4); + + dict.RemoveAt (1); + + Assert.AreEqual (1, dict[(int)0]); + Assert.AreEqual (4, dict[(int)1]); + } + + [Test] + public void RemoveAt_IndexOutOfRangeLower () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + dict.Add ("baz", 2); + + Assert.Throws<ArgumentOutOfRangeException> (() => dict.RemoveAt (-1)); + } + + [Test] + public void Values_RemoveAt () + { + var dict = new OrderedDictionary<string, int> (); + + Assert.Throws<NotSupportedException> (() => ((IList<int>) dict.Values).RemoveAt (0)); + } + + [Test] + public void IndexOf () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + + Assert.AreEqual (1, dict.IndexOf ("bar")); + } + + [Test] + public void IndexOf_NotFound () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + + Assert.AreEqual (-1, dict.IndexOf ("baz")); + } + + [Test] + public void IndexOf_KeyNull () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + + Assert.Throws<ArgumentNullException> (() => dict.IndexOf (null)); + } + + [Test] + public void IndexOf_StartIndex () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + dict.Add ("baz", 2); + dict.Add ("monkeys", 3); + + Assert.AreEqual (2, dict.IndexOf ("baz", 1)); + Assert.AreEqual (2, dict.IndexOf ("baz", 2)); + } + + [Test] + public void IndexOf_StartIndex_NotFound () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + dict.Add ("baz", 2); + dict.Add ("monkeys", 3); + + Assert.AreEqual (-1, dict.IndexOf ("asdf", 2)); + Assert.AreEqual (-1, dict.IndexOf ("bar", 2)); + } + + [Test] + public void IndexOf_StartIndex_KeyNull () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + dict.Add ("baz", 2); + dict.Add ("monkeys", 3); + + Assert.Throws<ArgumentNullException> (() => dict.IndexOf (null, 1)); + } + + [Test] + public void IndexOf_StartIndex_IndexOutOfRangeLower () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + dict.Add ("baz", 2); + dict.Add ("monkeys", 3); + + Assert.Throws<ArgumentOutOfRangeException> (() => dict.IndexOf ("monkeys", -1)); + } + + [Test] + public void IndexOf_StartIndex_IndexOutOfRangeUpper () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + dict.Add ("baz", 2); + dict.Add ("monkeys", 3); + + Assert.Throws<ArgumentOutOfRangeException> (() => dict.IndexOf ("monkeys", 5)); + } + + [Test] + public void IndexOf_StartIndexAndCount () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + dict.Add ("baz", 2); + dict.Add ("monkeys", 3); + + Assert.AreEqual (1, dict.IndexOf ("bar", 1, 1)); + Assert.AreEqual (2, dict.IndexOf ("baz", 0, 3)); + } + + [Test] + public void IndexOf_StartIndexAndCount_NotFound () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + dict.Add ("baz", 2); + dict.Add ("monkeys", 3); + + Assert.AreEqual (-1, dict.IndexOf ("bar", 2, 1)); + Assert.AreEqual (-1, dict.IndexOf ("baz", 0, 2)); + } + + [Test] + public void IndexOf_StartIndexAndCount_KeyNull () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + dict.Add ("baz", 2); + dict.Add ("monkeys", 3); + + Assert.Throws<ArgumentNullException> (() => dict.IndexOf (null, 1, 2)); + } + + [Test] + public void IndexOf_StartIndexAndCount_IndexOutOfRangeLower () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + dict.Add ("baz", 2); + dict.Add ("monkeys", 3); + + Assert.Throws<ArgumentOutOfRangeException> (() => dict.IndexOf ("monkeys", -1, 1)); + } + + [Test] + public void IndexOf_StartIndexAndCount_IndexOutOfRangeUpper () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + dict.Add ("baz", 2); + dict.Add ("monkeys", 3); + + Assert.Throws<ArgumentOutOfRangeException> (() => dict.IndexOf ("monkeys", 5, 1)); + } + + [Test] + public void IndexOf_StartIndexAndCount_CountOutOfRange () + { + var dict = new OrderedDictionary<string, int> (); + dict.Add ("foo", 0); + dict.Add ("bar", 1); + dict.Add ("baz", 2); + dict.Add ("monkeys", 3); + + Assert.Throws<ArgumentOutOfRangeException> (() => dict.IndexOf ("monkeys", 2, 3)); + } + } + + [TestFixture] + public class OrderedDictionaryListContractTests : ListContract<KeyValuePair<string, string>> + { + protected override ICollection<KeyValuePair<string, string>> CreateCollection (IEnumerable<KeyValuePair<string, string>> values) + { + var d = new OrderedDictionary<string, string> (); + foreach (var v in values) + d.Add (v.Key, v.Value); + return d; + } + + protected override KeyValuePair<string, string> CreateValueA () + { + return new KeyValuePair<string, string> ("A", "1"); + } + + protected override KeyValuePair<string, string> CreateValueB () + { + return new KeyValuePair<string, string> ("B", "2"); + } + + protected override KeyValuePair<string, string> CreateValueC () + { + return new KeyValuePair<string, string> ("C", "3"); + } + } + + [TestFixture] + public class OrderedDictionaryDictionaryContractTests : DictionaryContract + { + protected override IDictionary<string, string> CreateDictionary (IEnumerable<KeyValuePair<string, string>> values) + { + var d = new OrderedDictionary<string, string> (); + foreach (var v in values) + d.Add (v.Key, v.Value); + return d; + } + } + +// +// IEnumerableContract.cs +// +// Author: +// Jonathan Pryor <jpryor@novell.com> +// +// Copyright (c) 2010 Novell, Inc. (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + public abstract class DictionaryContract + { + + protected abstract IDictionary<string, string> CreateDictionary (IEnumerable<KeyValuePair<string, string>> values); + + [Test] + public void Add () + { + var d = CreateDictionary (new KeyValuePair<string, string>[0]); + + var n = d.Count; + Assert.AreEqual (n, d.Keys.Count); + Assert.AreEqual (n, d.Values.Count); + + // key cannot be null + try { + d.Add ("key", "value"); + Assert.IsTrue (d.ContainsKey ("key")); + Assert.IsFalse (d.ContainsKey ("value")); + Assert.AreEqual (n + 1, d.Keys.Count); + Assert.AreEqual (n + 1, d.Values.Count); + Assert.IsTrue (d.Keys.Contains ("key")); + Assert.IsTrue (d.Values.Contains ("value")); + + // Cannot use Add() w/ the same key + Assert.Throws<ArgumentException> (() => d.Add ("key", "value2")); + + Assert.Throws<ArgumentNullException> (() => d.Add (null, null)); + } catch (NotSupportedException) { + Assert.IsTrue (d.IsReadOnly); + } + } + + [Test] + public void ContainsKey () + { + var d = CreateDictionary (new KeyValuePair<string, string>[]{ + new KeyValuePair<string, string> ("another-key", "another-value"), + }); + Assert.Throws<ArgumentNullException> (() => d.ContainsKey (null)); + Assert.IsFalse (d.ContainsKey ("key")); + Assert.IsTrue (d.ContainsKey ("another-key")); + Assert.IsTrue (d.Keys.Contains ("another-key")); + } + + [Test] + public void Remove () + { + var d = CreateDictionary (new KeyValuePair<string, string>[]{ + new KeyValuePair<string, string> ("another-key", "another-value"), + }); + var n = d.Count; + try { + Assert.IsFalse (d.Remove ("key")); + Assert.AreEqual (n, d.Count); + Assert.IsTrue (d.Remove ("another-key")); + Assert.AreEqual (n - 1, d.Count); + Assert.AreEqual (n - 1, d.Keys.Count); + Assert.AreEqual (n - 1, d.Values.Count); + Assert.IsFalse (d.Keys.Contains ("another-key")); + Assert.IsFalse (d.Values.Contains ("another-value")); + + Assert.Throws<ArgumentNullException> (() => d.Remove (null)); + } catch (NotSupportedException) { + Assert.IsTrue (d.IsReadOnly); + } + } + + [Test] + public void TryGetValue () + { + var d = CreateDictionary (new KeyValuePair<string, string>[]{ + new KeyValuePair<string, string> ("key", "value"), + }); + string v = null; + Assert.Throws<ArgumentNullException> (() => d.TryGetValue (null, out v)); + Assert.IsFalse (d.TryGetValue ("another-key", out v)); + Assert.IsTrue (d.TryGetValue ("key", out v)); + Assert.AreEqual ("value", v); + } + + [Test] + public void Item () + { + var d = CreateDictionary (new KeyValuePair<string, string>[]{ + new KeyValuePair<string, string> ("key", "value"), + }); +#pragma warning disable 0168 + Assert.Throws<ArgumentNullException> (() => { var _ = d[null]; }); + Assert.Throws<KeyNotFoundException> (() => { var _ = d["another-key"]; }); +#pragma warning restore + try { + d["key"] = "another-value"; + Assert.IsFalse (d.Values.Contains ("value")); + Assert.IsTrue (d.Values.Contains ("another-value")); + Assert.AreEqual ("another-value", d["key"]); + Assert.AreEqual (1, d.Keys.Count); + Assert.AreEqual (1, d.Values.Count); + } catch (NotSupportedException) { + Assert.IsTrue (d.IsReadOnly); + } + } + + [Test] + public void Keys_And_Values_Order_Must_Match () + { + var d = CreateDictionary (new KeyValuePair<string, string>[]{ + new KeyValuePair<string, string> ("a", "1"), + new KeyValuePair<string, string> ("b", "2"), + new KeyValuePair<string, string> ("c", "3"), + }); + Assert.AreEqual (IndexOf (d.Keys, "a"), IndexOf (d.Values, "1")); + Assert.AreEqual (IndexOf (d.Keys, "b"), IndexOf (d.Values, "2")); + Assert.AreEqual (IndexOf (d.Keys, "c"), IndexOf (d.Values, "3")); + } + + private int IndexOf<T> (IEnumerable<T> items, T search) + { + int i = 0; + foreach (T element in items) { + if (Equals (element, search)) + return i; + + i++; + } + + return -1; + } + } + + [TestFixture] + public class OrderedDictionaryKeysTests + : SubCollectionContract + { + protected override ICollection<string> CreateCollection (IEnumerable<string> values) + { + var d = new OrderedDictionary<string, string> (); + foreach (var v in values.Select (v => new KeyValuePair<string, string> (v, v))) + d.Add (v.Key, v.Value); + + var c = d.Keys; + Assert.IsTrue (c.IsReadOnly); + return c; + } + } + + [TestFixture] + public class OrderedDictionaryValuesTests + : SubCollectionContract + { + protected override ICollection<string> CreateCollection (IEnumerable<string> values) + { + var d = new OrderedDictionary<string, string> (); + foreach (var v in values.Select (v => new KeyValuePair<string, string> (v, v))) + d.Add (v.Key, v.Value); + + var c = d.Values; + Assert.IsTrue (c.IsReadOnly); + return c; + } + } + + public abstract class SubCollectionContract + : CollectionContract<string> + { + protected override string CreateValueA () + { + return "A"; + } + + protected override string CreateValueB () + { + return "B"; + } + + protected override string CreateValueC () + { + return "C"; + } + } + +// +// IListContract.cs +// +// Author: +// Jonathan Pryor <jpryor@novell.com> +// +// Copyright (c) 2010 Novell, Inc. (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + + public abstract class ListContract<T> : CollectionContract<T> + { + + private IList<T> CreateList (IEnumerable<T> values) + { + return (IList<T>)CreateCollection (values); + } + + [Test] + public void IndexOf () + { + var a = CreateValueA (); + var b = CreateValueB (); + + var list = CreateList (new T[0]); + + Assert.AreEqual (-1, list.IndexOf (a)); + + try { + list.Add (a); + Assert.AreEqual (0, list.IndexOf (a)); + + list.Add (b); + Assert.AreEqual (1, list.IndexOf (b)); + + list.Remove (a); + Assert.AreEqual (-1, list.IndexOf (a)); + Assert.AreEqual (0, list.IndexOf (b)); + + list.Remove (b); + Assert.AreEqual (-1, list.IndexOf (b)); + } catch (NotSupportedException) { + Assert.IsTrue (list.IsReadOnly); + } + } + + [Test] + public void Insert () + { + var a = CreateValueA (); + var b = CreateValueB (); + + var list = CreateList (new T[0]); + + try { + Assert.Throws<ArgumentOutOfRangeException> (() => list.Insert (-1, a)); + Assert.Throws<ArgumentOutOfRangeException> (() => list.Insert (1, a)); + + list.Insert (0, a); + Assert.AreEqual (0, list.IndexOf (a)); + + list.Insert (0, b); + Assert.AreEqual (2, list.Count); + Assert.AreEqual (0, list.IndexOf (b)); + Assert.AreEqual (1, list.IndexOf (a)); + } catch (NotSupportedException) { + Assert.IsTrue (list.IsReadOnly); + } + } + + [Test] + public void RemoveAt () + { + var a = CreateValueA (); + var b = CreateValueB (); + + var list = CreateList (new T[0]); + + try { + Assert.Throws<ArgumentOutOfRangeException> (() => list.RemoveAt (-1)); + Assert.Throws<ArgumentOutOfRangeException> (() => list.RemoveAt (0)); + + list.Add (a); + Assert.AreEqual (1, list.Count); + + list.RemoveAt (0); + Assert.AreEqual (0, list.Count); + + list.Add (a); + list.Add (b); + list.RemoveAt (0); + Assert.AreEqual (1, list.Count); + Assert.AreEqual (0, list.IndexOf (b)); + } catch (NotSupportedException) { + Assert.IsTrue (list.IsReadOnly); + } + } + + [Test] + public void Item () + { + var a = CreateValueA (); + var b = CreateValueB (); + + var list = CreateList (new[] { a }); + + Assert.AreEqual (a, list[0]); + Assert.Throws<ArgumentOutOfRangeException> (() => list[-1].ToString()); + + try { + Assert.Throws<ArgumentOutOfRangeException> (() => list[-1] = a); + Assert.Throws<ArgumentOutOfRangeException> (() => list[1] = a); + + list[0] = b; + Assert.AreEqual (-1, list.IndexOf (a)); + Assert.AreEqual (0, list.IndexOf (b)); + } catch (NotSupportedException) { + Assert.IsTrue (list.IsReadOnly); + } + } + } + +// +// IEnumerableContract.cs +// +// Author: +// Jonathan Pryor <jpryor@novell.com> +// +// Copyright (c) 2010 Novell, Inc. (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + // NOTE: when adding new tests to this type, add them to the + // RunAllTests() method as well. + // RunAllTests() is used by IDictionaryContract<T>.Keys()/.Values() + // to test the behavior of the .Keys/.Values read-only collections. + // + // NOTE: No test may use [ExpectedException]; use Assert.Throws<T> instead. + public abstract class CollectionContract<T> + { + + protected abstract ICollection<T> CreateCollection (IEnumerable<T> values); + protected abstract T CreateValueA (); + protected abstract T CreateValueB (); + protected abstract T CreateValueC (); + + + [Test] + public void Ctor_Initial_Count_Is_Zero () + { + var c = CreateCollection (new T[0]); + Assert.AreEqual (0, c.Count); + } + + [Test] + public void Ctor_CopySequence () + { + var c = CreateCollection (new[] { CreateValueA (), CreateValueB (), CreateValueC () }); + Assert.AreEqual (3, c.Count); + } + + [Test] + public void Add () + { + var c = CreateCollection (new T[0]); + var n = c.Count; + try { + c.Add (CreateValueA ()); + Assert.AreEqual (n + 1, c.Count); + } catch (NotSupportedException) { + Assert.IsTrue (c.IsReadOnly); + } + } + + [Test] + public void Clear () + { + var c = CreateCollection (new[] { CreateValueA () }); + try { + c.Clear (); + Assert.AreEqual (0, c.Count); + } catch (NotSupportedException) { + Assert.IsTrue (c.IsReadOnly); + } + } + + [Test] + public void Contains () + { + var a = CreateValueA (); + var b = CreateValueB (); + + var c = CreateCollection (new[] { a, b }); + Assert.IsTrue (c.Contains (a)); + Assert.IsTrue (c.Contains (b)); + Assert.IsFalse (c.Contains (CreateValueC ())); + } + + [Test] + public void CopyTo_Exceptions () + { + var c = CreateCollection (new[] { CreateValueA (), CreateValueB (), CreateValueC () }); + Assert.Throws<ArgumentNullException> (() => c.CopyTo (null, 0)); + Assert.Throws<ArgumentOutOfRangeException> (() => c.CopyTo (new T[3], -1)); + var d = new T[5]; + // not enough space from d[3..d.Length-1] to hold c.Count elements. + Assert.Throws<ArgumentException> (() => c.CopyTo (d, 3)); + Assert.Throws<ArgumentException> (() => c.CopyTo (new T[0], 0)); + } + + // can fail for IDictionary<TKey,TValue> implementations; override if appropriate. + [Test] + public virtual void CopyTo_SequenceComparison () + { + var a = CreateValueA (); + var b = CreateValueB (); + var c = CreateValueC (); + + var coll = CreateCollection (new[] { a, b, c }); + var d = new T[5]; + coll.CopyTo (d, 1); + Assert.IsTrue (new[]{ + default (T), a, b, c, default (T), + }.SequenceEqual (d)); + } + + [Test] + public void CopyTo () + { + var a = CreateValueA (); + var b = CreateValueB (); + var c = CreateValueC (); + + var coll = CreateCollection (new[] { a, b, c }); + var d = new T[5]; + coll.CopyTo (d, 1); + Assert.IsTrue (Array.IndexOf (d, a) >= 0); + Assert.IsTrue (Array.IndexOf (d, b) >= 0); + Assert.IsTrue (Array.IndexOf (d, c) >= 0); + } + + [Test] + public void Remove () + { + var a = CreateValueA (); + var b = CreateValueB (); + var c = CreateValueC (); + + var coll = CreateCollection (new[] { a, b }); + int n = coll.Count; + try { + Assert.IsFalse (coll.Remove (c)); + Assert.AreEqual (n, coll.Count); + Assert.IsTrue (coll.Remove (a)); + Assert.AreEqual (n - 1, coll.Count); + } catch (NotSupportedException) { + Assert.IsTrue (coll.IsReadOnly); + } + } + } +} diff --git a/Xamarin.PropertyEditing.Tests/PanelViewModelTests.cs b/Xamarin.PropertyEditing.Tests/PanelViewModelTests.cs index 6d39f22..0626626 100644 --- a/Xamarin.PropertyEditing.Tests/PanelViewModelTests.cs +++ b/Xamarin.PropertyEditing.Tests/PanelViewModelTests.cs @@ -74,10 +74,10 @@ namespace Xamarin.PropertyEditing.Tests vm.SelectedObjects.Add (obj); Assume.That (vm.ArrangedEditors, Is.Not.Empty); - Assume.That (vm.ArrangedEditors[0].Count, Is.EqualTo (2)); + Assume.That (vm.ArrangedEditors[0].Editors.Count, Is.EqualTo (2)); vm.FilterText = "sub"; - Assert.That (vm.ArrangedEditors[0].Count, Is.EqualTo (1)); + Assert.That (vm.ArrangedEditors[0].Editors.Count, Is.EqualTo (1)); } [Test] @@ -93,7 +93,7 @@ namespace Xamarin.PropertyEditing.Tests vm.SelectedObjects.Add (obj); Assume.That (vm.ArrangedEditors, Is.Not.Empty); - Assume.That (vm.ArrangedEditors[0].Count, Is.EqualTo (2)); + Assume.That (vm.ArrangedEditors[0].Editors.Count, Is.EqualTo (2)); Assume.That (vm.IsFiltering, Is.False); bool changed = false; @@ -104,7 +104,7 @@ namespace Xamarin.PropertyEditing.Tests }; vm.FilterText = "sub"; - Assume.That (vm.ArrangedEditors[0].Count, Is.EqualTo (1)); + Assume.That (vm.ArrangedEditors[0].Editors.Count, Is.EqualTo (1)); Assert.That (vm.IsFiltering, Is.True); Assert.That (changed, Is.True); changed = false; @@ -128,13 +128,13 @@ namespace Xamarin.PropertyEditing.Tests vm.SelectedObjects.Add (obj); Assume.That (vm.ArrangedEditors, Is.Not.Empty); - Assume.That (vm.ArrangedEditors[0].Count, Is.EqualTo (2)); + Assume.That (vm.ArrangedEditors[0].Editors.Count, Is.EqualTo (2)); vm.FilterText = "sub"; - Assume.That (vm.ArrangedEditors[0].Count, Is.EqualTo (1)); + Assume.That (vm.ArrangedEditors[0].Editors.Count, Is.EqualTo (1)); vm.FilterText = String.Empty; - Assert.That (vm.ArrangedEditors[0].Count, Is.EqualTo (2)); + Assert.That (vm.ArrangedEditors[0].Editors.Count, Is.EqualTo (2)); } [Test] @@ -149,7 +149,7 @@ namespace Xamarin.PropertyEditing.Tests vm.SelectedObjects.Add (obj); Assume.That (vm.ArrangedEditors, Is.Not.Empty); - Assert.That (vm.ArrangedEditors.FirstOrDefault (g => g.Key == "Sub"), Is.Not.Null); + Assert.That (vm.ArrangedEditors.FirstOrDefault (g => g.Category == "Sub"), Is.Not.Null); } [Test] @@ -180,9 +180,9 @@ namespace Xamarin.PropertyEditing.Tests vm.ArrangeMode = PropertyArrangeMode.Category; vm.SelectedObjects.Add (editor.Target); - Assert.That (vm.ArrangedEditors[0].Key, Is.EqualTo ("A")); - Assert.That (vm.ArrangedEditors[1].Key, Is.EqualTo ("B")); - Assert.That (vm.ArrangedEditors[2].Key, Is.EqualTo ("C")); + Assert.That (vm.ArrangedEditors[0].Category, Is.EqualTo ("A")); + Assert.That (vm.ArrangedEditors[1].Category, Is.EqualTo ("B")); + Assert.That (vm.ArrangedEditors[2].Category, Is.EqualTo ("C")); } [Test] @@ -201,9 +201,9 @@ namespace Xamarin.PropertyEditing.Tests vm.FilterText = "sub"; Assert.That (vm.ArrangedEditors.Count, Is.EqualTo (1)); - var group = vm.ArrangedEditors.FirstOrDefault (g => g.Key == "Sub"); + var group = vm.ArrangedEditors.FirstOrDefault (g => g.Category == "Sub"); Assert.That (group, Is.Not.Null); - Assert.That (group.Count, Is.EqualTo (1)); + Assert.That (group.Editors.Count, Is.EqualTo (1)); } [Test] @@ -236,8 +236,8 @@ namespace Xamarin.PropertyEditing.Tests vm.SelectedObjects.Add (target); Assert.That (vm.ArrangedEditors.Count, Is.EqualTo (2)); - Assert.That (vm.ArrangedEditors[0].Key, Is.EqualTo ("ints"), "Grouped group not found or out of order"); - Assert.That (vm.ArrangedEditors[1].Key, Is.Null); + Assert.That (vm.ArrangedEditors[0].Category, Is.EqualTo ("ints"), "Grouped group not found or out of order"); + Assert.That (vm.ArrangedEditors[1].Category, Is.Empty); } [Test] @@ -272,7 +272,7 @@ namespace Xamarin.PropertyEditing.Tests vm.FilterText = "name"; Assert.That (vm.ArrangedEditors.Count, Is.EqualTo (1)); - var group = vm.ArrangedEditors.FirstOrDefault (g => g.Key == "ints"); + var group = vm.ArrangedEditors.FirstOrDefault (g => g.Category == "ints"); Assert.That (group, Is.Null); } @@ -332,7 +332,7 @@ namespace Xamarin.PropertyEditing.Tests }; vm.SelectedObjects.Add (target); - Assert.That (vm.ArrangedEditors.Any (g => g.Key == "ints"), Is.True, "Does not have grouped editors category"); + Assert.That (vm.ArrangedEditors.Any (g => g.Category == "ints"), Is.True, "Does not have grouped editors category"); } [Test] @@ -350,7 +350,7 @@ namespace Xamarin.PropertyEditing.Tests vm.SelectedObjects.Add (obj); Assume.That (vm.ArrangedEditors, Is.Not.Empty); - Assert.That (vm.GetIsExpanded (vm.ArrangedEditors[0].Key), Is.True); + Assert.That (vm.GetIsExpanded (vm.ArrangedEditors[0].Category), Is.True); } [Test] @@ -383,7 +383,7 @@ namespace Xamarin.PropertyEditing.Tests vm.SelectedObjects.Add (target); Assume.That (vm.ArrangedEditors, Is.Not.Empty); - Assume.That (vm.ArrangedEditors.Any (g => g.Key == "ints"), Is.True, "Does not have grouped editors category"); + Assume.That (vm.ArrangedEditors.Any (g => g.Category == "ints"), Is.True, "Does not have grouped editors category"); Assert.That (vm.GetIsExpanded ("ints"), Is.True); } diff --git a/Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj b/Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj index 61d14ad..85d1e3a 100644 --- a/Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj +++ b/Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj @@ -76,6 +76,7 @@ <Compile Include="MockValueConverter.cs" /> <Compile Include="NumericTests.cs" /> <Compile Include="NumericViewModelTests.cs" /> + <Compile Include="OrderedDictionaryTests.cs" /> <Compile Include="ResourceSelectorViewModelTests.cs" /> <Compile Include="ResourceTests.cs" /> <Compile Include="SimpleCollectionViewTests.cs" /> diff --git a/Xamarin.PropertyEditing.Windows/PropertyEditorPanel.cs b/Xamarin.PropertyEditing.Windows/PropertyEditorPanel.cs index 90ddb10..3fa7203 100644 --- a/Xamarin.PropertyEditing.Windows/PropertyEditorPanel.cs +++ b/Xamarin.PropertyEditing.Windows/PropertyEditorPanel.cs @@ -210,7 +210,7 @@ namespace Xamarin.PropertyEditing.Windows Binding itemsSource; if (newMode == PropertyArrangeMode.Name) - itemsSource = new Binding ("ArrangedEditors[0]"); + itemsSource = new Binding ("ArrangedEditors[0].Editors"); else itemsSource = new Binding ("ArrangedEditors"); diff --git a/Xamarin.PropertyEditing.Windows/Themes/PropertyEditorPanelStyle.xaml b/Xamarin.PropertyEditing.Windows/Themes/PropertyEditorPanelStyle.xaml index 29f7db0..b479b1d 100644 --- a/Xamarin.PropertyEditing.Windows/Themes/PropertyEditorPanelStyle.xaml +++ b/Xamarin.PropertyEditing.Windows/Themes/PropertyEditorPanelStyle.xaml @@ -72,15 +72,90 @@ </Setter> </Style> + <Style TargetType="ToggleButton" x:Key="AdvancedPropertiesToggleButton"> + <Setter Property="OverridesDefaultStyle" Value="True" /> + <Setter Property="Height" Value="14" /> + <Setter Property="Padding" Value="1.5,1,1.5,1" /> + <Setter Property="VerticalAlignment" Value="Stretch" /> + <Setter Property="Background" Value="Transparent" /> + <Setter Property="Foreground" Value="{DynamicResource ToggleItemForegroundBrush}" /> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type ToggleButton}"> + <Border x:Name="ExpanderButtonBorder" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{TemplateBinding Background}" DockPanel.Dock="Top"> + <Path x:Name="Chevron" Data="M 0,3 L 5,8 L 10,3" Stroke="{TemplateBinding Foreground}" Width="10" Height="10" StrokeThickness="2" + HorizontalAlignment="Stretch" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5"/> + </Border> + <ControlTemplate.Triggers> + <Trigger Property="IsChecked" Value="True"> + <Setter Property="Data" TargetName="Chevron" Value="M 0,8 L 5,3 L 10,8"/> + </Trigger> + <Trigger Property="IsMouseOver" Value="true"> + <Setter Property="Stroke" Value="{DynamicResource ToggleItemMouseOverForegroundBrush}" TargetName="Chevron"/> + </Trigger> + <Trigger Property="IsPressed" Value="true"> + <Setter Property="Stroke" Value="{DynamicResource ToggleItemSelectedForegroundBrush}" TargetName="Chevron"/> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + <Style.Triggers> + <Trigger Property="IsMouseOver" Value="True"> + <Setter Property="Background" Value="{DynamicResource AdvancedExpanderMouseOverBackgroundBrush}" /> + <Setter Property="BorderBrush" Value="{DynamicResource AdvancedExpanderMouseOverBorderBrush}" /> + <Setter Property="Foreground" Value="{DynamicResource AdvancedExpanderMouseOverForegroundBrush}" /> + </Trigger> + </Style.Triggers> + </Style> + + <Style TargetType="Expander" x:Key="AdvancedPropertiesExpander"> + <Setter Property="Foreground" Value="{DynamicResource PanelGroupSecondaryForegroundBrush}" /> + <Setter Property="Background" Value="{DynamicResource PanelGroupSecondaryBackgroundBrush}" /> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="Expander"> + <StackPanel Background="{TemplateBinding Background}"> + <ToggleButton + x:Name="ExpanderButton" DockPanel.Dock="Top" Style="{DynamicResource AdvancedPropertiesToggleButton}" Content="{TemplateBinding Header}" + IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"> + </ToggleButton> + <ContentPresenter x:Name="ExpanderContent" Visibility="Collapsed" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/> + </StackPanel> + <ControlTemplate.Triggers> + <Trigger Property="IsExpanded" Value="True"> + <Setter TargetName="ExpanderContent" Property="Visibility" Value="Visible"/> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + <DataTemplate x:Key="PropertyGroupTemplate"> - <local:CategoryExpander Header="{Binding Key,Mode=OneTime}" Style="{StaticResource TreeExpanderStyle}"> - <ItemsControl Style="{StaticResource PropertyListStyle}" ItemsSource="{Binding}"> - <ItemsControl.Template> - <ControlTemplate> - <ItemsPresenter /> - </ControlTemplate> - </ItemsControl.Template> - </ItemsControl> + <local:CategoryExpander Header="{Binding Category,Mode=OneTime}" Style="{StaticResource TreeExpanderStyle}"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <ItemsControl Grid.Row="0" Style="{StaticResource PropertyListStyle}" ItemsSource="{Binding Editors,Mode=OneTime}"> + <ItemsControl.Template> + <ControlTemplate> + <ItemsPresenter /> + </ControlTemplate> + </ItemsControl.Template> + </ItemsControl> + <Expander Grid.Row="1" Visibility="{Binding HasUncommonElements,Converter={StaticResource BoolToVisibilityConverter}}" Style="{StaticResource AdvancedPropertiesExpander}"> + <ItemsControl Grid.Row="0" Style="{StaticResource PropertyListStyle}" ItemsSource="{Binding UncommonEditors,Mode=OneTime}"> + <ItemsControl.Template> + <ControlTemplate> + <ItemsPresenter /> + </ControlTemplate> + </ItemsControl.Template> + </ItemsControl> + </Expander> + </Grid> </local:CategoryExpander> </DataTemplate> diff --git a/Xamarin.PropertyEditing.Windows/Themes/Resources.xaml b/Xamarin.PropertyEditing.Windows/Themes/Resources.xaml index 4ae48b6..9932fd2 100644 --- a/Xamarin.PropertyEditing.Windows/Themes/Resources.xaml +++ b/Xamarin.PropertyEditing.Windows/Themes/Resources.xaml @@ -702,7 +702,7 @@ </StackPanel> </Border> <Border Padding="1" Visibility="{Binding TargetPlatform.SupportsBrushOpacity, Mode=OneTime, Converter={StaticResource BoolToVisibilityConverter}}"> - <Expander Name="advancedPropertyPanel" Template="{DynamicResource AdvancedPropertiesExpander}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" + <Expander Name="advancedPropertyPanel" Style="{DynamicResource AdvancedPropertiesExpander}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Background="{DynamicResource PanelGroupSecondaryBackgroundBrush}" Foreground="{DynamicResource PanelForegroundBrush}"> <Expander.Content> <Border Padding="19,6,6,6" Background="{DynamicResource PanelGroupSecondaryBackgroundBrush}"> @@ -1489,7 +1489,6 @@ </Style> <Style TargetType="local:PropertyPresenter"> - <Setter Property="Background" Value="{DynamicResource PanelBackgroundBrush}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:PropertyPresenter"> @@ -1541,39 +1540,6 @@ </Style.Triggers> </Style> - <ControlTemplate TargetType="Expander" x:Key="AdvancedPropertiesExpander"> - <StackPanel Background="{DynamicResource PanelGroupSecondaryBackgroundBrush}"> - <ToggleButton - x:Name="ExpanderButton" DockPanel.Dock="Top" Template="{DynamicResource AdvancedPropertiesToggleButton}" Content="{TemplateBinding Header}" VerticalAlignment="Stretch" - IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" OverridesDefaultStyle="True" Height="14" Padding="1.5,1,1.5,1"> - </ToggleButton> - <ContentPresenter x:Name="ExpanderContent" Grid.Row="1" Visibility="Collapsed" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/> - </StackPanel> - <ControlTemplate.Triggers> - <Trigger Property="IsExpanded" Value="True"> - <Setter TargetName="ExpanderContent" Property="Visibility" Value="Visible"/> - </Trigger> - </ControlTemplate.Triggers> - </ControlTemplate> - - <ControlTemplate x:Key="AdvancedPropertiesToggleButton" TargetType="{x:Type ToggleButton}"> - <Border x:Name="ExpanderButtonBorder" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{DynamicResource PanelGroupSecondaryBackgroundBrush}" DockPanel.Dock="Top"> - <Path x:Name="Chevron" Data="M 0,3 L 5,8 L 10,3" Stroke="{DynamicResource ToggleItemForegroundBrush}" Width="10" Height="10" StrokeThickness="2" - HorizontalAlignment="Stretch" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5"/> - </Border> - <ControlTemplate.Triggers> - <Trigger Property="IsChecked" Value="True"> - <Setter Property="Data" TargetName="Chevron" Value="M 0,8 L 5,3 L 10,8"/> - </Trigger> - <Trigger Property="IsMouseOver" Value="true"> - <Setter Property="Stroke" Value="{DynamicResource ToggleItemMouseOverForegroundBrush}" TargetName="Chevron"/> - </Trigger> - <Trigger Property="IsPressed" Value="true"> - <Setter Property="Stroke" Value="{DynamicResource ToggleItemSelectedForegroundBrush}" TargetName="Chevron"/> - </Trigger> - </ControlTemplate.Triggers> - </ControlTemplate> - <Style TargetType="{x:Type TabItem}"> <Setter Property="Foreground" Value="{DynamicResource PanelForegroundBrush}"/> <Setter Property="BorderThickness" Value="1"/> diff --git a/Xamarin.PropertyEditing/IPropertyInfo.cs b/Xamarin.PropertyEditing/IPropertyInfo.cs index 2727ca0..26d1927 100644 --- a/Xamarin.PropertyEditing/IPropertyInfo.cs +++ b/Xamarin.PropertyEditing/IPropertyInfo.cs @@ -33,6 +33,14 @@ namespace Xamarin.PropertyEditing bool CanWrite { get; } /// <summary> + /// Gets whether the property is an uncommonly used property. + /// </summary> + /// <remarks> + /// This acts as a hint to hide the property behind disclosures when appropriate. + /// </remarks> + bool IsUncommon { get; } + + /// <summary> /// Gets the possible sources of values for this property. /// </summary> ValueSources ValueSources { get; } diff --git a/Xamarin.PropertyEditing/OrderedDictionary.cs b/Xamarin.PropertyEditing/OrderedDictionary.cs index 69df576..4b2ab91 100644 --- a/Xamarin.PropertyEditing/OrderedDictionary.cs +++ b/Xamarin.PropertyEditing/OrderedDictionary.cs @@ -30,11 +30,13 @@ using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; +using Xamarin.PropertyEditing; namespace Cadenza.Collections { internal class OrderedDictionary<TKey, TValue> - : IDictionary<TKey, TValue>, IList<KeyValuePair<TKey, TValue>>, IReadOnlyOrderedDictionary<TKey, TValue> + : IDictionary<TKey, TValue>, IList<KeyValuePair<TKey, TValue>>, IReadOnlyOrderedDictionary<TKey, TValue>, INotifyCollectionChanged { public OrderedDictionary () : this (0) @@ -56,7 +58,7 @@ namespace Cadenza.Collections this.dict = new Dictionary<TKey, TValue> (capacity, equalityComparer); this.kvpCollection = this.dict; this.keyOrder = new List<TKey> (capacity); - this.roKeys = new ReadOnlyCollection<TKey> (this.keyOrder); + this.roKeys = new ReadOnlyKeysCollection (this.keyOrder); this.roValues = new ReadOnlyValueCollection (this); } @@ -75,6 +77,8 @@ namespace Cadenza.Collections Add (kvp.Key, kvp.Value); } + public event NotifyCollectionChangedEventHandler CollectionChanged; + bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly { get { return false; } @@ -99,10 +103,24 @@ namespace Cadenza.Collections get { return this.dict[key]; } set { - if (!this.dict.ContainsKey (key)) + int index = -1; + bool replace = false; + if (!this.dict.TryGetValue (key, out TValue oldValue)) this.keyOrder.Add (key); + else { + replace = true; + index = this.keyOrder.IndexOf (key); + } this.dict[key] = value; + + if (replace) { + this.roValues.OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, oldValue, value, index)); + OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, + new KeyValuePair<TKey, TValue> (key, oldValue), new KeyValuePair<TKey, TValue> (key, value))); + } else { + OnCollectionChanged (NotifyCollectionChangedAction.Add, this.keyOrder.Count - 1, key, value); + } } } @@ -137,8 +155,22 @@ namespace Cadenza.Collections get { return new KeyValuePair<TKey, TValue> (this.keyOrder[index], this[index]); } set { - keyOrder[index] = value.Key; + TKey existingKey = this.keyOrder[index]; + TValue existingValue = this.dict[existingKey]; + if (!Equals (existingKey, value.Key)) { + if (this.dict.ContainsKey (value.Key)) + throw new ArgumentException ("Existing keys can't be moved by setting them into another index", $"{nameof(value)}.{nameof(value.Key)}"); + + this.keyOrder[index] = value.Key; + this.dict.Remove (existingKey); + } + this.dict[value.Key] = value.Value; + + this.roKeys.OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, existingKey, value.Key, index)); + this.roValues.OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, existingValue, value.Value, index)); + OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, + new KeyValuePair<TKey, TValue> (existingKey, existingValue), new KeyValuePair<TKey, TValue> (value.Key, value.Value))); } } @@ -282,8 +314,11 @@ namespace Cadenza.Collections /// <exception cref="ArgumentException"><paramref name="key"/> already exists in the dictionary.</exception> public void Add (TKey key, TValue value) { + int index = this.keyOrder.Count; this.dict.Add (key, value); this.keyOrder.Add (key); + + OnCollectionChanged (NotifyCollectionChangedAction.Add, index, key, value); } void ICollection<KeyValuePair<TKey, TValue>>.Add (KeyValuePair<TKey, TValue> item) @@ -301,8 +336,13 @@ namespace Cadenza.Collections /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0 or greater than <see cref="Count"/></exception> public void Insert (int index, TKey key, TValue value) { - this.keyOrder.Insert (index, key); + if (index < 0 || index > this.keyOrder.Count) + throw new ArgumentOutOfRangeException (nameof(index)); + this.dict.Add (key, value); + this.keyOrder.Insert (index, key); + + OnCollectionChanged (NotifyCollectionChangedAction.Add, index, key, value); } void IList<KeyValuePair<TKey, TValue>>.Insert (int index, KeyValuePair<TKey, TValue> item) @@ -318,22 +358,42 @@ namespace Cadenza.Collections /// <exception cref="ArgumentNullException"><paramref name="key"/> is <c>null</c>.</exception> public bool Remove (TKey key) { - return (this.dict.Remove (key) && this.keyOrder.Remove (key)); + if (key == null) + throw new ArgumentNullException (nameof(key)); + + int index = this.keyOrder.IndexOf (key); + if (index > -1) { + RemoveAt (index); + return true; + } + + return false; } bool ICollection<KeyValuePair<TKey, TValue>>.Remove (KeyValuePair<TKey, TValue> item) { - return (kvpCollection.Remove (item) && this.keyOrder.Remove (item.Key)); + int index = this.keyOrder.IndexOf (item.Key); + if (index > -1) { + RemoveAt (index); + return true; + } + + return false; } /// <summary> /// Removes they key and associated value from the dictionary located at <paramref name="index"/>. /// </summary> /// <param name="index">The index at which to remove an item.</param> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0 or greater than <see cref="Count"/></exception> public void RemoveAt (int index) { TKey key = this.keyOrder[index]; - Remove (key); + + this.keyOrder.RemoveAt (index); + this.dict.TryRemove (key, out var value); + + OnCollectionChanged (NotifyCollectionChangedAction.Remove, index, key, value); } /// <summary> @@ -343,6 +403,11 @@ namespace Cadenza.Collections { this.dict.Clear (); this.keyOrder.Clear (); + + var args = new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Reset); + this.roKeys.OnCollectionChanged (args); + this.roValues.OnCollectionChanged (args); + OnCollectionChanged (args); } void ICollection<KeyValuePair<TKey, TValue>>.CopyTo (KeyValuePair<TKey, TValue>[] array, int arrayIndex) @@ -372,19 +437,49 @@ namespace Cadenza.Collections } private readonly ReadOnlyValueCollection roValues; - private readonly ReadOnlyCollection<TKey> roKeys; + private readonly ReadOnlyKeysCollection roKeys; private readonly ICollection<KeyValuePair<TKey, TValue>> kvpCollection; private readonly Dictionary<TKey, TValue> dict; private readonly List<TKey> keyOrder; + private void OnCollectionChanged (NotifyCollectionChangedAction action, int index, TKey key, TValue value) + { + OnCollectionChanged (new NotifyCollectionChangedEventArgs (action, new KeyValuePair<TKey, TValue> (key, value), index)); + this.roKeys.OnCollectionChanged (new NotifyCollectionChangedEventArgs (action, key, index)); + this.roValues.OnCollectionChanged (new NotifyCollectionChangedEventArgs (action, value, index)); + } + + private void OnCollectionChanged (NotifyCollectionChangedEventArgs e) + { + CollectionChanged?.Invoke (this, e); + } + + private class ReadOnlyKeysCollection + : ReadOnlyCollection<TKey>, INotifyCollectionChanged + { + public ReadOnlyKeysCollection (IList<TKey> list) + : base (list) + { + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + internal void OnCollectionChanged (NotifyCollectionChangedEventArgs e) + { + CollectionChanged?.Invoke (this, e); + } + } + private class ReadOnlyValueCollection - : IList<TValue> + : IList<TValue>, IReadOnlyList<TValue>, INotifyCollectionChanged { public ReadOnlyValueCollection (OrderedDictionary<TKey, TValue> dict) { this.odict = dict; } + public event NotifyCollectionChangedEventHandler CollectionChanged; + public void Add (TValue item) { throw new NotSupportedException (); @@ -462,6 +557,11 @@ namespace Cadenza.Collections } private readonly OrderedDictionary<TKey, TValue> odict; + + internal void OnCollectionChanged (NotifyCollectionChangedEventArgs e) + { + CollectionChanged?.Invoke (this, e); + } } } }
\ No newline at end of file diff --git a/Xamarin.PropertyEditing/Reflection/ReflectionPropertyInfo.cs b/Xamarin.PropertyEditing/Reflection/ReflectionPropertyInfo.cs index 9734dfe..005c649 100644 --- a/Xamarin.PropertyEditing/Reflection/ReflectionPropertyInfo.cs +++ b/Xamarin.PropertyEditing/Reflection/ReflectionPropertyInfo.cs @@ -47,6 +47,8 @@ namespace Xamarin.PropertyEditing.Reflection public bool CanWrite => this.propertyInfo.CanWrite; + public bool IsUncommon => false; + public ValueSources ValueSources => ValueSources.Local; public IReadOnlyList<PropertyVariationOption> Variations => EmtpyVariationOptions; diff --git a/Xamarin.PropertyEditing/ViewModels/PanelViewModel.cs b/Xamarin.PropertyEditing/ViewModels/PanelViewModel.cs index ce8ebd7..25ff232 100644 --- a/Xamarin.PropertyEditing/ViewModels/PanelViewModel.cs +++ b/Xamarin.PropertyEditing/ViewModels/PanelViewModel.cs @@ -1,11 +1,114 @@ -using System; +using System; using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading.Tasks; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Cadenza.Collections; namespace Xamarin.PropertyEditing.ViewModels { + internal class PanelGroupViewModel + : NotifyingObject + { + public PanelGroupViewModel (string category, IEnumerable<EditorViewModel> editors, bool separateUncommon = true) + { + if (editors == null) + throw new ArgumentNullException (nameof(editors)); + + Category = category; + AddCore (editors, separateUncommon); + } + + public string Category + { + get; + } + + public IReadOnlyList<EditorViewModel> Editors => this.editors; + + public IReadOnlyList<EditorViewModel> UncommonEditors => this.uncommonEditors; + + public bool HasChildElements => Editors.Count > 0 || HasUncommonElements; + + public bool HasUncommonElements => UncommonEditors.Count > 0; + + public bool UncommonShown + { + get; + set; + } + + public void Add (IEnumerable<EditorViewModel> editors) + { + AddCore (editors, separate: true); + } + + public void Add (EditorViewModel editor) + { + AddCore (editor, separate: true); + } + + public bool Remove (EditorViewModel editor) + { + if (editor == null) + throw new ArgumentNullException (nameof(editor)); + + return GetList (editor, separate: true).Remove (editor); + } + + public bool GetIsExpanded (PropertyArrangeMode mode) + { + if (this.isExpanded == null) + return false; + + this.isExpanded.TryGetValue (mode, out bool expanded); + return expanded; + } + + public void SetIsExpanded (PropertyArrangeMode mode, bool expanded) + { + if (this.isExpanded == null) { + if (!expanded) + return; + + this.isExpanded = new Dictionary<PropertyArrangeMode, bool> (); + } + + this.isExpanded[mode] = expanded; + } + + private Dictionary<PropertyArrangeMode, bool> isExpanded; + private readonly ObservableCollectionEx<EditorViewModel> editors = new ObservableCollectionEx<EditorViewModel> (); + private readonly ObservableCollectionEx<EditorViewModel> uncommonEditors = new ObservableCollectionEx<EditorViewModel> (); + + private void AddCore (IEnumerable<EditorViewModel> editors, bool separate) + { + if (editors == null) + throw new ArgumentNullException (nameof (editors)); + + foreach (EditorViewModel evm in editors) + AddCore (evm, separate); + } + + private void AddCore (EditorViewModel editor, bool separate) + { + if (editor == null) + throw new ArgumentNullException (nameof (editor)); + + GetList (editor, separate).Add (editor); + OnPropertyChanged (nameof(HasChildElements)); + OnPropertyChanged (nameof(HasUncommonElements)); + } + + private IList<EditorViewModel> GetList (EditorViewModel evm, bool separate) + { + if (separate && evm is PropertyViewModel pvm) + return pvm.Property.IsUncommon ? this.uncommonEditors : this.editors; + else + return this.editors; + } + } + internal class PanelViewModel : PropertiesViewModel, IFilterable { @@ -20,8 +123,11 @@ namespace Xamarin.PropertyEditing.ViewModels public event EventHandler ArrangedPropertiesChanged; - public IReadOnlyList<IGroupingList<string, EditorViewModel>> ArrangedEditors => this.arranged; + public IReadOnlyList<PanelGroupViewModel> ArrangedEditors => (IReadOnlyList<PanelGroupViewModel>)this.arranged.Values; + /// <summary> + /// Gets or sets whether all categories should automatically expand. + /// </summary> public bool AutoExpand { get { return this.autoExpand; } @@ -78,11 +184,10 @@ namespace Xamarin.PropertyEditing.ViewModels public bool GetIsExpanded (string group) { - HashSet<string> groups; - if (!this.expandedGroups.TryGetValue (ArrangeMode, out groups)) + if (group == null || !this.arranged.TryGetValue (group, out PanelGroupViewModel panelGroup)) return false; - return groups.Contains (group); + return panelGroup.GetIsExpanded (ArrangeMode); } public void SetIsExpanded (string group, bool isExpanded) @@ -108,6 +213,8 @@ namespace Xamarin.PropertyEditing.ViewModels Dictionary<string, List<PropertyViewModel>> groupedTypeProperties = null; + bool isFlat = ArrangeMode == PropertyArrangeMode.Name; + this.arranged.Clear (); foreach (var grouping in props.GroupBy (GetGroup).OrderBy (g => g.Key, CategoryComparer.Instance)) { HashSet<EditorViewModel> remainingItems = null; @@ -131,37 +238,37 @@ namespace Xamarin.PropertyEditing.ViewModels } } - AutoExpandGroup (grouping.Key); - if (remainingItems != null) - this.arranged.Add (grouping.Key, remainingItems); + string key = grouping.Key ?? String.Empty; + if (remainingItems != null) // TODO: pretty sure this was out of order before, add test + this.arranged.Add (key, new PanelGroupViewModel (key, grouping.Where (evm => remainingItems.Contains (evm)))); else - this.arranged.Add (grouping); + this.arranged.Add (key, new PanelGroupViewModel (key, grouping, separateUncommon: !isFlat)); + + AutoExpandGroup (key); } if (groupedTypeProperties != null) { // Insert type-grouped properties back in sorted. int i = 0; foreach (var kvp in groupedTypeProperties.OrderBy (kvp => kvp.Key, CategoryComparer.Instance)) { - var group = new ObservableGrouping<string, EditorViewModel> (kvp.Key) { - new PropertyGroupViewModel (TargetPlatform, kvp.Key, kvp.Value, ObjectEditors) - }; - - AutoExpandGroup (group.Key); + var group = new PanelGroupViewModel (kvp.Key, new[] { new PropertyGroupViewModel (TargetPlatform, kvp.Key, kvp.Value, ObjectEditors) }); bool added = false; for (; i < this.arranged.Count; i++) { - var g = (IGrouping<string, EditorViewModel>) this.arranged[i]; + var g = this.arranged[i]; // TODO: Are we translating categories? If so this needs to lookup the resource and be culture specific // nulls go on the bottom. - if (g.Key == null || String.Compare (g.Key, kvp.Key, StringComparison.Ordinal) > 0) { + if (String.IsNullOrEmpty (g.Category) || String.Compare (g.Category, kvp.Key, StringComparison.Ordinal) > 0) { added = true; - this.arranged.Insert (i, group); + this.arranged.Insert (i, group.Category, group); break; } } if (!added) - this.arranged.Add (group); + this.arranged.Add (group.Category, group); + + AutoExpandGroup (group.Category); } } @@ -172,10 +279,13 @@ namespace Xamarin.PropertyEditing.ViewModels { foreach (EditorViewModel vm in editors) { string g = GetGroup (vm); - var grouping = this.arranged[g] as ObservableGrouping<string, EditorViewModel>; - if (grouping != null) { - this.arranged.Remove (g, vm); - } + PanelGroupViewModel group = this.arranged[g]; + if (group == null) + continue; + + group.Remove (vm); + if (!group.HasChildElements) + this.arranged.Remove (group.Category); } ArrangedPropertiesChanged?.Invoke (this, EventArgs.Empty); @@ -188,10 +298,7 @@ namespace Xamarin.PropertyEditing.ViewModels ArrangedPropertiesChanged?.Invoke (this, EventArgs.Empty); } - private readonly Dictionary<PropertyArrangeMode, HashSet<string>> expandedGroups = new Dictionary<PropertyArrangeMode, HashSet<string>> (); - private readonly ObservableLookup<string, EditorViewModel> arranged = new ObservableLookup<string, EditorViewModel> { - ReuseGroups = true - }; + private readonly OrderedDictionary<string, PanelGroupViewModel> arranged = new OrderedDictionary<string, PanelGroupViewModel> (); private PropertyArrangeMode arrangeMode; private string filterText; @@ -199,38 +306,35 @@ namespace Xamarin.PropertyEditing.ViewModels private void AutoExpandGroup (string group) { - if (AutoExpand || (group != null && TargetPlatform.AutoExpandGroups != null && TargetPlatform.AutoExpandGroups.Contains (group))) - UpdateExpanded (new[] { group }, true); + if (group == null || !this.arranged.TryGetValue (group, out PanelGroupViewModel panelGroup)) + return; + if (!AutoExpand && (TargetPlatform.AutoExpandGroups == null || !TargetPlatform.AutoExpandGroups.Contains (group))) + return; + + UpdateExpanded (new[] { panelGroup }, true); } private void SetIsExpanded (PropertyArrangeMode mode, string group, bool isExpanded) { - if (!this.expandedGroups.TryGetValue (mode, out HashSet<string> groups)) { - if (!isExpanded) - return; - - this.expandedGroups[mode] = groups = new HashSet<string> (); - } + if (!this.arranged.TryGetValue (group, out PanelGroupViewModel panelGroup) || mode == PropertyArrangeMode.Name) + return; - if (isExpanded) - groups.Add (group); - else - groups.Remove (group); + panelGroup.SetIsExpanded (mode, isExpanded); } private void UpdateExpanded (bool expanded) { - UpdateExpanded (this.arranged.Select<IGroupingList<string, EditorViewModel>, string> (g => g.Key), expanded); + UpdateExpanded (this.arranged.Values, expanded); } - private void UpdateExpanded (IEnumerable<string> groups, bool expanded) + private void UpdateExpanded (IEnumerable<PanelGroupViewModel> groups, bool expanded) { - foreach (string group in groups) { + foreach (PanelGroupViewModel group in groups) { foreach (var mode in ArrangeModes) { if (mode.ArrangeMode == PropertyArrangeMode.Name) continue; - SetIsExpanded (mode.ArrangeMode, group, expanded); + group.SetIsExpanded (mode.ArrangeMode, expanded); } } } @@ -248,8 +352,8 @@ namespace Xamarin.PropertyEditing.ViewModels if (FilterText != null && (String.IsNullOrWhiteSpace (oldFilter) || FilterText.StartsWith (oldFilter, StringComparison.OrdinalIgnoreCase))) { var toRemove = new List<EditorViewModel> (); - foreach (var g in this.arranged) { - foreach (var vm in g) { + foreach (PanelGroupViewModel g in this.arranged.Values) { + foreach (EditorViewModel vm in g.Editors.Concat (g.UncommonEditors)) { if (!MatchesFilter (vm)) toRemove.Add (vm); else if (vm is IFilterable) { @@ -272,14 +376,14 @@ namespace Xamarin.PropertyEditing.ViewModels private string GetGroup (EditorViewModel vm) { - return (ArrangeMode == PropertyArrangeMode.Name) ? "0" : vm.Category; + return (ArrangeMode == PropertyArrangeMode.Name) ? "0" : (vm.Category ?? String.Empty); } private bool MatchesFilter (EditorViewModel vm) { if (String.IsNullOrWhiteSpace (FilterText)) return true; - if (ArrangeMode == PropertyArrangeMode.Category && vm.Category != null && vm.Category.Contains (FilterText, StringComparison.OrdinalIgnoreCase)) + if (ArrangeMode == PropertyArrangeMode.Category && !String.IsNullOrEmpty (vm.Category) && vm.Category.Contains (FilterText, StringComparison.OrdinalIgnoreCase)) return true; if (String.IsNullOrWhiteSpace (vm.Name)) return false; |