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 @@ -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.
/// </summary>
public class TestCollection : ObservableCollection<ITest>, IModelCollection<ITest>
public class TestCollection : SimpleModelCollection<ITest>, IModelCollection<ITest>, INotifyPropertyChanged
{
#region Struct TestCounts
struct TestCounts
@ -133,10 +133,13 @@ namespace ICSharpCode.UnitTesting @@ -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 @@ -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<ITest> removedItems, IReadOnlyCollection<ITest> 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);
}
}

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

@ -136,30 +136,16 @@ namespace ICSharpCode.UnitTesting @@ -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.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<IProject>()) {
AddProject(project);
}
}
}
foreach (var project in addedItems) {
AddProject(project);
}
}

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

@ -111,13 +111,8 @@ namespace ICSharpCode.UnitTesting @@ -111,13 +111,8 @@ namespace ICSharpCode.UnitTesting
/// </summary>
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<NUnitTestMethod>().LastOrDefault(method => method.MethodName == name);
}
/// <summary>

22
src/AddIns/Analysis/UnitTesting/Pad/UnitTestNode.cs

@ -75,30 +75,14 @@ namespace ICSharpCode.UnitTesting @@ -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) {
SwitchBackToLazyLoading();
return;
}
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
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");
}
Children.RemoveAll(n => removedItems.Contains(n.Model));
InsertNestedTests(addedItems);
}
void SwitchBackToLazyLoading()

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

@ -185,12 +185,10 @@ namespace RootNamespace { @@ -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<ITest> removedItems, IReadOnlyCollection<ITest> addedItems)
{
if (e.Action == NotifyCollectionChangedAction.Add)
classesAdded.AddRange(e.NewItems.Cast<NUnitTestClass>());
else if (e.Action == NotifyCollectionChangedAction.Remove)
classesRemoved.AddRange(e.OldItems.Cast<NUnitTestClass>());
classesRemoved.AddRange(removedItems.Cast<NUnitTestClass>());
classesAdded.AddRange(addedItems.Cast<NUnitTestClass>());
}
}
}

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

@ -32,15 +32,20 @@ namespace ICSharpCode.PackageManagement.Design @@ -32,15 +32,20 @@ namespace ICSharpCode.PackageManagement.Design
}
}
public readonly ConcatModelCollection<IProject> AllProjects = new ConcatModelCollection<IProject>();
IModelCollection<IProject> IPackageManagementProjectService.AllProjects {
get { return AllProjects; }
public readonly SimpleModelCollection<IModelCollection<IProject>> ProjectCollections = new SimpleModelCollection<IModelCollection<IProject>>();
IModelCollection<IProject> allProjects;
public IModelCollection<IProject> AllProjects {
get {
if (allProjects == null)
allProjects = ProjectCollections.SelectMany(c => c);
return allProjects;
}
}
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)

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

@ -44,11 +44,13 @@ namespace ICSharpCode.PackageManagement.Scripting @@ -44,11 +44,13 @@ namespace ICSharpCode.PackageManagement.Scripting
void Init()
{
projectService.AllProjects.CollectionChanged += OnProjectCollectionChanged;
projects = new ObservableCollection<IProject>(projectService.AllProjects);
CreateCommands();
UpdatePackageSourceViewModels();
ReceiveNotificationsWhenPackageSourcesUpdated();
UpdateDefaultProject();
ReceiveNotificationsWhenSolutionIsUpdated();
InitConsoleHost();
}
@ -142,13 +144,14 @@ namespace ICSharpCode.PackageManagement.Scripting @@ -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<IProject> removedItems, IReadOnlyCollection<IProject> 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 @@ -173,8 +176,10 @@ namespace ICSharpCode.PackageManagement.Scripting
return null;
}
public IModelCollection<IProject> Projects {
get { return projectService.AllProjects; }
ObservableCollection<IProject> projects;
public ObservableCollection<IProject> Projects {
get { return projects; }
}
public IProject DefaultProject {

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

@ -102,7 +102,7 @@ namespace PackageManagement.Tests.Scripting @@ -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 @@ -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 @@ -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)

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

@ -1,153 +0,0 @@ @@ -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 @@ @@ -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; @@ -10,44 +10,47 @@ using System.Linq;
namespace ICSharpCode.SharpDevelop.Dom
{
/// <summary>
/// A read-only collection that provides change notifications.
/// Event handler for the <see cref="IModelCollection{T}.CollectionChanged"/> event.
/// </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>
/// A collection that provides change notifications.
/// A read-only collection that provides change notifications.
/// </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>
/// A model collection implementation that is based on a ObservableCollection.
/// A collection that provides change notifications.
/// </summary>
public class SimpleModelCollection<T> : ObservableCollection<T>, IMutableModelCollection<T>
public interface IMutableModelCollection<T> : IModelCollection<T>, ICollection<T>
{
public SimpleModelCollection()
{
}
public SimpleModelCollection(IEnumerable<T> items)
: base(items)
{
}
}
/// <summary>
/// Adds the specified items to the collection.
/// </summary>
void AddRange(IEnumerable<T> items);
/// <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())
{
}
/// <summary>
/// Removes all items matching the specified precidate.
/// </summary>
int RemoveAll(Predicate<T> predicate);
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 @@ -38,7 +38,7 @@ namespace ICSharpCode.SharpDevelop.Dom
{
public static readonly EmptyTypeDefinitionModelCollection Instance = new EmptyTypeDefinitionModelCollection();
event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged {
event ModelCollectionChangedEventHandler<ITypeDefinitionModel> IModelCollection<ITypeDefinitionModel>.CollectionChanged {
add { }
remove { }
}

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

@ -1,54 +0,0 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -84,17 +84,17 @@
</Compile>
<Compile Include="Designer\IDesignerTypeResolutionService.cs" />
<Compile Include="Designer\TypeResolutionService.cs" />
<Compile Include="Dom\ConcatModelCollection.cs" />
<Compile Include="Dom\FilterModelCollection.cs" />
<Compile Include="Dom\IEntityModelContext.cs" />
<Compile Include="Dom\IMemberModel.cs" />
<Compile Include="Dom\IModelFactory.cs" />
<Compile Include="Dom\ITypeDefinitionModel.cs" />
<Compile Include="Dom\ITypeDefinitionModelCollection.cs" />
<Compile Include="Dom\KeyedModelCollection.cs" />
<Compile Include="Dom\IEntityModel.cs" />
<Compile Include="Dom\IModelCollection.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\Bookmarks\BookmarkBase.cs" />
<Compile Include="Editor\Bookmarks\BookmarkEventArgs.cs" />

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

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

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

@ -59,7 +59,7 @@ namespace ICSharpCode.SharpDevelop.Project @@ -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.
/// </summary>
IList<SolutionSection> GlobalSections { get; }
IMutableModelCollection<SolutionSection> GlobalSections { get; }
/// <summary>
/// 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 @@ -22,7 +22,19 @@ namespace ICSharpCode.SharpDevelop.Dom
IProjectContent projectContent;
IEntityModelContext context;
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
[SetUp]
@ -37,7 +49,7 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -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<ITypeDefinitionModel>(oldItems, newItems));
}
[TearDown]
@ -94,7 +106,7 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -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 @@ -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 @@ -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);

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

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

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

@ -4,6 +4,7 @@ @@ -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 @@ -18,7 +19,7 @@ namespace ICSharpCode.SharpDevelop.Dom
this.context = context;
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public event ModelCollectionChangedEventHandler<ITypeDefinitionModel> CollectionChanged;
public IEnumerator<ITypeDefinitionModel> GetEnumerator()
{
@ -101,13 +102,9 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -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<ITypeDefinitionModel> emptyList = EmptyList<ITypeDefinitionModel>.Instance;
CollectionChanged(oldModels ?? emptyList, newModels ?? emptyList);
}
}
}

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

