Browse Source

Change IModelCollection to use a strongly-typed event handler.

pull/32/merge
Daniel Grunwald 13 years ago
parent
commit
e6b05f1b4a
  1. 65
      src/AddIns/Analysis/UnitTesting/Model/TestCollection.cs
  2. 28
      src/AddIns/Analysis/UnitTesting/Model/TestSolution.cs
  3. 9
      src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestClass.cs
  4. 22
      src/AddIns/Analysis/UnitTesting/Pad/UnitTestNode.cs
  5. 8
      src/AddIns/Analysis/UnitTesting/Test/Project/TestProjectWithOneClassTestFixture.cs
  6. 15
      src/AddIns/Misc/PackageManagement/Project/Src/Design/FakePackageManagementProjectService.cs
  7. 23
      src/AddIns/Misc/PackageManagement/Project/Src/Scripting/PackageManagementConsoleViewModel.cs
  8. 8
      src/AddIns/Misc/PackageManagement/Test/Src/Scripting/PackageManagementConsoleViewModelTests.cs
  9. 153
      src/Main/Base/Project/Dom/ConcatModelCollection.cs
  10. 104
      src/Main/Base/Project/Dom/FilterModelCollection.cs
  11. 57
      src/Main/Base/Project/Dom/IModelCollection.cs
  12. 2
      src/Main/Base/Project/Dom/ITypeDefinitionModelCollection.cs
  13. 54
      src/Main/Base/Project/Dom/KeyedModelCollection.cs
  14. 191
      src/Main/Base/Project/Dom/ModelCollectionLinq.cs
  15. 22
      src/Main/Base/Project/Dom/ReadOnlyModelCollection.cs
  16. 206
      src/Main/Base/Project/Dom/SimpleModelCollection.cs
  17. 6
      src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
  18. 2
      src/Main/Base/Project/Project/Configuration/IConfigurationOrPlatformNameCollection.cs
  19. 2
      src/Main/Base/Project/Project/ISolution.cs
  20. 21
      src/Main/Base/Test/Dom/CSharpModelTests.cs
  21. 33
      src/Main/Base/Test/Utils/ModelCollectionEventCheck.cs
  22. 13
      src/Main/SharpDevelop/Dom/NestedTypeDefinitionModelCollection.cs
  23. 13
      src/Main/SharpDevelop/Dom/TopLevelTypeDefinitionModelCollection.cs
  24. 14
      src/Main/SharpDevelop/Dom/TypeDefinitionModel.cs
  25. 51
      src/Main/SharpDevelop/Project/Configuration/SolutionConfigurationOrPlatformNameCollection.cs
  26. 10
      src/Main/SharpDevelop/Project/ProjectService.cs
  27. 33
      src/Main/SharpDevelop/Project/Solution.cs
  28. 54
      src/Main/SharpDevelop/Project/SolutionFolder.cs

65
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, /// Collection of tests that monitors the amount of tests for each result type,
/// allowing efficient updates of the overall result. /// allowing efficient updates of the overall result.
/// </summary> /// </summary>
public class TestCollection : ObservableCollection<ITest>, IModelCollection<ITest> public class TestCollection : SimpleModelCollection<ITest>, IModelCollection<ITest>, INotifyPropertyChanged
{ {
#region Struct TestCounts #region Struct TestCounts
struct TestCounts struct TestCounts
@ -133,10 +133,13 @@ namespace ICSharpCode.UnitTesting
OnPropertyChanged(compositeResultArgs); OnPropertyChanged(compositeResultArgs);
} }
// change visibility of PropertyChanged event to public public event PropertyChangedEventHandler PropertyChanged;
public new event PropertyChangedEventHandler PropertyChanged {
add { base.PropertyChanged += value; } void OnPropertyChanged(PropertyChangedEventArgs e)
remove { base.PropertyChanged -= value; } {
var handler = PropertyChanged;
if (handler != null)
handler(this, e);
} }
#endregion #endregion
@ -148,53 +151,25 @@ namespace ICSharpCode.UnitTesting
RaiseCountChangeEvents(old); RaiseCountChangeEvents(old);
} }
protected override void ClearItems() protected override void ValidateItem(ITest item)
{
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)
{ {
if (item == null) if (item == null)
throw new ArgumentNullException("item"); throw new ArgumentNullException("item");
TestCounts old = counts; base.ValidateItem(item);
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);
} }
protected override void SetItem(int index, ITest item) protected override void OnCollectionChanged(IReadOnlyCollection<ITest> removedItems, IReadOnlyCollection<ITest> addedItems)
{ {
if (item == null)
throw new ArgumentNullException("item");
TestCounts old = counts; TestCounts old = counts;
foreach (ITest test in removedItems) {
ITest oldItem = Items[index]; counts.Remove(test.Result);
counts.Remove(oldItem.Result); test.ResultChanged -= item_ResultChanged;
oldItem.ResultChanged -= item_ResultChanged; }
foreach (ITest test in addedItems) {
counts.Add(item.Result); counts.Add(test.Result);
item.ResultChanged += item_ResultChanged; test.ResultChanged += item_ResultChanged;
}
base.SetItem(index, item); base.OnCollectionChanged(removedItems, addedItems);
RaiseCountChangeEvents(old); RaiseCountChangeEvents(old);
} }
} }

28
src/AddIns/Analysis/UnitTesting/Model/TestSolution.cs

@ -136,30 +136,16 @@ namespace ICSharpCode.UnitTesting
} }
} }
void OnProjectsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) void OnProjectsCollectionChanged(IReadOnlyCollection<IProject> removedItems, IReadOnlyCollection<IProject> 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[i].Stop();
changeListeners.RemoveAt(i--);
} }
changeListeners.Clear(); }
foreach (var project in SD.ProjectService.AllProjects) { foreach (var project in addedItems) {
AddProject(project); 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<IProject>()) {
AddProject(project);
}
}
} }
} }

9
src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestClass.cs

@ -111,13 +111,8 @@ namespace ICSharpCode.UnitTesting
/// </summary> /// </summary>
public NUnitTestMethod FindTestMethodWithShortName(string name) public NUnitTestMethod FindTestMethodWithShortName(string name)
{ {
// Go backwards because base class tests come first // Use last match because base class tests come first
for (int i = this.NestedTestCollection.Count - 1; i >= 0; i--) { return this.NestedTestCollection.OfType<NUnitTestMethod>().LastOrDefault(method => method.MethodName == name);
var method = this.NestedTestCollection[i] as NUnitTestMethod;
if (method != null && method.MethodName == name)
return method;
}
return null;
} }
/// <summary> /// <summary>

