From e6b05f1b4a641da4bec154546662d3d1e1150e2a Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 3 Mar 2013 16:46:12 +0100 Subject: [PATCH] Change IModelCollection to use a strongly-typed event handler. --- .../UnitTesting/Model/TestCollection.cs | 65 ++---- .../UnitTesting/Model/TestSolution.cs | 28 +-- .../UnitTesting/NUnit/NUnitTestClass.cs | 9 +- .../Analysis/UnitTesting/Pad/UnitTestNode.cs | 22 +- .../TestProjectWithOneClassTestFixture.cs | 8 +- .../FakePackageManagementProjectService.cs | 15 +- .../PackageManagementConsoleViewModel.cs | 23 +- .../PackageManagementConsoleViewModelTests.cs | 8 +- .../Base/Project/Dom/ConcatModelCollection.cs | 153 ------------- .../Base/Project/Dom/FilterModelCollection.cs | 104 --------- src/Main/Base/Project/Dom/IModelCollection.cs | 57 ++--- .../Dom/ITypeDefinitionModelCollection.cs | 2 +- .../Base/Project/Dom/KeyedModelCollection.cs | 54 ----- .../Base/Project/Dom/ModelCollectionLinq.cs | 191 ++++++++++++++++ .../Project/Dom/ReadOnlyModelCollection.cs | 22 ++ .../Base/Project/Dom/SimpleModelCollection.cs | 206 ++++++++++++++++++ .../Project/ICSharpCode.SharpDevelop.csproj | 6 +- .../IConfigurationOrPlatformNameCollection.cs | 2 +- src/Main/Base/Project/Project/ISolution.cs | 2 +- src/Main/Base/Test/Dom/CSharpModelTests.cs | 21 +- .../Test/Utils/ModelCollectionEventCheck.cs | 33 +-- .../NestedTypeDefinitionModelCollection.cs | 13 +- .../TopLevelTypeDefinitionModelCollection.cs | 13 +- .../SharpDevelop/Dom/TypeDefinitionModel.cs | 14 +- ...onConfigurationOrPlatformNameCollection.cs | 51 ++++- .../SharpDevelop/Project/ProjectService.cs | 10 +- src/Main/SharpDevelop/Project/Solution.cs | 33 ++- .../SharpDevelop/Project/SolutionFolder.cs | 54 +---- 28 files changed, 640 insertions(+), 579 deletions(-) delete mode 100644 src/Main/Base/Project/Dom/ConcatModelCollection.cs delete mode 100644 src/Main/Base/Project/Dom/FilterModelCollection.cs delete mode 100644 src/Main/Base/Project/Dom/KeyedModelCollection.cs create mode 100644 src/Main/Base/Project/Dom/ModelCollectionLinq.cs create mode 100644 src/Main/Base/Project/Dom/ReadOnlyModelCollection.cs create mode 100644 src/Main/Base/Project/Dom/SimpleModelCollection.cs diff --git a/src/AddIns/Analysis/UnitTesting/Model/TestCollection.cs b/src/AddIns/Analysis/UnitTesting/Model/TestCollection.cs index c7b12f9561..9cc497b9a5 100644 --- a/src/AddIns/Analysis/UnitTesting/Model/TestCollection.cs +++ b/src/AddIns/Analysis/UnitTesting/Model/TestCollection.cs @@ -15,7 +15,7 @@ namespace ICSharpCode.UnitTesting /// Collection of tests that monitors the amount of tests for each result type, /// allowing efficient updates of the overall result. /// - public class TestCollection : ObservableCollection, IModelCollection + public class TestCollection : SimpleModelCollection, IModelCollection, INotifyPropertyChanged { #region Struct TestCounts struct TestCounts @@ -133,10 +133,13 @@ namespace ICSharpCode.UnitTesting OnPropertyChanged(compositeResultArgs); } - // change visibility of PropertyChanged event to public - public new event PropertyChangedEventHandler PropertyChanged { - add { base.PropertyChanged += value; } - remove { base.PropertyChanged -= value; } + public event PropertyChangedEventHandler PropertyChanged; + + void OnPropertyChanged(PropertyChangedEventArgs e) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, e); } #endregion @@ -148,53 +151,25 @@ namespace ICSharpCode.UnitTesting RaiseCountChangeEvents(old); } - protected override void ClearItems() - { - TestCounts old = counts; - counts = default(TestCounts); - foreach (ITest test in Items) { - counts.Remove(test.Result); - test.ResultChanged -= item_ResultChanged; - } - base.ClearItems(); - RaiseCountChangeEvents(old); - } - - protected override void InsertItem(int index, ITest item) + protected override void ValidateItem(ITest item) { if (item == null) throw new ArgumentNullException("item"); - TestCounts old = counts; - counts.Add(item.Result); - base.InsertItem(index, item); - item.ResultChanged += item_ResultChanged; - RaiseCountChangeEvents(old); - } - - protected override void RemoveItem(int index) - { - TestCounts old = counts; - ITest oldItem = Items[index]; - counts.Remove(oldItem.Result); - oldItem.ResultChanged -= item_ResultChanged; - base.RemoveItem(index); - RaiseCountChangeEvents(old); + base.ValidateItem(item); } - protected override void SetItem(int index, ITest item) + protected override void OnCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) { - if (item == null) - throw new ArgumentNullException("item"); TestCounts old = counts; - - ITest oldItem = Items[index]; - counts.Remove(oldItem.Result); - oldItem.ResultChanged -= item_ResultChanged; - - counts.Add(item.Result); - item.ResultChanged += item_ResultChanged; - - base.SetItem(index, item); + foreach (ITest test in removedItems) { + counts.Remove(test.Result); + test.ResultChanged -= item_ResultChanged; + } + foreach (ITest test in addedItems) { + counts.Add(test.Result); + test.ResultChanged += item_ResultChanged; + } + base.OnCollectionChanged(removedItems, addedItems); RaiseCountChangeEvents(old); } } diff --git a/src/AddIns/Analysis/UnitTesting/Model/TestSolution.cs b/src/AddIns/Analysis/UnitTesting/Model/TestSolution.cs index 187e503795..6d7b9f10e5 100644 --- a/src/AddIns/Analysis/UnitTesting/Model/TestSolution.cs +++ b/src/AddIns/Analysis/UnitTesting/Model/TestSolution.cs @@ -136,30 +136,16 @@ namespace ICSharpCode.UnitTesting } } - void OnProjectsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + void OnProjectsCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) { - if (e.Action == NotifyCollectionChangedAction.Reset) { - for (int i = 0; i < changeListeners.Count; i++) { + for (int i = 0; i < changeListeners.Count; i++) { + if (removedItems.Contains(changeListeners[i].project)) { changeListeners[i].Stop(); + changeListeners.RemoveAt(i--); } - changeListeners.Clear(); - foreach (var project in SD.ProjectService.AllProjects) { - AddProject(project); - } - } else { - if (e.OldItems != null) { - for (int i = 0; i < changeListeners.Count; i++) { - if (e.OldItems.Contains(changeListeners[i].project)) { - changeListeners[i].Stop(); - changeListeners.RemoveAt(i--); - } - } - } - if (e.NewItems != null) { - foreach (var project in e.NewItems.OfType()) { - AddProject(project); - } - } + } + foreach (var project in addedItems) { + AddProject(project); } } diff --git a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestClass.cs b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestClass.cs index 995e65aa79..f6c500b4c8 100644 --- a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestClass.cs +++ b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestClass.cs @@ -111,13 +111,8 @@ namespace ICSharpCode.UnitTesting /// public NUnitTestMethod FindTestMethodWithShortName(string name) { - // Go backwards because base class tests come first - for (int i = this.NestedTestCollection.Count - 1; i >= 0; i--) { - var method = this.NestedTestCollection[i] as NUnitTestMethod; - if (method != null && method.MethodName == name) - return method; - } - return null; + // Use last match because base class tests come first + return this.NestedTestCollection.OfType().LastOrDefault(method => method.MethodName == name); } /// diff --git a/src/AddIns/Analysis/UnitTesting/Pad/UnitTestNode.cs b/src/AddIns/Analysis/UnitTesting/Pad/UnitTestNode.cs index 122d2151c3..7f78688c19 100644 --- a/src/AddIns/Analysis/UnitTesting/Pad/UnitTestNode.cs +++ b/src/AddIns/Analysis/UnitTesting/Pad/UnitTestNode.cs @@ -75,30 +75,14 @@ namespace ICSharpCode.UnitTesting } } - void test_NestedTests_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + void test_NestedTests_CollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) { if (!IsVisible) { SwitchBackToLazyLoading(); return; } - switch (e.Action) { - case NotifyCollectionChangedAction.Add: - InsertNestedTests(e.NewItems.Cast()); - break; - case NotifyCollectionChangedAction.Remove: - Children.RemoveAll(n => e.OldItems.Contains(n.Model)); - break; - case NotifyCollectionChangedAction.Reset: - if (IsExpanded) { - Children.Clear(); - InsertNestedTests(test.NestedTests); - } else { - SwitchBackToLazyLoading(); - } - break; - default: - throw new NotSupportedException("Invalid value for NotifyCollectionChangedAction"); - } + Children.RemoveAll(n => removedItems.Contains(n.Model)); + InsertNestedTests(addedItems); } void SwitchBackToLazyLoading() diff --git a/src/AddIns/Analysis/UnitTesting/Test/Project/TestProjectWithOneClassTestFixture.cs b/src/AddIns/Analysis/UnitTesting/Test/Project/TestProjectWithOneClassTestFixture.cs index b3807ca4fe..dbfe4d6274 100644 --- a/src/AddIns/Analysis/UnitTesting/Test/Project/TestProjectWithOneClassTestFixture.cs +++ b/src/AddIns/Analysis/UnitTesting/Test/Project/TestProjectWithOneClassTestFixture.cs @@ -185,12 +185,10 @@ namespace RootNamespace { Assert.AreEqual("NewMethod", testClass.NestedTests.Single().DisplayName); } - void testProject_TestClasses_CollectionChanged(object source, NotifyCollectionChangedEventArgs e) + void testProject_TestClasses_CollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) { - if (e.Action == NotifyCollectionChangedAction.Add) - classesAdded.AddRange(e.NewItems.Cast()); - else if (e.Action == NotifyCollectionChangedAction.Remove) - classesRemoved.AddRange(e.OldItems.Cast()); + classesRemoved.AddRange(removedItems.Cast()); + classesAdded.AddRange(addedItems.Cast()); } } } diff --git a/src/AddIns/Misc/PackageManagement/Project/Src/Design/FakePackageManagementProjectService.cs b/src/AddIns/Misc/PackageManagement/Project/Src/Design/FakePackageManagementProjectService.cs index b24abf5d4b..392648b7c1 100644 --- a/src/AddIns/Misc/PackageManagement/Project/Src/Design/FakePackageManagementProjectService.cs +++ b/src/AddIns/Misc/PackageManagement/Project/Src/Design/FakePackageManagementProjectService.cs @@ -32,15 +32,20 @@ namespace ICSharpCode.PackageManagement.Design } } - public readonly ConcatModelCollection AllProjects = new ConcatModelCollection(); - - IModelCollection IPackageManagementProjectService.AllProjects { - get { return AllProjects; } + public readonly SimpleModelCollection> ProjectCollections = new SimpleModelCollection>(); + IModelCollection allProjects; + + public IModelCollection AllProjects { + get { + if (allProjects == null) + allProjects = ProjectCollections.SelectMany(c => c); + return allProjects; + } } public void AddProject(IProject project) { - AllProjects.Inputs.Add(new ReadOnlyModelCollection(new[] { project })); + ProjectCollections.Add(new ReadOnlyModelCollection(new[] { project })); } public void AddProjectItem(IProject project, ProjectItem item) diff --git a/src/AddIns/Misc/PackageManagement/Project/Src/Scripting/PackageManagementConsoleViewModel.cs b/src/AddIns/Misc/PackageManagement/Project/Src/Scripting/PackageManagementConsoleViewModel.cs index 4314fcf513..fb9ab8c30f 100644 --- a/src/AddIns/Misc/PackageManagement/Project/Src/Scripting/PackageManagementConsoleViewModel.cs +++ b/src/AddIns/Misc/PackageManagement/Project/Src/Scripting/PackageManagementConsoleViewModel.cs @@ -44,11 +44,13 @@ namespace ICSharpCode.PackageManagement.Scripting void Init() { + projectService.AllProjects.CollectionChanged += OnProjectCollectionChanged; + projects = new ObservableCollection(projectService.AllProjects); + CreateCommands(); UpdatePackageSourceViewModels(); ReceiveNotificationsWhenPackageSourcesUpdated(); UpdateDefaultProject(); - ReceiveNotificationsWhenSolutionIsUpdated(); InitConsoleHost(); } @@ -142,13 +144,14 @@ namespace ICSharpCode.PackageManagement.Scripting DefaultProject = this.Projects.FirstOrDefault(); } - void ReceiveNotificationsWhenSolutionIsUpdated() - { - projectService.AllProjects.CollectionChanged += OnProjectCollectionChanged; - } - - void OnProjectCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + void OnProjectCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) { + foreach (var removedProject in removedItems) { + projects.Remove(removedProject); + } + foreach (var addedProject in addedItems) { + projects.Add(addedProject); + } UpdateDefaultProject(); } @@ -173,8 +176,10 @@ namespace ICSharpCode.PackageManagement.Scripting return null; } - public IModelCollection Projects { - get { return projectService.AllProjects; } + ObservableCollection projects; + + public ObservableCollection Projects { + get { return projects; } } public IProject DefaultProject { diff --git a/src/AddIns/Misc/PackageManagement/Test/Src/Scripting/PackageManagementConsoleViewModelTests.cs b/src/AddIns/Misc/PackageManagement/Test/Src/Scripting/PackageManagementConsoleViewModelTests.cs index c71ba6ff8c..f7024b2830 100644 --- a/src/AddIns/Misc/PackageManagement/Test/Src/Scripting/PackageManagementConsoleViewModelTests.cs +++ b/src/AddIns/Misc/PackageManagement/Test/Src/Scripting/PackageManagementConsoleViewModelTests.cs @@ -102,7 +102,7 @@ namespace PackageManagement.Tests.Scripting ISolution solution = CreateSolutionWithOneProject(); projectService = new FakePackageManagementProjectService(); projectService.OpenSolution = solution; - projectService.AllProjects.Inputs.Add(solution.Projects); + projectService.ProjectCollections.Add(solution.Projects); CreateViewModel(consoleHost, projectService); return solution; @@ -151,7 +151,7 @@ namespace PackageManagement.Tests.Scripting var solution = ProjectHelper.CreateSolution(); projectService = new FakePackageManagementProjectService(); projectService.OpenSolution = solution; - projectService.AllProjects.Inputs.Add(solution.Projects); + projectService.ProjectCollections.Add(solution.Projects); CreateViewModel(consoleHost, projectService); return solution; } @@ -166,14 +166,14 @@ namespace PackageManagement.Tests.Scripting { ISolution solution = projectService.OpenSolution; projectService.OpenSolution = null; - projectService.AllProjects.Inputs.Remove(solution.Projects); + projectService.ProjectCollections.Remove(solution.Projects); projectService.FireSolutionClosedEvent(solution); } void OpenSolution(ISolution solution) { projectService.OpenSolution = solution; - projectService.AllProjects.Inputs.Add(solution.Projects); + projectService.ProjectCollections.Add(solution.Projects); } IProject RemoveProjectFromSolution(ISolution solution) diff --git a/src/Main/Base/Project/Dom/ConcatModelCollection.cs b/src/Main/Base/Project/Dom/ConcatModelCollection.cs deleted file mode 100644 index 5b69969510..0000000000 --- a/src/Main/Base/Project/Dom/ConcatModelCollection.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) -// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; - -namespace ICSharpCode.SharpDevelop.Dom -{ - /// - /// A that works by concatening multiple - /// other model collections. - /// - public sealed class ConcatModelCollection : IModelCollection - { - sealed class InputCollection : Collection> - { - readonly ConcatModelCollection owner; - - public InputCollection(ConcatModelCollection owner) - { - this.owner = owner; - } - - protected override void ClearItems() - { - if (owner.collectionChanged != null) { - foreach (var input in Items) { - input.CollectionChanged -= owner.OnInputCollectionChanged; - } - } - base.ClearItems(); - owner.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - protected override void InsertItem(int index, IModelCollection item) - { - if (owner.collectionChanged != null) - item.CollectionChanged += owner.OnInputCollectionChanged; - base.InsertItem(index, item); - owner.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, item.ToArray(), owner.GetCount(index))); - } - - protected override void RemoveItem(int index) - { - if (owner.collectionChanged != null) - Items[index].CollectionChanged -= owner.OnInputCollectionChanged; - var oldItems = Items[index].ToArray(); - base.RemoveItem(index); - owner.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Remove, oldItems, owner.GetCount(index))); - } - - protected override void SetItem(int index, IModelCollection item) - { - RemoveItem(index); - InsertItem(index, item); - } - } - - InputCollection inputs; - - public ConcatModelCollection() - { - this.inputs = new InputCollection(this); - } - - public IList> Inputs { - get { return inputs; } - } - - NotifyCollectionChangedEventHandler collectionChanged; - - public event NotifyCollectionChangedEventHandler CollectionChanged { - add { - var oldEventHandlers = collectionChanged; - collectionChanged += value; - if (oldEventHandlers == null && collectionChanged != null) { - foreach (var input in inputs) - input.CollectionChanged += OnInputCollectionChanged; - } - } - remove { - var oldEventHandlers = collectionChanged; - collectionChanged -= value; - if (oldEventHandlers != null && collectionChanged == null) { - foreach (var input in inputs) - input.CollectionChanged -= OnInputCollectionChanged; - } - } - } - - void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e) - { - if (collectionChanged != null) - collectionChanged(this, e); - } - - void OnInputCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - int inputIndex = inputs.IndexOf((IModelCollection)sender); - int startIndex = GetCount(inputIndex); - NotifyCollectionChangedEventArgs newEventArgs; - switch (e.Action) { - case NotifyCollectionChangedAction.Add: - newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.NewItems, startIndex + e.NewStartingIndex); - break; - case NotifyCollectionChangedAction.Remove: - newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.OldItems, startIndex + e.OldStartingIndex); - break; - case NotifyCollectionChangedAction.Replace: - newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.OldItems, e.NewItems, startIndex + e.OldStartingIndex); - break; - case NotifyCollectionChangedAction.Move: - newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.OldItems, startIndex + e.OldStartingIndex, startIndex + e.NewStartingIndex); - break; - case NotifyCollectionChangedAction.Reset: - newEventArgs = new NotifyCollectionChangedEventArgs(e.Action); - break; - default: - throw new NotSupportedException("Invalid value for NotifyCollectionChangedAction"); - } - collectionChanged(this, newEventArgs); - } - - public int Count { - get { return GetCount(inputs.Count); } - } - - int GetCount(int inputIndex) - { - int count = 0; - for (int i = 0; i < inputIndex; i++) { - count += inputs[i].Count; - } - return count; - } - - public IEnumerator GetEnumerator() - { - return inputs.SelectMany(i => i).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/src/Main/Base/Project/Dom/FilterModelCollection.cs b/src/Main/Base/Project/Dom/FilterModelCollection.cs deleted file mode 100644 index 48d44378cf..0000000000 --- a/src/Main/Base/Project/Dom/FilterModelCollection.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) -// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; - -namespace ICSharpCode.SharpDevelop.Dom -{ - /// - /// A model collection that filters an input collection. - /// - public sealed class FilterModelCollection : IModelCollection - { - readonly IModelCollection input; - readonly Func predicate; - bool isAttached; - NotifyCollectionChangedEventHandler collectionChanged; - - public FilterModelCollection(IModelCollection input, Func predicate) - { - if (input == null) - throw new ArgumentNullException("input"); - if (predicate == null) - throw new ArgumentNullException("predicate"); - this.input = input; - this.predicate = predicate; - } - - public event NotifyCollectionChangedEventHandler CollectionChanged { - add { - collectionChanged += value; - if (collectionChanged != null && !isAttached) { - input.CollectionChanged += input_CollectionChanged; - isAttached = true; - } - } - remove { - collectionChanged -= value; - if (collectionChanged == null && isAttached) { - input.CollectionChanged -= input_CollectionChanged; - isAttached = false; - } - } - } - - void input_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - switch (e.Action) { - case NotifyCollectionChangedAction.Add: - collectionChanged(this, new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, ApplyFilter(e.NewItems))); - break; - case NotifyCollectionChangedAction.Remove: - collectionChanged(this, new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Remove, ApplyFilter(e.OldItems))); - break; - case NotifyCollectionChangedAction.Replace: - collectionChanged(this, new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Replace, ApplyFilter(e.OldItems), ApplyFilter(e.NewItems))); - break; - case NotifyCollectionChangedAction.Move: - // this collection is unordered - break; - case NotifyCollectionChangedAction.Reset: - collectionChanged(this, new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Reset)); - break; - default: - throw new NotSupportedException(); - } - } - - IList ApplyFilter(IList inputItems) - { - if (inputItems == null) - return null; - List outputItems = new List(); - foreach (T item in inputItems) { - if (predicate(item)) - outputItems.Add(item); - } - return outputItems; - } - - public int Count { - get { - return input.Count(predicate); - } - } - - public IEnumerator GetEnumerator() - { - return input.Where(predicate).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/src/Main/Base/Project/Dom/IModelCollection.cs b/src/Main/Base/Project/Dom/IModelCollection.cs index 618f0a1f96..836ee4cd37 100644 --- a/src/Main/Base/Project/Dom/IModelCollection.cs +++ b/src/Main/Base/Project/Dom/IModelCollection.cs @@ -10,44 +10,47 @@ using System.Linq; namespace ICSharpCode.SharpDevelop.Dom { /// - /// A read-only collection that provides change notifications. + /// Event handler for the event. /// - public interface IModelCollection : IReadOnlyCollection, INotifyCollectionChanged - { - } + /// + /// We don't use the classic 'EventArgs' model for this event, because a EventArgs-class couldn't be covariant. + /// + public delegate void ModelCollectionChangedEventHandler(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems); /// - /// A collection that provides change notifications. + /// A read-only collection that provides change notifications. /// - public interface IMutableModelCollection : IModelCollection, ICollection + /// + /// We don't use INotifyCollectionChanged because that has the annoying 'Reset' update, + /// where it's impossible for to detect what kind of changes happened unless the event consumer + /// maintains a copy of the list. + /// Also, INotifyCollectionChanged isn't type-safe. + /// + public interface IModelCollection : IReadOnlyCollection { + event ModelCollectionChangedEventHandler CollectionChanged; } /// - /// A model collection implementation that is based on a ObservableCollection. + /// A collection that provides change notifications. /// - public class SimpleModelCollection : ObservableCollection, IMutableModelCollection + public interface IMutableModelCollection : IModelCollection, ICollection { - public SimpleModelCollection() - { - } + /// + /// Adds the specified items to the collection. + /// + void AddRange(IEnumerable items); - public SimpleModelCollection(IEnumerable items) - : base(items) - { - } - } - - /// - /// A model collection implementation that is based on a ReadOnlyCollection. - /// - public class ReadOnlyModelCollection : ReadOnlyCollection, IModelCollection - { - public ReadOnlyModelCollection(IEnumerable items) - : base(items.ToList()) - { - } + /// + /// Removes all items matching the specified precidate. + /// + int RemoveAll(Predicate predicate); - event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged { add {} remove {} } + /// + /// Can be used to group several operations into a batch update. + /// The event will not fire during the batch update; + /// instead the event will be raised a single time after the batch update finishes. + /// + IDisposable BatchUpdate(); } } diff --git a/src/Main/Base/Project/Dom/ITypeDefinitionModelCollection.cs b/src/Main/Base/Project/Dom/ITypeDefinitionModelCollection.cs index ddffe89e64..e50e0c59dd 100644 --- a/src/Main/Base/Project/Dom/ITypeDefinitionModelCollection.cs +++ b/src/Main/Base/Project/Dom/ITypeDefinitionModelCollection.cs @@ -38,7 +38,7 @@ namespace ICSharpCode.SharpDevelop.Dom { public static readonly EmptyTypeDefinitionModelCollection Instance = new EmptyTypeDefinitionModelCollection(); - event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged { + event ModelCollectionChangedEventHandler IModelCollection.CollectionChanged { add { } remove { } } diff --git a/src/Main/Base/Project/Dom/KeyedModelCollection.cs b/src/Main/Base/Project/Dom/KeyedModelCollection.cs deleted file mode 100644 index fc5054f37e..0000000000 --- a/src/Main/Base/Project/Dom/KeyedModelCollection.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) -// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) - -using System; -using System.Collections.ObjectModel; -using System.Collections.Specialized; - -namespace ICSharpCode.SharpDevelop.Dom -{ - /// - /// Observable KeyedCollection. - /// - public abstract class KeyedModelCollection : KeyedCollection, IModelCollection - { - // TODO: do we still need this class? maybe we should remove it? - - public bool TryGetValue(TKey key, out TItem item) - { - return Dictionary.TryGetValue(key, out item); - } - - protected override void ClearItems() - { - base.ClearItems(); - if (CollectionChanged != null) - CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - protected override void InsertItem(int index, TItem item) - { - base.InsertItem(index, item); - if (CollectionChanged != null) - CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); - } - - protected override void RemoveItem(int index) - { - var oldItem = Items[index]; - base.RemoveItem(index); - if (CollectionChanged != null) - CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItem, index)); - } - - protected override void SetItem(int index, TItem item) - { - var oldItem = Items[index]; - base.SetItem(index, item); - if (CollectionChanged != null) - CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItem, index)); - } - - public event NotifyCollectionChangedEventHandler CollectionChanged; - } -} diff --git a/src/Main/Base/Project/Dom/ModelCollectionLinq.cs b/src/Main/Base/Project/Dom/ModelCollectionLinq.cs new file mode 100644 index 0000000000..f2c1dc0949 --- /dev/null +++ b/src/Main/Base/Project/Dom/ModelCollectionLinq.cs @@ -0,0 +1,191 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; + +namespace ICSharpCode.SharpDevelop.Dom +{ + /// + /// Provides LINQ operators for . + /// + public static class ModelCollectionLinq + { + // A general 'AsObservableCollection()' would be nice; but I don't see any good way + // to implement that without leaking memory. + // The problem is that IModelCollection is unordered; but ObservableCollection requires us to maintain a stable order. + + #region Where + /*public static IModelCollection Where(this IModelCollection source, Func predicate) + { + + }*/ + #endregion + + #region Select + public static IModelCollection Select(this IModelCollection source, Func selector) + { + // HACK: emulating Select with SelectMany is much less efficient than a direct implementation could be + return SelectMany(source, item => new ReadOnlyModelCollection(new[] { item }), (a, b) => selector(b)); + } + #endregion + + #region SelectMany + public static IModelCollection SelectMany(this IModelCollection input, Func> selector) + { + return SelectMany(input, selector, (a, b) => b); + } + + public static IModelCollection SelectMany(this IModelCollection source, Func> collectionSelector, Func resultSelector) + { + if (source == null) + throw new ArgumentNullException("source"); + if (collectionSelector == null) + throw new ArgumentNullException("collectionSelector"); + if (resultSelector == null) + throw new ArgumentNullException("resultSelector"); + return new SelectManyModelCollection(source, collectionSelector, resultSelector); + } + + sealed class SelectManyModelCollection : IModelCollection + { + readonly IModelCollection source; + readonly Func> collectionSelector; + readonly Func resultSelector; + List inputCollections; + + public SelectManyModelCollection(IModelCollection source, Func> collectionSelector, Func resultSelector) + { + this.source = source; + this.collectionSelector = collectionSelector; + this.resultSelector = resultSelector; + } + + ModelCollectionChangedEventHandler collectionChanged; + + public event ModelCollectionChangedEventHandler CollectionChanged { + add { + if (value == null) + return; + if (inputCollections == null) { + source.CollectionChanged += OnSourceCollectionChanged; + inputCollections = new List(); + foreach (var item in source) { + var inputCollection = new InputCollection(this, item); + inputCollection.RegisterEvent(); + inputCollections.Add(inputCollection); + } + } + collectionChanged += value; + } + remove { + if (collectionChanged == null) + return; + collectionChanged -= value; + if (collectionChanged == null) { + source.CollectionChanged -= OnSourceCollectionChanged; + foreach (var inputCollection in inputCollections) { + inputCollection.UnregisterEvent(); + } + inputCollections = null; + } + } + } + + void OnCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) + { + if (collectionChanged != null) + collectionChanged(removedItems, addedItems); + } + + void OnSourceCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) + { + List removedResults = new List(); + foreach (TSource removedSource in removedItems) { + for (int i = 0; i < inputCollections.Count; i++) { + var inputCollection = inputCollections[i]; + if (EqualityComparer.Default.Equals(inputCollection.source, removedSource)) { + inputCollection.AddResultsToList(removedResults); + inputCollection.UnregisterEvent(); + inputCollections.RemoveAt(i); + break; + } + } + } + List addedResults = new List(); + foreach (TSource addedSource in addedItems) { + var inputCollection = new InputCollection(this, addedSource); + inputCollection.AddResultsToList(addedResults); + inputCollection.RegisterEvent(); + inputCollections.Add(inputCollection); + } + OnCollectionChanged(removedResults, addedResults); + } + + class InputCollection + { + readonly SelectManyModelCollection parent; + internal readonly TSource source; + readonly IModelCollection collection; + + public InputCollection(SelectManyModelCollection parent, TSource source) + { + this.parent = parent; + this.source = source; + this.collection = parent.collectionSelector(source); + } + + public void AddResultsToList(List output) + { + foreach (var item in collection) { + output.Add(parent.resultSelector(source, item)); + } + } + + public void RegisterEvent() + { + collection.CollectionChanged += OnCollectionChanged; + } + + public void UnregisterEvent() + { + collection.CollectionChanged -= OnCollectionChanged; + } + + IReadOnlyCollection GetResults(IReadOnlyCollection itemsCollection) + { + List results = new List(itemsCollection.Count); + foreach (var item in itemsCollection) { + results.Add(parent.resultSelector(source, item)); + } + return results; + } + + void OnCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) + { + parent.OnCollectionChanged(GetResults(removedItems), GetResults(addedItems)); + } + } + + public IEnumerator GetEnumerator() + { + return source.AsEnumerable().SelectMany(collectionSelector, resultSelector).GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public int Count { + get { + return source.Sum(c => collectionSelector(c).Count); + } + } + } + #endregion + } +} diff --git a/src/Main/Base/Project/Dom/ReadOnlyModelCollection.cs b/src/Main/Base/Project/Dom/ReadOnlyModelCollection.cs new file mode 100644 index 0000000000..0e38e2d998 --- /dev/null +++ b/src/Main/Base/Project/Dom/ReadOnlyModelCollection.cs @@ -0,0 +1,22 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)using System; + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace ICSharpCode.SharpDevelop.Dom +{ + /// + /// A model collection implementation that is based on a ReadOnlyCollection. + /// + public class ReadOnlyModelCollection : ReadOnlyCollection, IModelCollection + { + public ReadOnlyModelCollection(IEnumerable items) + : base(items.ToList()) + { + } + + event ModelCollectionChangedEventHandler IModelCollection.CollectionChanged { add {} remove {} } + } +} diff --git a/src/Main/Base/Project/Dom/SimpleModelCollection.cs b/src/Main/Base/Project/Dom/SimpleModelCollection.cs new file mode 100644 index 0000000000..154dec956c --- /dev/null +++ b/src/Main/Base/Project/Dom/SimpleModelCollection.cs @@ -0,0 +1,206 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)using System; + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using ICSharpCode.NRefactory; +using ICSharpCode.NRefactory.Utils; + +namespace ICSharpCode.SharpDevelop.Dom +{ + /// + /// A model collection implementation. + /// + public class SimpleModelCollection : IMutableModelCollection + { + readonly List list; + List addedItems; + List removedItems; + + public SimpleModelCollection() + { + this.list = new List(); + } + + public SimpleModelCollection(IEnumerable items) + { + this.list = new List(items); + } + + protected void CheckReentrancy() + { + if (isRaisingEvent) + throw new InvalidOperationException("Cannot modify the collection from within the CollectionChanged event."); + } + + protected virtual void ValidateItem(T item) + { + } + + #region CollectionChanged / BatchUpdate() + public event ModelCollectionChangedEventHandler CollectionChanged; + + bool isWithinBatchOperation; + bool isRaisingEvent; + + void RaiseEventIfNotInBatch() + { + if (isWithinBatchOperation) + return; + IReadOnlyCollection removed = this.removedItems; + IReadOnlyCollection added = this.addedItems; + this.removedItems = null; + this.addedItems = null; + this.isRaisingEvent = true; + try { + OnCollectionChanged(removed ?? EmptyList.Instance, added ?? EmptyList.Instance); + } finally { + this.isRaisingEvent = false; + } + } + + protected virtual void OnCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) + { + var handler = CollectionChanged; + if (handler != null) + handler(removedItems, addedItems); + } + + public IDisposable BatchUpdate() + { + if (isWithinBatchOperation) + return null; + isWithinBatchOperation = true; + return new CallbackOnDispose( + delegate { + isWithinBatchOperation = false; + if (removedItems != null || addedItems != null) + RaiseEventIfNotInBatch(); + }); + } + #endregion + + #region Read-Only list access + + public int Count { + get { return list.Count; } + } + + public bool Contains(T item) + { + return list.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + list.CopyTo(array, arrayIndex); + } + + bool ICollection.IsReadOnly { + get { return false; } + } + + public IEnumerator GetEnumerator() + { + return list.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return list.GetEnumerator(); + } + + #endregion + + #region IMutableModelCollection implementation + + public void Clear() + { + CheckReentrancy(); + addedItems = null; + if (removedItems == null) + removedItems = new List(); + removedItems.AddRange(list); + list.Clear(); + RaiseEventIfNotInBatch(); + } + + public void Add(T item) + { + CheckReentrancy(); + ValidateItem(item); + if (removedItems != null) + removedItems.Remove(item); + if (addedItems == null) + addedItems = new List(); + addedItems.Add(item); + list.Add(item); + RaiseEventIfNotInBatch(); + } + + public void AddRange(IEnumerable items) + { + if (items == null) + throw new ArgumentNullException("items"); + CheckReentrancy(); + List itemsList = items.ToList(); + for (int i = 0; i < itemsList.Count; i++) { + ValidateItem(itemsList[i]); + } + if (removedItems != null) { + for (int i = 0; i < itemsList.Count; i++) { + removedItems.Remove(itemsList[i]); + } + } + if (addedItems != null) + addedItems.AddRange(itemsList); + else + addedItems = itemsList; + list.AddRange(itemsList); + RaiseEventIfNotInBatch(); + } + + public bool Remove(T item) + { + CheckReentrancy(); + if (list.Remove(item)) { + if (addedItems != null) + addedItems.Remove(item); + if (removedItems == null) + removedItems = new List(); + removedItems.Add(item); + RaiseEventIfNotInBatch(); + return true; + } else { + return false; + } + } + + public int RemoveAll(Predicate predicate) + { + CheckReentrancy(); + int count = list.RemoveAll( + delegate(T obj) { + if (predicate(obj)) { + if (addedItems != null) + addedItems.Remove(obj); + if (removedItems == null) + removedItems = new List(); + removedItems.Add(obj); + return true; + } else { + return false; + } + }); + if (count > 0) + RaiseEventIfNotInBatch(); + return count; + } + + #endregion + } +} + diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 84b4676dbf..78e6ad2054 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -84,17 +84,17 @@ - - - + + + diff --git a/src/Main/Base/Project/Project/Configuration/IConfigurationOrPlatformNameCollection.cs b/src/Main/Base/Project/Project/Configuration/IConfigurationOrPlatformNameCollection.cs index 478c4d39a8..4deceab87e 100644 --- a/src/Main/Base/Project/Project/Configuration/IConfigurationOrPlatformNameCollection.cs +++ b/src/Main/Base/Project/Project/Configuration/IConfigurationOrPlatformNameCollection.cs @@ -12,7 +12,7 @@ namespace ICSharpCode.SharpDevelop.Project /// /// Represents a collection of configuration or platform names. /// - public interface IConfigurationOrPlatformNameCollection : IReadOnlyCollection, INotifyCollectionChanged + public interface IConfigurationOrPlatformNameCollection : IModelCollection { /// /// Validates the input name. diff --git a/src/Main/Base/Project/Project/ISolution.cs b/src/Main/Base/Project/Project/ISolution.cs index f606128edd..1c772ca6ad 100644 --- a/src/Main/Base/Project/Project/ISolution.cs +++ b/src/Main/Base/Project/Project/ISolution.cs @@ -59,7 +59,7 @@ namespace ICSharpCode.SharpDevelop.Project /// Gets the list of global sections. /// These can be used to store additional data within the solution file. /// - IList GlobalSections { get; } + IMutableModelCollection GlobalSections { get; } /// /// Finds the item with the specified ; diff --git a/src/Main/Base/Test/Dom/CSharpModelTests.cs b/src/Main/Base/Test/Dom/CSharpModelTests.cs index 8f1abad29a..edf67c7a0a 100644 --- a/src/Main/Base/Test/Dom/CSharpModelTests.cs +++ b/src/Main/Base/Test/Dom/CSharpModelTests.cs @@ -22,7 +22,19 @@ namespace ICSharpCode.SharpDevelop.Dom IProjectContent projectContent; IEntityModelContext context; TopLevelTypeDefinitionModelCollection topLevelTypeModels; - List topLevelChangeEventArgs = new List(); + List> topLevelChangeEventArgs = new List>(); + + class RemovedAndAddedPair + { + public IReadOnlyCollection OldItems { get; private set; } + public IReadOnlyCollection NewItems { get; private set; } + + public RemovedAndAddedPair(IReadOnlyCollection oldItems, IReadOnlyCollection newItems) + { + this.OldItems = oldItems; + this.NewItems = newItems; + } + } #region SetUp and other helper methods [SetUp] @@ -37,7 +49,7 @@ namespace ICSharpCode.SharpDevelop.Dom SD.ParserService.Stub(p => p.GetCompilation(project)).WhenCalled(c => c.ReturnValue = projectContent.CreateCompilation()); topLevelChangeEventArgs.Clear(); - topLevelTypeModels.CollectionChanged += (sender, e) => topLevelChangeEventArgs.Add(e); + topLevelTypeModels.CollectionChanged += (oldItems, newItems) => topLevelChangeEventArgs.Add(new RemovedAndAddedPair(oldItems, newItems)); } [TearDown] @@ -94,7 +106,7 @@ namespace ICSharpCode.SharpDevelop.Dom AddCodeFile("test.cs", @"class SimpleClass {}"); Assert.AreEqual(1, topLevelTypeModels.Count); var simpleClass = topLevelTypeModels.Single(); - Assert.AreEqual(NotifyCollectionChangedAction.Add, topLevelChangeEventArgs.Single().Action); + Assert.IsEmpty(topLevelChangeEventArgs.Single().OldItems); Assert.AreEqual(new[] { simpleClass }, topLevelChangeEventArgs.Single().NewItems); topLevelChangeEventArgs.Clear(); // clear for follow-up tests } @@ -119,7 +131,6 @@ namespace ICSharpCode.SharpDevelop.Dom UpdateCodeFile("test.cs", "class OtherClass { }"); var otherClass = topLevelTypeModels.Single(); Assert.AreNotSame(simpleClass, otherClass); - Assert.AreEqual(NotifyCollectionChangedAction.Replace, topLevelChangeEventArgs.Single().Action); Assert.AreEqual(new[] { simpleClass }, topLevelChangeEventArgs.Single().OldItems); Assert.AreEqual(new[] { otherClass }, topLevelChangeEventArgs.Single().NewItems); } @@ -132,8 +143,8 @@ namespace ICSharpCode.SharpDevelop.Dom RemoveCodeFile("test.cs"); Assert.IsEmpty(topLevelTypeModels); // check removal event - Assert.AreEqual(NotifyCollectionChangedAction.Remove, topLevelChangeEventArgs.Single().Action); Assert.AreEqual(new[] { simpleClass }, topLevelChangeEventArgs.Single().OldItems); + Assert.IsEmpty(topLevelChangeEventArgs.Single().NewItems); // test that accessing properties of a removed class still works Assert.AreEqual(new FullTypeName("SimpleClass"), simpleClass.FullTypeName); diff --git a/src/Main/Base/Test/Utils/ModelCollectionEventCheck.cs b/src/Main/Base/Test/Utils/ModelCollectionEventCheck.cs index ac90030bfb..554de99369 100644 --- a/src/Main/Base/Test/Utils/ModelCollectionEventCheck.cs +++ b/src/Main/Base/Test/Utils/ModelCollectionEventCheck.cs @@ -28,34 +28,13 @@ namespace ICSharpCode.SharpDevelop.Utils modelCollection.CollectionChanged += OnCollectionChanged; } - void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + void OnCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) { - switch (e.Action) { - case NotifyCollectionChangedAction.Add: - list.InsertRange(e.NewStartingIndex, e.NewItems.Cast()); - break; - case NotifyCollectionChangedAction.Remove: - Assert.AreEqual(list.GetRange(e.OldStartingIndex, e.OldItems.Count), e.OldItems); - list.RemoveRange(e.OldStartingIndex, e.OldItems.Count); - break; - case NotifyCollectionChangedAction.Replace: - Assert.AreEqual(e.OldStartingIndex, e.NewStartingIndex, "Old and new starting index must be identical for replace action"); - Assert.AreEqual(list.GetRange(e.OldStartingIndex, e.OldItems.Count), e.OldItems); - list.RemoveRange(e.OldStartingIndex, e.OldItems.Count); - list.InsertRange(e.NewStartingIndex, e.NewItems.Cast()); - break; - case NotifyCollectionChangedAction.Move: - Assert.AreEqual(e.OldItems, e.NewItems); - Assert.AreEqual(list.GetRange(e.OldStartingIndex, e.OldItems.Count), e.OldItems); - list.RemoveRange(e.OldStartingIndex, e.OldItems.Count); - list.InsertRange(e.NewStartingIndex, e.NewItems.Cast()); - break; - case NotifyCollectionChangedAction.Reset: - list.Clear(); - list.AddRange(modelCollection); - break; - default: - throw new Exception("Invalid value for NotifyCollectionChangedAction"); + foreach (T removed in removedItems) { + list.Remove(removed); + } + foreach (T added in addedItems) { + list.Add(added); } Verify(); } diff --git a/src/Main/SharpDevelop/Dom/NestedTypeDefinitionModelCollection.cs b/src/Main/SharpDevelop/Dom/NestedTypeDefinitionModelCollection.cs index d261afb629..a4f0e39394 100644 --- a/src/Main/SharpDevelop/Dom/NestedTypeDefinitionModelCollection.cs +++ b/src/Main/SharpDevelop/Dom/NestedTypeDefinitionModelCollection.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.TypeSystem; namespace ICSharpCode.SharpDevelop.Dom @@ -18,7 +19,7 @@ namespace ICSharpCode.SharpDevelop.Dom this.context = context; } - public event NotifyCollectionChangedEventHandler CollectionChanged; + public event ModelCollectionChangedEventHandler CollectionChanged; public IEnumerator GetEnumerator() { @@ -101,13 +102,9 @@ namespace ICSharpCode.SharpDevelop.Dom } } // Raise the event if necessary: - if (CollectionChanged != null) { - if (oldModels != null && newModels != null) - CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newModels, oldModels)); - else if (oldModels != null) - CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldModels)); - else if (newModels != null) - CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newModels)); + if (CollectionChanged != null && (oldModels != null || newModels != null)) { + IReadOnlyCollection emptyList = EmptyList.Instance; + CollectionChanged(oldModels ?? emptyList, newModels ?? emptyList); } } } diff --git a/src/Main/SharpDevelop/Dom/TopLevelTypeDefinitionModelCollection.cs b/src/Main/SharpDevelop/Dom/TopLevelTypeDefinitionModelCollection.cs index 22f9a63829..4764083818 100644 --- a/src/Main/SharpDevelop/Dom/TopLevelTypeDefinitionModelCollection.cs +++ b/src/Main/SharpDevelop/Dom/TopLevelTypeDefinitionModelCollection.cs @@ -5,6 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; +using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.TypeSystem; namespace ICSharpCode.SharpDevelop.Dom @@ -16,7 +17,7 @@ namespace ICSharpCode.SharpDevelop.Dom { readonly IEntityModelContext context; Dictionary dict = new Dictionary(); - public event NotifyCollectionChangedEventHandler CollectionChanged; + public event ModelCollectionChangedEventHandler CollectionChanged; public TopLevelTypeDefinitionModelCollection(IEntityModelContext context) { @@ -114,13 +115,9 @@ namespace ICSharpCode.SharpDevelop.Dom } } // Raise the event if necessary: - if (CollectionChanged != null) { - if (oldModels != null && newModels != null) - CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newModels, oldModels)); - else if (oldModels != null) - CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldModels)); - else if (newModels != null) - CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newModels)); + if (CollectionChanged != null && (oldModels != null || newModels != null)) { + IReadOnlyCollection emptyList = EmptyList.Instance; + CollectionChanged(oldModels ?? emptyList, newModels ?? emptyList); } } diff --git a/src/Main/SharpDevelop/Dom/TypeDefinitionModel.cs b/src/Main/SharpDevelop/Dom/TypeDefinitionModel.cs index b3ea138583..4eb408fe90 100644 --- a/src/Main/SharpDevelop/Dom/TypeDefinitionModel.cs +++ b/src/Main/SharpDevelop/Dom/TypeDefinitionModel.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; +using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop.Parser; using ICSharpCode.SharpDevelop.Project; @@ -98,7 +99,7 @@ namespace ICSharpCode.SharpDevelop.Dom } lists.Insert(partIndex, newItems); if (collectionChanged != null) - collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, GetCount(partIndex))); + collectionChanged(EmptyList.Instance, newItems); } public void RemovePart(int partIndex) @@ -106,7 +107,7 @@ namespace ICSharpCode.SharpDevelop.Dom var oldItems = lists[partIndex]; lists.RemoveAt(partIndex); if (collectionChanged != null) - collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, GetCount(partIndex))); + collectionChanged(oldItems, EmptyList.Instance); } public void UpdatePart(int partIndex, IUnresolvedTypeDefinition newPart) @@ -143,8 +144,9 @@ namespace ICSharpCode.SharpDevelop.Dom newItems[i].strongParentCollectionReference = this; } list.InsertRange(startPos, newItems); - if (collectionChanged != null) - collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, GetCount(partIndex) + startPos)); + if (collectionChanged != null && (oldItems.Count > 0 || newItems.Length > 0)) { + collectionChanged(oldItems, newItems); + } } static bool IsMatch(MemberModel memberModel, IUnresolvedMember newMember) @@ -152,9 +154,9 @@ namespace ICSharpCode.SharpDevelop.Dom return memberModel.EntityType == newMember.EntityType && memberModel.Name == newMember.Name; } - NotifyCollectionChangedEventHandler collectionChanged; + ModelCollectionChangedEventHandler collectionChanged; - public event NotifyCollectionChangedEventHandler CollectionChanged { + public event ModelCollectionChangedEventHandler CollectionChanged { add { collectionChanged += value; // Set strong reference to collection while there are event listeners diff --git a/src/Main/SharpDevelop/Project/Configuration/SolutionConfigurationOrPlatformNameCollection.cs b/src/Main/SharpDevelop/Project/Configuration/SolutionConfigurationOrPlatformNameCollection.cs index 1365b01630..271c045908 100644 --- a/src/Main/SharpDevelop/Project/Configuration/SolutionConfigurationOrPlatformNameCollection.cs +++ b/src/Main/SharpDevelop/Project/Configuration/SolutionConfigurationOrPlatformNameCollection.cs @@ -2,15 +2,21 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using ICSharpCode.Core; +using ICSharpCode.NRefactory; +using ICSharpCode.SharpDevelop.Dom; using Microsoft.Build.Logging; namespace ICSharpCode.SharpDevelop.Project { - class SolutionConfigurationOrPlatformNameCollection : ObservableCollection, IConfigurationOrPlatformNameCollection + class SolutionConfigurationOrPlatformNameCollection : IConfigurationOrPlatformNameCollection { + public event ModelCollectionChangedEventHandler CollectionChanged; + + readonly List list = new List(); readonly Solution solution; readonly bool isPlatform; @@ -20,6 +26,24 @@ namespace ICSharpCode.SharpDevelop.Project this.isPlatform = isPlatform; } + #region IReadOnlyCollection implementation + + public int Count { + get { return list.Count; } + } + + public IEnumerator GetEnumerator() + { + return list.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return list.GetEnumerator(); + } + + #endregion + public string ValidateName(string name) { if (name == null) @@ -38,13 +62,16 @@ namespace ICSharpCode.SharpDevelop.Project newName = ValidateName(newName); if (newName == null) throw new ArgumentException(); - Add(newName); + list.Add(newName); + if (copyFrom != null) + throw new NotImplementedException(); + OnCollectionChanged(EmptyList.Instance, new[] { newName }); } int GetIndex(string name) { - for (int i = 0; i < this.Count; i++) { - if (ConfigurationAndPlatform.ConfigurationNameComparer.Equals(this[i], name)) + for (int i = 0; i < list.Count; i++) { + if (ConfigurationAndPlatform.ConfigurationNameComparer.Equals(list[i], name)) return i; } return -1; @@ -53,8 +80,11 @@ namespace ICSharpCode.SharpDevelop.Project void IConfigurationOrPlatformNameCollection.Remove(string name) { int pos = GetIndex(name); - if (pos >= 0) - RemoveAt(pos); + if (pos >= 0) { + name = list[pos]; // get the name in original case + list.RemoveAt(pos); + OnCollectionChanged(new[] { name }, EmptyList.Instance); + } } void IConfigurationOrPlatformNameCollection.Rename(string oldName, string newName) @@ -65,16 +95,19 @@ namespace ICSharpCode.SharpDevelop.Project int pos = GetIndex(oldName); if (pos < 0) throw new ArgumentException(); + oldName = list[pos]; // get oldName in original case foreach (var project in solution.Projects) { throw new NotImplementedException(); //project.ConfigurationMapping.RenameSolutionConfig(oldName, newName, isPlatform); } - this[pos] = newName; + list[pos] = newName; + OnCollectionChanged(new[] { oldName }, new[] { newName }); } - protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + void OnCollectionChanged(IReadOnlyCollection oldItems, IReadOnlyCollection newItems) { - base.OnCollectionChanged(e); + if (CollectionChanged != null) + CollectionChanged(oldItems, newItems); solution.IsDirty = true; } } diff --git a/src/Main/SharpDevelop/Project/ProjectService.cs b/src/Main/SharpDevelop/Project/ProjectService.cs index abe23c5221..6a3407795b 100644 --- a/src/Main/SharpDevelop/Project/ProjectService.cs +++ b/src/Main/SharpDevelop/Project/ProjectService.cs @@ -24,11 +24,15 @@ namespace ICSharpCode.SharpDevelop.Project applicationStateInfoService.RegisterStateGetter("ProjectService.CurrentSolution", delegate { return CurrentSolution; }); applicationStateInfoService.RegisterStateGetter("ProjectService.CurrentProject", delegate { return CurrentProject; }); } + + allSolutions = new SimpleModelCollection(); + allProjects = allSolutions.SelectMany(s => s.Projects); } #region CurrentSolution property + AllProjects collection volatile static ISolution currentSolution; - ConcatModelCollection allProjects = new ConcatModelCollection(); + readonly SimpleModelCollection allSolutions; + readonly IModelCollection allProjects; public event PropertyChangedEventHandler CurrentSolutionChanged = delegate { }; @@ -41,10 +45,10 @@ namespace ICSharpCode.SharpDevelop.Project if (oldValue != value) { currentSolution = value; if (oldValue != null) - allProjects.Inputs.Remove(oldValue.Projects); + allSolutions.Remove(oldValue); CurrentSolutionChanged(this, new PropertyChangedEventArgs(oldValue, value)); if (value != null) - allProjects.Inputs.Add(value.Projects); + allSolutions.Add(value); CommandManager.InvalidateRequerySuggested(); SD.ParserService.InvalidateCurrentSolutionSnapshot(); } diff --git a/src/Main/SharpDevelop/Project/Solution.cs b/src/Main/SharpDevelop/Project/Solution.cs index 6c85a0450b..3af02b44e5 100644 --- a/src/Main/SharpDevelop/Project/Solution.cs +++ b/src/Main/SharpDevelop/Project/Solution.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Windows.Threading; using ICSharpCode.Core; +using ICSharpCode.NRefactory; using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Workbench; @@ -109,14 +110,16 @@ namespace ICSharpCode.SharpDevelop.Project void OnProjectAdded(IProject project) { projects.Add(project); - project.ProjectSections.CollectionChanged += OnProjectSectionsChanged; + project.ProjectSections.CollectionChanged += OnSolutionSectionCollectionChanged; + OnSolutionSectionCollectionChanged(EmptyList.Instance, project.ProjectSections); if (startupProject == null && AutoDetectStartupProject() == project) this.StartupProject = project; // when there's no startable project in the solution and one is added, we mark that it as the startup project } void OnProjectRemoved(IProject project) { - project.ProjectSections.CollectionChanged -= OnProjectSectionsChanged; + project.ProjectSections.CollectionChanged -= OnSolutionSectionCollectionChanged; + OnSolutionSectionCollectionChanged(project.ProjectSections, EmptyList.Instance); bool wasStartupProject = (startupProject == project); if (wasStartupProject) startupProject = null; // this will force auto-detection on the next property access @@ -131,12 +134,6 @@ namespace ICSharpCode.SharpDevelop.Project }, DispatcherPriority.Background); } - void OnProjectSectionsChanged(object sender, EventArgs e) - { - this.IsDirty = true; - // TODO: also monitor changes within the project section - } - internal void ReportRemovedItem(ISolutionItem oldItem) { if (oldItem is ISolutionFolder) { @@ -168,12 +165,28 @@ namespace ICSharpCode.SharpDevelop.Project } } - List globalSections = new List(); + SimpleModelCollection globalSections = new SimpleModelCollection(); - public IList GlobalSections { + public IMutableModelCollection GlobalSections { get { return globalSections; } } + void OnSolutionSectionCollectionChanged(IReadOnlyCollection oldItems, IReadOnlyCollection newItems) + { + this.IsDirty = true; + foreach (var section in oldItems) { + section.Changed -= OnSolutionSectionChanged; + } + foreach (var section in newItems) { + section.Changed += OnSolutionSectionChanged; + } + } + + void OnSolutionSectionChanged(object sender, EventArgs e) + { + this.IsDirty = true; + } + public ISolutionItem GetItemByGuid(Guid guid) { // Maybe we should maintain a dictionary to make these lookups faster? diff --git a/src/Main/SharpDevelop/Project/SolutionFolder.cs b/src/Main/SharpDevelop/Project/SolutionFolder.cs index 1e24e9a369..ea3a0dee19 100644 --- a/src/Main/SharpDevelop/Project/SolutionFolder.cs +++ b/src/Main/SharpDevelop/Project/SolutionFolder.cs @@ -2,6 +2,7 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; @@ -40,7 +41,7 @@ namespace ICSharpCode.SharpDevelop.Project this.folder = folder; } - void ValidateItem(ISolutionItem item) + protected override void ValidateItem(ISolutionItem item) { if (item == null) throw new ArgumentNullException("item"); @@ -50,52 +51,17 @@ namespace ICSharpCode.SharpDevelop.Project throw new ArgumentException("The item already has a parent folder"); } - protected override void ClearItems() + protected override void OnCollectionChanged(IReadOnlyCollection removedItems, IReadOnlyCollection addedItems) { - var oldItems = this.ToArray(); - base.ClearItems(); - using (BlockReentrancy()) { - foreach (var item in oldItems) { - folder.parentSolution.ReportRemovedItem(item); - item.ParentFolder = null; - } + foreach (ISolutionItem item in removedItems) { + folder.parentSolution.ReportRemovedItem(item); + item.ParentFolder = null; } - } - - protected override void InsertItem(int index, ISolutionItem item) - { - ValidateItem(item); - base.InsertItem(index, item); - } - - protected override void SetItem(int index, ISolutionItem item) - { - ValidateItem(item); - base.SetItem(index, item); - } - - protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) - { - switch (e.Action) { - case NotifyCollectionChangedAction.Add: - case NotifyCollectionChangedAction.Remove: - case NotifyCollectionChangedAction.Replace: - if (e.OldItems != null) { - foreach (ISolutionItem item in e.OldItems) { - folder.parentSolution.ReportRemovedItem(item); - item.ParentFolder = null; - } - } - if (e.NewItems != null) { - foreach (ISolutionItem item in e.NewItems) { - item.ParentFolder = folder; - folder.parentSolution.ReportAddedItem(item); - } - } - break; - // ignore Move; and Reset is special-cased in ClearItems() + foreach (ISolutionItem item in addedItems) { + item.ParentFolder = folder; + folder.parentSolution.ReportAddedItem(item); } - base.OnCollectionChanged(e); + base.OnCollectionChanged(removedItems, addedItems); folder.parentSolution.IsDirty = true; } }