@ -5,6 +5,7 @@ using System; @@ -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 @@ -16,7 +17,7 @@ namespace ICSharpCode.SharpDevelop.Dom
{
readonly IEntityModelContext context;
Dictionary<TopLevelTypeName, TypeDefinitionModel> dict = new Dictionary<TopLevelTypeName, TypeDefinitionModel>();
public event NotifyCollectionChangedEventHandler CollectionChanged;
public event ModelCollectionChangedEventHandler<ITypeDefinitionModel> CollectionChanged;
public TopLevelTypeDefinitionModelCollection(IEntityModelContext context)
{
@ -114,13 +115,9 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -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<ITypeDefinitionModel> emptyList = EmptyList<ITypeDefinitionModel>.Instance;
CollectionChanged(oldModels ?? emptyList, newModels ?? emptyList);
}
}

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

@ -7,6 +7,7 @@ using System.Collections.Generic; @@ -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 @@ -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<MemberModel>.Instance, newItems);
}
public void RemovePart(int partIndex)
@ -106,7 +107,7 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -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<MemberModel>.Instance);
}
public void UpdatePart(int partIndex, IUnresolvedTypeDefinition newPart)
@ -143,8 +144,9 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -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 @@ -152,9 +154,9 @@ namespace ICSharpCode.SharpDevelop.Dom
return memberModel.EntityType == newMember.EntityType && memberModel.Name == newMember.Name;
}
NotifyCollectionChangedEventHandler collectionChanged;
ModelCollectionChangedEventHandler<MemberModel> collectionChanged;
public event NotifyCollectionChangedEventHandler CollectionChanged {
public event ModelCollectionChangedEventHandler<MemberModel> CollectionChanged {
add {
collectionChanged += value;
// Set strong reference to collection while there are event listeners

51
src/Main/SharpDevelop/Project/Configuration/SolutionConfigurationOrPlatformNameCollection.cs

@ -2,15 +2,21 @@ @@ -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<string>, IConfigurationOrPlatformNameCollection
class SolutionConfigurationOrPlatformNameCollection : IConfigurationOrPlatformNameCollection
{
public event ModelCollectionChangedEventHandler<string> CollectionChanged;
readonly List<string> list = new List<string>();
readonly Solution solution;
readonly bool isPlatform;
@ -20,6 +26,24 @@ namespace ICSharpCode.SharpDevelop.Project @@ -20,6 +26,24 @@ namespace ICSharpCode.SharpDevelop.Project
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)
{
if (name == null)
@ -38,13 +62,16 @@ namespace ICSharpCode.SharpDevelop.Project @@ -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<string>.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 @@ -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<string>.Instance);
}
}
void IConfigurationOrPlatformNameCollection.Rename(string oldName, string newName)
@ -65,16 +95,19 @@ namespace ICSharpCode.SharpDevelop.Project @@ -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<string> oldItems, IReadOnlyCollection<string> newItems)
{
base.OnCollectionChanged(e);
if (CollectionChanged != null)
CollectionChanged(oldItems, newItems);
solution.IsDirty = true;
}
}

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

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

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

@ -7,6 +7,7 @@ using System.IO; @@ -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 @@ -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<SolutionSection>.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<SolutionSection>.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 @@ -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 @@ -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; }
}
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)
{
// Maybe we should maintain a dictionary to make these lookups faster?

54
src/Main/SharpDevelop/Project/SolutionFolder.cs

@ -2,6 +2,7 @@ @@ -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 @@ -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 @@ -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<ISolutionItem> removedItems, IReadOnlyCollection<ISolutionItem> 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;
}
}

Loading…
Cancel
Save