22
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<ITest> removedItems, IReadOnlyCollection<ITest> addedItems)
{ {
if (!IsVisible) { if (!IsVisible) {
SwitchBackToLazyLoading(); SwitchBackToLazyLoading();
return; return;
} }
switch (e.Action) { Children.RemoveAll(n => removedItems.Contains(n.Model));
case NotifyCollectionChangedAction.Add: InsertNestedTests(addedItems);
InsertNestedTests(e.NewItems.Cast<ITest>());
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");
}
} }
void SwitchBackToLazyLoading() void SwitchBackToLazyLoading()

8
src/AddIns/Analysis/UnitTesting/Test/Project/TestProjectWithOneClassTestFixture.cs

@ -185,12 +185,10 @@ namespace RootNamespace {
Assert.AreEqual("NewMethod", testClass.NestedTests.Single().DisplayName); Assert.AreEqual("NewMethod", testClass.NestedTests.Single().DisplayName);
} }
void testProject_TestClasses_CollectionChanged(object source, NotifyCollectionChangedEventArgs e) void testProject_TestClasses_CollectionChanged(IReadOnlyCollection<ITest> removedItems, IReadOnlyCollection<ITest> addedItems)
{ {
if (e.Action == NotifyCollectionChangedAction.Add) classesRemoved.AddRange(removedItems.Cast<NUnitTestClass>());
classesAdded.AddRange(e.NewItems.Cast<NUnitTestClass>()); classesAdded.AddRange(addedItems.Cast<NUnitTestClass>());
else if (e.Action == NotifyCollectionChangedAction.Remove)
classesRemoved.AddRange(e.OldItems.Cast<NUnitTestClass>());
} }
} }
} }

15
src/AddIns/Misc/PackageManagement/Project/Src/Design/FakePackageManagementProjectService.cs

@ -32,15 +32,20 @@ namespace ICSharpCode.PackageManagement.Design
} }
} }
public readonly ConcatModelCollection<IProject> AllProjects = new ConcatModelCollection<IProject>(); public readonly SimpleModelCollection<IModelCollection<IProject>> ProjectCollections = new SimpleModelCollection<IModelCollection<IProject>>();
IModelCollection<IProject> allProjects;
IModelCollection<IProject> IPackageManagementProjectService.AllProjects {
get { return AllProjects; } public IModelCollection<IProject> AllProjects {
get {
if (allProjects == null)
allProjects = ProjectCollections.SelectMany(c => c);
return allProjects;
}
} }
public void AddProject(IProject project) public void AddProject(IProject project)
{ {
AllProjects.Inputs.Add(new ReadOnlyModelCollection<IProject>(new[] { project })); ProjectCollections.Add(new ReadOnlyModelCollection<IProject>(new[] { project }));
} }
public void AddProjectItem(IProject project, ProjectItem item) public void AddProjectItem(IProject project, ProjectItem item)

23
src/AddIns/Misc/PackageManagement/Project/Src/Scripting/PackageManagementConsoleViewModel.cs

@ -44,11 +44,13 @@ namespace ICSharpCode.PackageManagement.Scripting
void Init() void Init()
{ {
projectService.AllProjects.CollectionChanged += OnProjectCollectionChanged;
projects = new ObservableCollection<IProject>(projectService.AllProjects);
CreateCommands(); CreateCommands();
UpdatePackageSourceViewModels(); UpdatePackageSourceViewModels();
ReceiveNotificationsWhenPackageSourcesUpdated(); ReceiveNotificationsWhenPackageSourcesUpdated();
UpdateDefaultProject(); UpdateDefaultProject();
ReceiveNotificationsWhenSolutionIsUpdated();
InitConsoleHost(); InitConsoleHost();
} }
@ -142,13 +144,14 @@ namespace ICSharpCode.PackageManagement.Scripting
DefaultProject = this.Projects.FirstOrDefault(); DefaultProject = this.Projects.FirstOrDefault();
} }
void ReceiveNotificationsWhenSolutionIsUpdated() void OnProjectCollectionChanged(IReadOnlyCollection<IProject> removedItems, IReadOnlyCollection<IProject> addedItems)
{
projectService.AllProjects.CollectionChanged += OnProjectCollectionChanged;
}
void OnProjectCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{ {
foreach (var removedProject in removedItems) {
projects.Remove(removedProject);
}
foreach (var addedProject in addedItems) {
projects.Add(addedProject);
}
UpdateDefaultProject(); UpdateDefaultProject();
} }
@ -173,8 +176,10 @@ namespace ICSharpCode.PackageManagement.Scripting
return null; return null;
} }
public IModelCollection<IProject> Projects { ObservableCollection<IProject> projects;
get { return projectService.AllProjects; }
public ObservableCollection<IProject> Projects {
get { return projects; }
} }
public IProject DefaultProject { public IProject DefaultProject {

8
src/AddIns/Misc/PackageManagement/Test/Src/Scripting/PackageManagementConsoleViewModelTests.cs

@ -102,7 +102,7 @@ namespace PackageManagement.Tests.Scripting
ISolution solution = CreateSolutionWithOneProject(); ISolution solution = CreateSolutionWithOneProject();
projectService = new FakePackageManagementProjectService(); projectService = new FakePackageManagementProjectService();
projectService.OpenSolution = solution; projectService.OpenSolution = solution;
projectService.AllProjects.Inputs.Add(solution.Projects); projectService.ProjectCollections.Add(solution.Projects);
CreateViewModel(consoleHost, projectService); CreateViewModel(consoleHost, projectService);
return solution; return solution;
@ -151,7 +151,7 @@ namespace PackageManagement.Tests.Scripting
var solution = ProjectHelper.CreateSolution(); var solution = ProjectHelper.CreateSolution();
projectService = new FakePackageManagementProjectService(); projectService = new FakePackageManagementProjectService();
projectService.OpenSolution = solution; projectService.OpenSolution = solution;
projectService.AllProjects.Inputs.Add(solution.Projects); projectService.ProjectCollections.Add(solution.Projects);
CreateViewModel(consoleHost, projectService); CreateViewModel(consoleHost, projectService);
return solution; return solution;
} }
@ -166,14 +166,14 @@ namespace PackageManagement.Tests.Scripting
{ {
ISolution solution = projectService.OpenSolution; ISolution solution = projectService.OpenSolution;
projectService.OpenSolution = null; projectService.OpenSolution = null;
projectService.AllProjects.Inputs.Remove(solution.Projects); projectService.ProjectCollections.Remove(solution.Projects);
projectService.FireSolutionClosedEvent(solution); projectService.FireSolutionClosedEvent(solution);
} }
void OpenSolution(ISolution solution) void OpenSolution(ISolution solution)
{ {
projectService.OpenSolution = solution; projectService.OpenSolution = solution;
projectService.AllProjects.Inputs.Add(solution.Projects); projectService.ProjectCollections.Add(solution.Projects);
} }
IProject RemoveProjectFromSolution(ISolution solution) IProject RemoveProjectFromSolution(ISolution solution)

153
src/Main/Base/Project/Dom/ConcatModelCollection.cs

@ -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
{
/// <summary>
/// A <see cref="IModelCollection{T}"/> that works by concatening multiple
/// other model collections.
/// </summary>
public sealed class ConcatModelCollection<T> : IModelCollection<T>
{
sealed class InputCollection : Collection<IModelCollection<T>>
{
readonly ConcatModelCollection<T> owner;
public InputCollection(ConcatModelCollection<T> 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<T> 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<T> item)
{
RemoveItem(index);
InsertItem(index, item);
}
}
InputCollection inputs;
public ConcatModelCollection()
{
this.inputs = new InputCollection(this);
}
public IList<IModelCollection<T>> 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<T>)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<T> GetEnumerator()
{
return inputs.SelectMany(i => i).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

104
src/Main/Base/Project/Dom/FilterModelCollection.cs

@ -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
{
/// <summary>
/// A model collection that filters an input collection.
/// </summary>
public sealed class FilterModelCollection<T> : IModelCollection<T>
{
readonly IModelCollection<T> input;
readonly Func<T, bool> predicate;
bool isAttached;
NotifyCollectionChangedEventHandler collectionChanged;
public FilterModelCollection(IModelCollection<T> input, Func<T, bool> 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<T> outputItems = new List<T>();
foreach (T item in inputItems) {
if (predicate(item))
outputItems.Add(item);
}
return outputItems;
}
public int Count {
get {
return input.Count(predicate);
}
}
public IEnumerator<T> GetEnumerator()
{
return input.Where(predicate).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

57
src/Main/Base/Project/Dom/IModelCollection.cs

@ -10,44 +10,47 @@ using System.Linq;
namespace ICSharpCode.SharpDevelop.Dom namespace ICSharpCode.SharpDevelop.Dom
{ {
/// <summary> /// <summary>
/// A read-only collection that provides change notifications. /// Event handler for the <see cref="IModelCollection{T}.CollectionChanged"/> event.
/// </summary> /// </summary>
public interface IModelCollection<out T> : IReadOnlyCollection<T>, INotifyCollectionChanged /// <remarks>
{ /// We don't use the classic 'EventArgs' model for this event, because a EventArgs-class couldn't be covariant.
} /// </remarks>
public delegate void ModelCollectionChangedEventHandler<in T>(IReadOnlyCollection<T> removedItems, IReadOnlyCollection<T> addedItems);
/// <summary> /// <summary>
/// A collection that provides change notifications. /// A read-only collection that provides change notifications.
/// </summary> /// </summary>
public interface IMutableModelCollection<T> : IModelCollection<T>, ICollection<T> /// <remarks>
/// 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.
/// </remarks>
public interface IModelCollection<out T> : IReadOnlyCollection<T>
{ {
event ModelCollectionChangedEventHandler<T> CollectionChanged;
} }
/// <summary> /// <summary>
/// A model collection implementation that is based on a ObservableCollection. /// A collection that provides change notifications.
/// </summary> /// </summary>
public class SimpleModelCollection<T> : ObservableCollection<T>, IMutableModelCollection<T> public interface IMutableModelCollection<T> : IModelCollection<T>, ICollection<T>
{ {
public SimpleModelCollection() /// <summary>
{ /// Adds the specified items to the collection.
} /// </summary>
void AddRange(IEnumerable<T> items);
public SimpleModelCollection(IEnumerable<T> items) /// <summary>
: base(items) /// Removes all items matching the specified precidate.
{ /// </summary>
} int RemoveAll(Predicate<T> predicate);
}
/// <summary>
/// A model collection implementation that is based on a ReadOnlyCollection.
/// </summary>
public class ReadOnlyModelCollection<T> : ReadOnlyCollection<T>, IModelCollection<T>
{
public ReadOnlyModelCollection(IEnumerable<T> items)
: base(items.ToList())
{
}
event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged { add {} remove {} } /// <summary>
/// Can be used to group several operations into a batch update.
/// The <see cref="CollectionChanged"/> event will not fire during the batch update;
/// instead the event will be raised a single time after the batch update finishes.
/// </summary>
IDisposable BatchUpdate();
} }
} }

2
src/Main/Base/Project/Dom/ITypeDefinitionModelCollection.cs

@ -38,7 +38,7 @@ namespace ICSharpCode.SharpDevelop.Dom
{ {
public static readonly EmptyTypeDefinitionModelCollection Instance = new EmptyTypeDefinitionModelCollection(); public static readonly EmptyTypeDefinitionModelCollection Instance = new EmptyTypeDefinitionModelCollection();
event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged { event ModelCollectionChangedEventHandler<ITypeDefinitionModel> IModelCollection<ITypeDefinitionModel>.CollectionChanged {
add { } add { }
remove { } remove { }
} }

54
src/Main/Base/Project/Dom/KeyedModelCollection.cs

@ -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
{
/// <summary>
/// Observable KeyedCollection.
/// </summary>
public abstract class KeyedModelCollection<TKey, TItem> : KeyedCollection<TKey, TItem>, IModelCollection<TItem>
{
// 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;
}
}

191
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
{
/// <summary>
/// Provides LINQ operators for .
/// </summary>
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<TSource> Where<TSource>(this IModelCollection<TSource> source, Func<TSource, bool> predicate)
{
}*/
#endregion
#region Select
public static IModelCollection<TResult> Select<TSource, TResult>(this IModelCollection<TSource> source, Func<TSource, TResult> selector)
{
// HACK: emulating Select with SelectMany is much less efficient than a direct implementation could be
return SelectMany(source, item => new ReadOnlyModelCollection<TSource>(new[] { item }), (a, b) => selector(b));
}
#endregion
#region SelectMany
public static IModelCollection<S> SelectMany<T, S>(this IModelCollection<T> input, Func<T, IModelCollection<S>> selector)
{
return SelectMany(input, selector, (a, b) => b);
}
public static IModelCollection<TResult> SelectMany<TSource, TCollection, TResult>(this IModelCollection<TSource> source, Func<TSource, IModelCollection<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> 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<TSource, TCollection, TResult>(source, collectionSelector, resultSelector);
}
sealed class SelectManyModelCollection<TSource, TCollection, TResult> : IModelCollection<TResult>
{
readonly IModelCollection<TSource> source;
readonly Func<TSource, IModelCollection<TCollection>> collectionSelector;
readonly Func<TSource, TCollection, TResult> resultSelector;
List<InputCollection> inputCollections;
public SelectManyModelCollection(IModelCollection<TSource> source, Func<TSource, IModelCollection<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)
{
this.source = source;
this.collectionSelector = collectionSelector;
this.resultSelector = resultSelector;
}
ModelCollectionChangedEventHandler<TResult> collectionChanged;
public event ModelCollectionChangedEventHandler<TResult> CollectionChanged {
add {
if (value == null)
return;
if (inputCollections == null) {
source.CollectionChanged += OnSourceCollectionChanged;
inputCollections = new List<InputCollection>();
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<TResult> removedItems, IReadOnlyCollection<TResult> addedItems)
{
if (collectionChanged != null)
collectionChanged(removedItems, addedItems);
}
void OnSourceCollectionChanged(IReadOnlyCollection<TSource> removedItems, IReadOnlyCollection<TSource> addedItems)
{
List<TResult> removedResults = new List<TResult>();
foreach (TSource removedSource in removedItems) {
for (int i = 0; i < inputCollections.Count; i++) {
var inputCollection = inputCollections[i];
if (EqualityComparer<TSource>.Default.Equals(inputCollection.source, removedSource)) {
inputCollection.AddResultsToList(removedResults);
inputCollection.UnregisterEvent();
inputCollections.RemoveAt(i);
break;
}
}
}
List<TResult> addedResults = new List<TResult>();
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<TSource, TCollection, TResult> parent;
internal readonly TSource source;
readonly IModelCollection<TCollection> collection;
public InputCollection(SelectManyModelCollection<TSource, TCollection, TResult> parent, TSource source)
{
this.parent = parent;
this.source = source;
this.collection = parent.collectionSelector(source);
}
public void AddResultsToList(List<TResult> 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<TResult> GetResults(IReadOnlyCollection<TCollection> itemsCollection)
{
List<TResult> results = new List<TResult>(itemsCollection.Count);
foreach (var item in itemsCollection) {
results.Add(parent.resultSelector(source, item));
}
return results;
}
void OnCollectionChanged(IReadOnlyCollection<TCollection> removedItems, IReadOnlyCollection<TCollection> addedItems)
{
parent.OnCollectionChanged(GetResults(removedItems), GetResults(addedItems));
}
}
public IEnumerator<TResult> 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
}
}

22
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
{
/// <summary>
/// A model collection implementation that is based on a ReadOnlyCollection.
/// </summary>
public class ReadOnlyModelCollection<T> : ReadOnlyCollection<T>, IModelCollection<T>
{
public ReadOnlyModelCollection(IEnumerable<T> items)
: base(items.ToList())
{
}
event ModelCollectionChangedEventHandler<T> IModelCollection<T>.CollectionChanged { add {} remove {} }
}
}

206
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
{
/// <summary>
/// A model collection implementation.
/// </summary>
public class SimpleModelCollection<T> : IMutableModelCollection<T>
{
readonly List<T> list;
List<T> addedItems;
List<T> removedItems;
public SimpleModelCollection()
{
this.list = new List<T>();
}
public SimpleModelCollection(IEnumerable<T> items)
{
this.list = new List<T>(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<T> CollectionChanged;
bool isWithinBatchOperation;
bool isRaisingEvent;
void RaiseEventIfNotInBatch()
{
if (isWithinBatchOperation)
return;
IReadOnlyCollection<T> removed = this.removedItems;
IReadOnlyCollection<T> added = this.addedItems;
this.removedItems = null;
this.addedItems = null;
this.isRaisingEvent = true;
try {
OnCollectionChanged(removed ?? EmptyList<T>.Instance, added ?? EmptyList<T>.Instance);
} finally {
this.isRaisingEvent = false;
}
}
protected virtual void OnCollectionChanged(IReadOnlyCollection<T> removedItems, IReadOnlyCollection<T> 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<T>.IsReadOnly {
get { return false; }
}
public IEnumerator<T> 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<T>();
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<T>();
addedItems.Add(item);
list.Add(item);
RaiseEventIfNotInBatch();
}
public void AddRange(IEnumerable<T> items)
{
if (items == null)
throw new ArgumentNullException("items");
CheckReentrancy();
List<T> 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<T>();
removedItems.Add(item);
RaiseEventIfNotInBatch();
return true;
} else {
return false;
}
}
public int RemoveAll(Predicate<T> predicate)
{
CheckReentrancy();
int count = list.RemoveAll(
delegate(T obj) {
if (predicate(obj)) {
if (addedItems != null)
addedItems.Remove(obj);
if (removedItems == null)
removedItems = new List<T>();
removedItems.Add(obj);
return true;
} else {
return false;
}
});
if (count > 0)
RaiseEventIfNotInBatch();
return count;
}
#endregion
}
}

6
src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj

@ -84,17 +84,17 @@
</Compile> </Compile>
<Compile Include="Designer\IDesignerTypeResolutionService.cs" /> <Compile Include="Designer\IDesignerTypeResolutionService.cs" />
<Compile Include="Designer\TypeResolutionService.cs" /> <Compile Include="Designer\TypeResolutionService.cs" />
<Compile Include="Dom\ConcatModelCollection.cs" />
<Compile Include="Dom\FilterModelCollection.cs" />
<Compile Include="Dom\IEntityModelContext.cs" /> <Compile Include="Dom\IEntityModelContext.cs" />
<Compile Include="Dom\IMemberModel.cs" /> <Compile Include="Dom\IMemberModel.cs" />
<Compile Include="Dom\IModelFactory.cs" /> <Compile Include="Dom\IModelFactory.cs" />
<Compile Include="Dom\ITypeDefinitionModel.cs" /> <Compile Include="Dom\ITypeDefinitionModel.cs" />
<Compile Include="Dom\ITypeDefinitionModelCollection.cs" /> <Compile Include="Dom\ITypeDefinitionModelCollection.cs" />
<Compile Include="Dom\KeyedModelCollection.cs" />
<Compile Include="Dom\IEntityModel.cs" /> <Compile Include="Dom\IEntityModel.cs" />
<Compile Include="Dom\IModelCollection.cs" /> <Compile Include="Dom\IModelCollection.cs" />
<Compile Include="Dom\ITreeNodeFactory.cs" /> <Compile Include="Dom\ITreeNodeFactory.cs" />
<Compile Include="Dom\ModelCollectionLinq.cs" />
<Compile Include="Dom\ReadOnlyModelCollection.cs" />
<Compile Include="Dom\SimpleModelCollection.cs" />
<Compile Include="Editor\AvalonEditTextEditorAdapter.cs" /> <Compile Include="Editor\AvalonEditTextEditorAdapter.cs" />
<Compile Include="Editor\Bookmarks\BookmarkBase.cs" /> <Compile Include="Editor\Bookmarks\BookmarkBase.cs" />
<Compile Include="Editor\Bookmarks\BookmarkEventArgs.cs" /> <Compile Include="Editor\Bookmarks\BookmarkEventArgs.cs" />

2
src/Main/Base/Project/Project/Configuration/IConfigurationOrPlatformNameCollection.cs

@ -12,7 +12,7 @@ namespace ICSharpCode.SharpDevelop.Project
/// <summary> /// <summary>
/// Represents a collection of configuration or platform names. /// Represents a collection of configuration or platform names.
/// </summary> /// </summary>
public interface IConfigurationOrPlatformNameCollection : IReadOnlyCollection<string>, INotifyCollectionChanged public interface IConfigurationOrPlatformNameCollection : IModelCollection<string>
{ {
/// <summary> /// <summary>
/// Validates the input name. /// Validates the input name.

2
src/Main/Base/Project/Project/ISolution.cs

@ -59,7 +59,7 @@ namespace ICSharpCode.SharpDevelop.Project
/// Gets the list of global sections. /// Gets the list of global sections.
/// These can be used to store additional data within the solution file. /// These can be used to store additional data within the solution file.
/// </summary> /// </summary>
IList<SolutionSection> GlobalSections { get; } IMutableModelCollection<SolutionSection> GlobalSections { get; }
/// <summary> /// <summary>
/// Finds the item with the specified <see cref="ISolutionItem.IdGuid"/>; /// Finds the item with the specified <see cref="ISolutionItem.IdGuid"/>;

21
src/Main/Base/Test/Dom/CSharpModelTests.cs

@ -22,7 +22,19 @@ namespace ICSharpCode.SharpDevelop.Dom
IProjectContent projectContent; IProjectContent projectContent;
IEntityModelContext context; IEntityModelContext context;
TopLevelTypeDefinitionModelCollection topLevelTypeModels; TopLevelTypeDefinitionModelCollection topLevelTypeModels;
List<NotifyCollectionChangedEventArgs> topLevelChangeEventArgs = new List<NotifyCollectionChangedEventArgs>(); List<RemovedAndAddedPair<ITypeDefinitionModel>> topLevelChangeEventArgs = new List<RemovedAndAddedPair<ITypeDefinitionModel>>();
class RemovedAndAddedPair<T>
{
public IReadOnlyCollection<T> OldItems { get; private set; }
public IReadOnlyCollection<T> NewItems { get; private set; }
public RemovedAndAddedPair(IReadOnlyCollection<T> oldItems, IReadOnlyCollection<T> newItems)
{
this.OldItems = oldItems;
this.NewItems = newItems;
}
}
#region SetUp and other helper methods #region SetUp and other helper methods
[SetUp] [SetUp]
@ -37,7 +49,7 @@ namespace ICSharpCode.SharpDevelop.Dom
SD.ParserService.Stub(p => p.GetCompilation(project)).WhenCalled(c => c.ReturnValue = projectContent.CreateCompilation()); SD.ParserService.Stub(p => p.GetCompilation(project)).WhenCalled(c => c.ReturnValue = projectContent.CreateCompilation());
topLevelChangeEventArgs.Clear(); topLevelChangeEventArgs.Clear();
topLevelTypeModels.CollectionChanged += (sender, e) => topLevelChangeEventArgs.Add(e); topLevelTypeModels.CollectionChanged += (oldItems, newItems) => topLevelChangeEventArgs.Add(new RemovedAndAddedPair<ITypeDefinitionModel>(oldItems, newItems));
} }
[TearDown] [TearDown]
@ -94,7 +106,7 @@ namespace ICSharpCode.SharpDevelop.Dom
AddCodeFile("test.cs", @"class SimpleClass {}"); AddCodeFile("test.cs", @"class SimpleClass {}");
Assert.AreEqual(1, topLevelTypeModels.Count); Assert.AreEqual(1, topLevelTypeModels.Count);
var simpleClass = topLevelTypeModels.Single(); var simpleClass = topLevelTypeModels.Single();
Assert.AreEqual(NotifyCollectionChangedAction.Add, topLevelChangeEventArgs.Single().Action); Assert.IsEmpty(topLevelChangeEventArgs.Single().OldItems);
Assert.AreEqual(new[] { simpleClass }, topLevelChangeEventArgs.Single().NewItems); Assert.AreEqual(new[] { simpleClass }, topLevelChangeEventArgs.Single().NewItems);
topLevelChangeEventArgs.Clear(); // clear for follow-up tests topLevelChangeEventArgs.Clear(); // clear for follow-up tests
} }
@ -119,7 +131,6 @@ namespace ICSharpCode.SharpDevelop.Dom
UpdateCodeFile("test.cs", "class OtherClass { }"); UpdateCodeFile("test.cs", "class OtherClass { }");
var otherClass = topLevelTypeModels.Single(); var otherClass = topLevelTypeModels.Single();
Assert.AreNotSame(simpleClass, otherClass); Assert.AreNotSame(simpleClass, otherClass);
Assert.AreEqual(NotifyCollectionChangedAction.Replace, topLevelChangeEventArgs.Single().Action);
Assert.AreEqual(new[] { simpleClass }, topLevelChangeEventArgs.Single().OldItems); Assert.AreEqual(new[] { simpleClass }, topLevelChangeEventArgs.Single().OldItems);
Assert.AreEqual(new[] { otherClass }, topLevelChangeEventArgs.Single().NewItems); Assert.AreEqual(new[] { otherClass }, topLevelChangeEventArgs.Single().NewItems);
} }
@ -132,8 +143,8 @@ namespace ICSharpCode.SharpDevelop.Dom
RemoveCodeFile("test.cs"); RemoveCodeFile("test.cs");
Assert.IsEmpty(topLevelTypeModels); Assert.IsEmpty(topLevelTypeModels);
// check removal event // check removal event
Assert.AreEqual(NotifyCollectionChangedAction.Remove, topLevelChangeEventArgs.Single().Action);
Assert.AreEqual(new[] { simpleClass }, topLevelChangeEventArgs.Single().OldItems); Assert.AreEqual(new[] { simpleClass }, topLevelChangeEventArgs.Single().OldItems);
Assert.IsEmpty(topLevelChangeEventArgs.Single().NewItems);
// test that accessing properties of a removed class still works // test that accessing properties of a removed class still works
Assert.AreEqual(new FullTypeName("SimpleClass"), simpleClass.FullTypeName); Assert.AreEqual(new FullTypeName("SimpleClass"), simpleClass.FullTypeName);

33
src/Main/Base/Test/Utils/ModelCollectionEventCheck.cs

@ -28,34 +28,13 @@ namespace ICSharpCode.SharpDevelop.Utils
modelCollection.CollectionChanged += OnCollectionChanged; modelCollection.CollectionChanged += OnCollectionChanged;
} }
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) void OnCollectionChanged(IReadOnlyCollection<T> removedItems, IReadOnlyCollection<T> addedItems)
{ {
switch (e.Action) { foreach (T removed in removedItems) {
case NotifyCollectionChangedAction.Add: list.Remove(removed);
list.InsertRange(e.NewStartingIndex, e.NewItems.Cast<T>()); }
break; foreach (T added in addedItems) {
case NotifyCollectionChangedAction.Remove: list.Add(added);
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<T>());
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<T>());
break;
case NotifyCollectionChangedAction.Reset:
list.Clear();
list.AddRange(modelCollection);
break;
default:
throw new Exception("Invalid value for NotifyCollectionChangedAction");
} }
Verify(); Verify();
} }

13
src/Main/SharpDevelop/Dom/NestedTypeDefinitionModelCollection.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem;
namespace ICSharpCode.SharpDevelop.Dom namespace ICSharpCode.SharpDevelop.Dom
@ -18,7 +19,7 @@ namespace ICSharpCode.SharpDevelop.Dom
this.context = context; this.context = context;
} }
public event NotifyCollectionChangedEventHandler CollectionChanged; public event ModelCollectionChangedEventHandler<ITypeDefinitionModel> CollectionChanged;
public IEnumerator<ITypeDefinitionModel> GetEnumerator() public IEnumerator<ITypeDefinitionModel> GetEnumerator()
{ {
@ -101,13 +102,9 @@ namespace ICSharpCode.SharpDevelop.Dom
} }
} }
// Raise the event if necessary: // Raise the event if necessary:
if (CollectionChanged != null) { if (CollectionChanged != null && (oldModels != null || newModels != null)) {
if (oldModels != null && newModels != null) IReadOnlyCollection<ITypeDefinitionModel> emptyList = EmptyList<ITypeDefinitionModel>.Instance;
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newModels, oldModels)); CollectionChanged(oldModels ?? emptyList, newModels ?? emptyList);
else if (oldModels != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldModels));
else if (newModels != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newModels));
} }
} }
} }

13
src/Main/SharpDevelop/Dom/TopLevelTypeDefinitionModelCollection.cs

@ -5,6 +5,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem;
namespace ICSharpCode.SharpDevelop.Dom namespace ICSharpCode.SharpDevelop.Dom
@ -16,7 +17,7 @@ namespace ICSharpCode.SharpDevelop.Dom
{ {
readonly IEntityModelContext context; readonly IEntityModelContext context;
Dictionary<TopLevelTypeName, TypeDefinitionModel> dict = new Dictionary<TopLevelTypeName, TypeDefinitionModel>(); Dictionary<TopLevelTypeName, TypeDefinitionModel> dict = new Dictionary<TopLevelTypeName, TypeDefinitionModel>();
public event NotifyCollectionChangedEventHandler CollectionChanged; public event ModelCollectionChangedEventHandler<ITypeDefinitionModel> CollectionChanged;
public TopLevelTypeDefinitionModelCollection(IEntityModelContext context) public TopLevelTypeDefinitionModelCollection(IEntityModelContext context)
{ {
@ -114,13 +115,9 @@ namespace ICSharpCode.SharpDevelop.Dom
} }
} }
// Raise the event if necessary: // Raise the event if necessary:
if (CollectionChanged != null) { if (CollectionChanged != null && (oldModels != null || newModels != null)) {
if (oldModels != null && newModels != null) IReadOnlyCollection<ITypeDefinitionModel> emptyList = EmptyList<ITypeDefinitionModel>.Instance;
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newModels, oldModels)); CollectionChanged(oldModels ?? emptyList, newModels ?? emptyList);
else if (oldModels != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldModels));
else if (newModels != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newModels));
} }
} }

14
src/Main/SharpDevelop/Dom/TypeDefinitionModel.cs

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.SharpDevelop.Parser; using ICSharpCode.SharpDevelop.Parser;
using ICSharpCode.SharpDevelop.Project; using ICSharpCode.SharpDevelop.Project;
@ -98,7 +99,7 @@ namespace ICSharpCode.SharpDevelop.Dom
} }
lists.Insert(partIndex, newItems); lists.Insert(partIndex, newItems);
if (collectionChanged != null) if (collectionChanged != null)
collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, GetCount(partIndex))); collectionChanged(EmptyList<MemberModel>.Instance, newItems);
} }
public void RemovePart(int partIndex) public void RemovePart(int partIndex)
@ -106,7 +107,7 @@ namespace ICSharpCode.SharpDevelop.Dom
var oldItems = lists[partIndex]; var oldItems = lists[partIndex];
lists.RemoveAt(partIndex); lists.RemoveAt(partIndex);
if (collectionChanged != null) if (collectionChanged != null)
collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, GetCount(partIndex))); collectionChanged(oldItems, EmptyList<MemberModel>.Instance);
} }
public void UpdatePart(int partIndex, IUnresolvedTypeDefinition newPart) public void UpdatePart(int partIndex, IUnresolvedTypeDefinition newPart)
@ -143,8 +144,9 @@ namespace ICSharpCode.SharpDevelop.Dom
newItems[i].strongParentCollectionReference = this; newItems[i].strongParentCollectionReference = this;
} }
list.InsertRange(startPos, newItems); list.InsertRange(startPos, newItems);
if (collectionChanged != null) if (collectionChanged != null && (oldItems.Count > 0 || newItems.Length > 0)) {
collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, GetCount(partIndex) + startPos)); collectionChanged(oldItems, newItems);
}
} }
static bool IsMatch(MemberModel memberModel, IUnresolvedMember newMember) static bool IsMatch(MemberModel memberModel, IUnresolvedMember newMember)
@ -152,9 +154,9 @@ namespace ICSharpCode.SharpDevelop.Dom
return memberModel.EntityType == newMember.EntityType && memberModel.Name == newMember.Name; return memberModel.EntityType == newMember.EntityType && memberModel.Name == newMember.Name;
} }
NotifyCollectionChangedEventHandler collectionChanged; ModelCollectionChangedEventHandler<MemberModel> collectionChanged;
public event NotifyCollectionChangedEventHandler CollectionChanged { public event ModelCollectionChangedEventHandler<MemberModel> CollectionChanged {
add { add {
collectionChanged += value; collectionChanged += value;
// Set strong reference to collection while there are event listeners // Set strong reference to collection while there are event listeners

51
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) // 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.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.NRefactory;
using ICSharpCode.SharpDevelop.Dom;
using Microsoft.Build.Logging; using Microsoft.Build.Logging;
namespace ICSharpCode.SharpDevelop.Project namespace ICSharpCode.SharpDevelop.Project
{ {
class SolutionConfigurationOrPlatformNameCollection : ObservableCollection<string>, IConfigurationOrPlatformNameCollection class SolutionConfigurationOrPlatformNameCollection : IConfigurationOrPlatformNameCollection
{ {
public event ModelCollectionChangedEventHandler<string> CollectionChanged;
readonly List<string> list = new List<string>();
readonly Solution solution; readonly Solution solution;
readonly bool isPlatform; readonly bool isPlatform;
@ -20,6 +26,24 @@ namespace ICSharpCode.SharpDevelop.Project
this.isPlatform = isPlatform; this.isPlatform = isPlatform;
} }
#region IReadOnlyCollection implementation
public int Count {
get { return list.Count; }
}
public IEnumerator<string> GetEnumerator()
{
return list.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return list.GetEnumerator();
}
#endregion
public string ValidateName(string name) public string ValidateName(string name)
{ {
if (name == null) if (name == null)
@ -38,13 +62,16 @@ namespace ICSharpCode.SharpDevelop.Project
newName = ValidateName(newName); newName = ValidateName(newName);
if (newName == null) if (newName == null)
throw new ArgumentException(); throw new ArgumentException();
Add(newName); list.Add(newName);
if (copyFrom != null)
throw new NotImplementedException();
OnCollectionChanged(EmptyList<string>.Instance, new[] { newName });
} }
int GetIndex(string name) int GetIndex(string name)
{ {
for (int i = 0; i < this.Count; i++) { for (int i = 0; i < list.Count; i++) {
if (ConfigurationAndPlatform.ConfigurationNameComparer.Equals(this[i], name)) if (ConfigurationAndPlatform.ConfigurationNameComparer.Equals(list[i], name))
return i; return i;
} }
return -1; return -1;
@ -53,8 +80,11 @@ namespace ICSharpCode.SharpDevelop.Project
void IConfigurationOrPlatformNameCollection.Remove(string name) void IConfigurationOrPlatformNameCollection.Remove(string name)
{ {
int pos = GetIndex(name); int pos = GetIndex(name);
if (pos >= 0) if (pos >= 0) {
RemoveAt(pos); name = list[pos]; // get the name in original case
list.RemoveAt(pos);
OnCollectionChanged(new[] { name }, EmptyList<string>.Instance);
}
} }
void IConfigurationOrPlatformNameCollection.Rename(string oldName, string newName) void IConfigurationOrPlatformNameCollection.Rename(string oldName, string newName)
@ -65,16 +95,19 @@ namespace ICSharpCode.SharpDevelop.Project
int pos = GetIndex(oldName); int pos = GetIndex(oldName);
if (pos < 0) if (pos < 0)
throw new ArgumentException(); throw new ArgumentException();
oldName = list[pos]; // get oldName in original case
foreach (var project in solution.Projects) { foreach (var project in solution.Projects) {
throw new NotImplementedException(); throw new NotImplementedException();
//project.ConfigurationMapping.RenameSolutionConfig(oldName, newName, isPlatform); //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<string> oldItems, IReadOnlyCollection<string> newItems)
{ {
base.OnCollectionChanged(e); if (CollectionChanged != null)
CollectionChanged(oldItems, newItems);
solution.IsDirty = true; solution.IsDirty = true;
} }
} }

10
src/Main/SharpDevelop/Project/ProjectService.cs

@ -24,11 +24,15 @@ namespace ICSharpCode.SharpDevelop.Project
applicationStateInfoService.RegisterStateGetter("ProjectService.CurrentSolution", delegate { return CurrentSolution; }); applicationStateInfoService.RegisterStateGetter("ProjectService.CurrentSolution", delegate { return CurrentSolution; });
applicationStateInfoService.RegisterStateGetter("ProjectService.CurrentProject", delegate { return CurrentProject; }); applicationStateInfoService.RegisterStateGetter("ProjectService.CurrentProject", delegate { return CurrentProject; });
} }
allSolutions = new SimpleModelCollection<ISolution>();
allProjects = allSolutions.SelectMany(s => s.Projects);
} }
#region CurrentSolution property + AllProjects collection #region CurrentSolution property + AllProjects collection
volatile static ISolution currentSolution; volatile static ISolution currentSolution;
ConcatModelCollection<IProject> allProjects = new ConcatModelCollection<IProject>(); readonly SimpleModelCollection<ISolution> allSolutions;
readonly IModelCollection<IProject> allProjects;
public event PropertyChangedEventHandler<ISolution> CurrentSolutionChanged = delegate { }; public event PropertyChangedEventHandler<ISolution> CurrentSolutionChanged = delegate { };
@ -41,10 +45,10 @@ namespace ICSharpCode.SharpDevelop.Project
if (oldValue != value) { if (oldValue != value) {
currentSolution = value; currentSolution = value;
if (oldValue != null) if (oldValue != null)
allProjects.Inputs.Remove(oldValue.Projects); allSolutions.Remove(oldValue);
CurrentSolutionChanged(this, new PropertyChangedEventArgs<ISolution>(oldValue, value)); CurrentSolutionChanged(this, new PropertyChangedEventArgs<ISolution>(oldValue, value));
if (value != null) if (value != null)
allProjects.Inputs.Add(value.Projects); allSolutions.Add(value);
CommandManager.InvalidateRequerySuggested(); CommandManager.InvalidateRequerySuggested();
SD.ParserService.InvalidateCurrentSolutionSnapshot(); SD.ParserService.InvalidateCurrentSolutionSnapshot();
} }

33
src/Main/SharpDevelop/Project/Solution.cs

@ -7,6 +7,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Threading; using System.Windows.Threading;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.NRefactory;
using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Workbench; using ICSharpCode.SharpDevelop.Workbench;
@ -109,14 +110,16 @@ namespace ICSharpCode.SharpDevelop.Project
void OnProjectAdded(IProject project) void OnProjectAdded(IProject project)
{ {
projects.Add(project); projects.Add(project);
project.ProjectSections.CollectionChanged += OnProjectSectionsChanged; project.ProjectSections.CollectionChanged += OnSolutionSectionCollectionChanged;
OnSolutionSectionCollectionChanged(EmptyList<SolutionSection>.Instance, project.ProjectSections);
if (startupProject == null && AutoDetectStartupProject() == project) 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 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) void OnProjectRemoved(IProject project)
{ {
project.ProjectSections.CollectionChanged -= OnProjectSectionsChanged; project.ProjectSections.CollectionChanged -= OnSolutionSectionCollectionChanged;
OnSolutionSectionCollectionChanged(project.ProjectSections, EmptyList<SolutionSection>.Instance);
bool wasStartupProject = (startupProject == project); bool wasStartupProject = (startupProject == project);
if (wasStartupProject) if (wasStartupProject)
startupProject = null; // this will force auto-detection on the next property access startupProject = null; // this will force auto-detection on the next property access
@ -131,12 +134,6 @@ namespace ICSharpCode.SharpDevelop.Project
}, DispatcherPriority.Background); }, DispatcherPriority.Background);
} }
void OnProjectSectionsChanged(object sender, EventArgs e)
{
this.IsDirty = true;
// TODO: also monitor changes within the project section
}
internal void ReportRemovedItem(ISolutionItem oldItem) internal void ReportRemovedItem(ISolutionItem oldItem)
{ {
if (oldItem is ISolutionFolder) { if (oldItem is ISolutionFolder) {
@ -168,12 +165,28 @@ namespace ICSharpCode.SharpDevelop.Project
} }
} }
List<SolutionSection> globalSections = new List<SolutionSection>(); SimpleModelCollection<SolutionSection> globalSections = new SimpleModelCollection<SolutionSection>();
public IList<SolutionSection> GlobalSections { public IMutableModelCollection<SolutionSection> GlobalSections {
get { return globalSections; } get { return globalSections; }
} }
void OnSolutionSectionCollectionChanged(IReadOnlyCollection<SolutionSection> oldItems, IReadOnlyCollection<SolutionSection> 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) public ISolutionItem GetItemByGuid(Guid guid)
{ {
// Maybe we should maintain a dictionary to make these lookups faster? // Maybe we should maintain a dictionary to make these lookups faster?

54
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) // 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.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
@ -40,7 +41,7 @@ namespace ICSharpCode.SharpDevelop.Project
this.folder = folder; this.folder = folder;
} }
void ValidateItem(ISolutionItem item) protected override void ValidateItem(ISolutionItem item)
{ {
if (item == null) if (item == null)
throw new ArgumentNullException("item"); throw new ArgumentNullException("item");
@ -50,52 +51,17 @@ namespace ICSharpCode.SharpDevelop.Project
throw new ArgumentException("The item already has a parent folder"); throw new ArgumentException("The item already has a parent folder");
} }
protected override void ClearItems() protected override void OnCollectionChanged(IReadOnlyCollection<ISolutionItem> removedItems, IReadOnlyCollection<ISolutionItem> addedItems)
{ {
var oldItems = this.ToArray(); foreach (ISolutionItem item in removedItems) {
base.ClearItems(); folder.parentSolution.ReportRemovedItem(item);
using (BlockReentrancy()) { item.ParentFolder = null;
foreach (var item in oldItems) {
folder.parentSolution.ReportRemovedItem(item);
item.ParentFolder = null;
}
} }
} foreach (ISolutionItem item in addedItems) {
item.ParentFolder = folder;
protected override void InsertItem(int index, ISolutionItem item) folder.parentSolution.ReportAddedItem(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()
} }
base.OnCollectionChanged(e); base.OnCollectionChanged(removedItems, addedItems);
folder.parentSolution.IsDirty = true; folder.parentSolution.IsDirty = true;
} }
} }

Loading…
Cancel
Save