2a98410bad2771fcbe6d775698141cfafdcf4226
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / Base / Core / Internal / PropertyEditing / Model / ModelPropertyValueCollection.cs
1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //----------------------------------------------------------------
4 namespace System.Activities.Presentation.Internal.PropertyEditing.Model 
5 {
6     using System;
7     using System.Collections.Generic;
8     using System.Collections.Specialized;
9     using System.Diagnostics;
10     using System.Diagnostics.CodeAnalysis;
11     using System.Globalization;
12     using System.Text;
13
14     using System.Activities.Presentation;
15     using System.Activities.Presentation.Model;
16     using System.Activities.Presentation.PropertyEditing;
17
18     using System.Activities.Presentation.Internal.Properties;
19     using System.Runtime;
20
21     // <summary>
22     // Collection of ModelPropertyValues used to model collections of ModelItems
23     // </summary>
24     internal class ModelPropertyValueCollection : PropertyValueCollection 
25     {
26
27         private List<ModelPropertyIndexer> _values;
28         private bool _listenToCollectionChanges = true;
29
30         // <summary>
31         // Basic ctor
32         // </summary>
33         // <param name="parentValue">Parent PropertyValue</param>
34         public ModelPropertyValueCollection(ModelPropertyValue parentValue) : base(parentValue) 
35         {
36             if (parentValue == null)
37             {
38                 throw FxTrace.Exception.ArgumentNull("parentValue");
39             }
40
41             // Wrap each existing item in the collection in ModelPropertyEntryIndexer
42             ModelItemCollection collection = this.GetRawCollection();
43             if (collection != null && collection.Count > 0) 
44             {
45                 _values = new List<ModelPropertyIndexer>();
46                 int i = 0;
47                 foreach (ModelItem item in collection) 
48                 {
49                     _values.Add(CreateModelPropertyIndexer(item, i++));
50                 }
51             }
52
53             // Hook into the collection changed events
54             if (collection != null)
55             {
56                 collection.CollectionChanged += new NotifyCollectionChangedEventHandler(OnUnderlyingCollectionChanged);
57             }
58         }
59
60         // <summary>
61         // Gets the number of items in this collection
62         // </summary>
63         public override int Count 
64         {
65             get {
66                 return _values == null ? 0 : _values.Count;
67             }
68         }
69
70         // <summary>
71         // Gets the PropertyValue at the specified index
72         // </summary>
73         // <param name="index">Index to look up</param>
74         // <returns>PropertyValue at the specified index</returns>
75         public override PropertyValue this[int index] {
76             get {
77                 VerifyExistingIndex(index);
78                 return _values[index].PropertyValue;
79             }
80         }
81
82         // <summary>
83         // Adds the specified object to this collection, returning its wrapped version
84         // </summary>
85         // <param name="value">Value to add and wrap in PropertyValue</param>
86         // <returns>Wrapped value</returns>
87         public override PropertyValue Add(object value) 
88         {
89             return Insert(value, this.Count);
90         }
91
92         // <summary>
93         // Inserts the specified object into this collection, returning its wrapped version
94         // </summary>
95         // <param name="value">Value to insert and wrap</param>
96         // <param name="index">Index to insert at</param>
97         // <returns>Wrapped version of the inserted value</returns>
98         public override PropertyValue Insert(object value, int index) 
99         {
100             VerifyNewIndex(index);
101
102             if (_values == null)
103             {
104                 _values = new List<ModelPropertyIndexer>();
105             }
106
107             ModelItem item;
108             bool previouslyActive = SnoozeListeningToCollectionChanges();
109             try 
110             {
111                 item = GetRawCollection().Insert(index, value);
112             }
113             finally 
114             {
115                 StartListeningToCollectionChanges(previouslyActive);
116             }
117
118             return InsertExternal(item, index);
119         }
120
121         // Same as Insert(), except it doesn't modify the raw collection, because it assumes
122         // that the raw collection was already modified externally.
123         private PropertyValue InsertExternal(ModelItem item, int index) 
124         {
125             if (_values == null)
126             {
127                 _values = new List<ModelPropertyIndexer>();
128             }
129
130             PropertyValue insertedValue = InsertHelper(item, index);
131
132             // Fire OnChanged event
133             this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, insertedValue, index));
134
135             return insertedValue;
136         }
137
138         // Updates internal structures, but does not fire any notification
139         private PropertyValue InsertHelper(ModelItem item, int index) 
140         {
141             // Only insert the value into the collection, if it's not already there.
142             // Say that an ItemAdded event comes in even though this collection has not yet been used.
143             // By requesting the instance of this collection for the first time, the collection
144             // gets populated correctly and fully.  Now when InsertExternal is called as a result
145             // of the ItemAdded event, we would be adding the new item into the collection twice.
146             // We need to prevent that from happening.
147             if (_values.Count > index &&
148                 object.Equals(_values[index].ModelItem, item))
149             {
150                 return _values[index].PropertyValue;
151             }
152
153             ModelPropertyIndexer indexer = CreateModelPropertyIndexer(item, index);
154             _values.Insert(index, indexer);
155
156             // Adjust all indexes of the remaining indexers in the list
157             for (int i = index + 1; i < _values.Count; i++)
158             {
159                 _values[i].Index++;
160             }
161
162             return indexer.PropertyValue;
163         }
164
165         // <summary>
166         // Removes the specified PropertyValue from the collection.
167         // </summary>
168         // <param name="property">Property to remove</param>
169         // <returns>True, if the PropertyValue was found and removed, false
170         // otherwise.</returns>
171         public override bool Remove(PropertyValue propertyValue) 
172         {
173             if (propertyValue == null)
174             {
175                 throw FxTrace.Exception.ArgumentNull("property");
176             }
177
178             if (_values == null)
179             {
180                 return false;
181             }
182
183             for (int i = 0; i < _values.Count; i++) 
184             {
185                 if (_values[i].PropertyValue == propertyValue) {
186                     this.RemoveAt(i);
187                     return true;
188                 }
189             }
190
191             // Call to RemoveAt() already fires the right CollectionChanged events
192             return false;
193         }
194
195         // Same as Remove, except it doesn't modify the raw collection, because it's
196         // assumed that the raw collection was already modified externally.
197         private bool RemoveExternal(ModelItem item) 
198         {
199             Fx.Assert(item != null, "item parameter should not be null");
200             Fx.Assert(_values != null, "_values parameter should not be null");
201
202             for (int i = 0; i < _values.Count; i++) 
203             {
204                 if (_values[i].ModelItem == item) {
205                     this.RemoveAtExternal(i);
206                     return true;
207                 }
208             }
209
210             return false;
211         }
212
213         // <summary>
214         // Removes the PropertyValue at the specified index.
215         // </summary>
216         // <param name="index">Index at which to remove the value.</param>
217         public override void RemoveAt(int index) 
218         {
219             VerifyExistingIndex(index);
220
221             bool previouslyActive = SnoozeListeningToCollectionChanges();
222             try 
223             {
224                 this.GetRawCollection().RemoveAt(index);
225             }
226             finally 
227             {
228                 StartListeningToCollectionChanges(previouslyActive);
229             }
230
231             RemoveAtExternal(index);
232         }
233
234         // Same as RemoveAt, except it doesn't modify the raw collection, because it's
235         // assumed that the raw collection was already modified externally.
236         private void RemoveAtExternal(int index) 
237         {
238             VerifyExistingIndex(index);
239             PropertyValue removedValue = RemoveAtHelper(index);
240
241             // Fire OnChanged event
242             this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedValue, index));
243         }
244
245         // Updates internal structures, but does not fire any notification
246         private PropertyValue RemoveAtHelper(int index) 
247         {
248             // invalidate the ModelPropertyEntryIndexer at the index and adjust all other indexes
249             ModelPropertyIndexer indexer = _values[index];
250             DestroyModelPropertyIndexer(indexer);
251
252             _values.RemoveAt(index);
253             for (int i = index; i < _values.Count; i++)
254             {
255                 _values[i].Index--;
256             }
257
258             return indexer.PropertyValue;
259         }
260
261         // Replaces the old ModelItem with the new one, assuming that the raw collection
262         // has already been verified
263         private void ReplaceExternal(ModelItem oldItem, ModelItem newItem) 
264         {
265             Fx.Assert(_values != null, "_values parameter should not be null");
266             Fx.Assert(oldItem != null, "oldItem parameter should not be null");
267             Fx.Assert(newItem != null, "newItem parameter should not be null");
268
269             for (int i = 0; i < _values.Count; i++) 
270             {
271                 if (_values[i].ModelItem == oldItem) {
272                     this.RemoveAtHelper(i);
273                     this.InsertHelper(newItem, i);
274
275                     // Fire OnChanged event
276                     this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, _values[i].PropertyValue));
277                     return;
278                 }
279             }
280
281             Debug.Fail("Didn't find the expected item to remove");
282         }
283
284         // Clears the collection, assuming that the raw collection was already cleared externally
285         private void ClearExternal() 
286         {
287             _values.Clear();
288             this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
289         }
290
291         // <summary>
292         // Swaps the items at the specified indexes
293         // </summary>
294         // <param name="currentIndex">Index of item 1</param>
295         // <param name="newIndex">Index of item 2</param>
296         public override void SetIndex(int currentIndex, int newIndex) 
297         {
298
299             VerifyExistingIndex(currentIndex);
300             VerifyExistingIndex(newIndex);
301
302             if (currentIndex == newIndex)
303             {
304                 return;
305             }
306
307             ModelItemCollection collection = this.GetRawCollection();
308             ModelPropertyIndexer currentIndexer = _values[currentIndex];
309             ModelPropertyIndexer newIndexer = _values[newIndex];
310
311             bool previouslyActive = SnoozeListeningToCollectionChanges();
312             try 
313             {
314                 // Remove the higher index first (doesn't affect the value of the lower index)
315                 if (currentIndex < newIndex) 
316                 {
317                     collection.RemoveAt(newIndex);
318                     collection.RemoveAt(currentIndex);
319                 }
320                 else 
321                 {
322                     collection.RemoveAt(currentIndex);
323                     collection.RemoveAt(newIndex);
324                 }
325
326                 // Insert the lower index first (fixes the value of the higher index)
327                 if (currentIndex < newIndex) 
328                 {
329                     collection.Insert(currentIndex, newIndexer.ModelItem);
330                     collection.Insert(newIndex, currentIndexer.ModelItem);
331                 }
332                 else 
333                 {
334                     collection.Insert(newIndex, currentIndexer.ModelItem);
335                     collection.Insert(currentIndex, newIndexer.ModelItem);
336                 }
337             }
338             finally 
339             {
340                 StartListeningToCollectionChanges(previouslyActive);
341             }
342
343             SetIndexExternal(currentIndex, newIndex);
344         }
345
346         // Same as SetIndex, except it doesn't modify the raw collection, because it's
347         // assumed that the raw collection was already modified externally.
348         private void SetIndexExternal(int currentIndex, int newIndex) 
349         {
350
351             if (currentIndex == newIndex)
352             {
353                 return;
354             }
355
356             ModelPropertyIndexer currentIndexer = _values[currentIndex];
357             ModelPropertyIndexer newIndexer = _values[newIndex];
358
359             // Remove the higher index first (doesn't affect the value of the lower index)
360             if (currentIndex < newIndex) 
361             {
362                 _values.RemoveAt(newIndex);
363                 _values.RemoveAt(currentIndex);
364             }
365             else 
366             {
367                 _values.RemoveAt(currentIndex);
368                 _values.RemoveAt(newIndex);
369             }
370
371             // Insert the lower index first (fixes the value of the higher index)
372             if (currentIndex < newIndex) 
373             {
374                 _values.Insert(currentIndex, newIndexer);
375                 _values.Insert(newIndex, currentIndexer);
376             }
377             else 
378             {
379                 _values.Insert(newIndex, currentIndexer);
380                 _values.Insert(currentIndex, newIndexer);
381             }
382
383             newIndexer.Index = currentIndex;
384             currentIndexer.Index = newIndex;
385
386             // Fire OnChanged event
387             this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(
388                 NotifyCollectionChangedAction.Move, currentIndexer.PropertyValue, newIndex, currentIndex));
389         }
390
391         // <summary>
392         // Gets the underlying ModelItemCollection
393         // </summary>
394         // <returns>The underlying ModelItemCollection</returns>
395         internal ModelItemCollection GetRawCollection() 
396         {
397             ModelPropertyEntry parentAsEntry = ParentValue.ParentProperty as ModelPropertyEntry;
398             if (parentAsEntry != null)
399             {
400                 return parentAsEntry.FirstModelProperty.Collection;
401             }
402
403             ModelPropertyIndexer parentAsIndexer = ParentValue.ParentProperty as ModelPropertyIndexer;
404             if (parentAsIndexer != null) 
405             {
406                 ModelItemCollection modelItemCollection = parentAsIndexer.ModelItem as ModelItemCollection;
407
408                 // If the parent is an indexer, that means we are a collection within another collection
409                 // and the ModelItem of the indexer is really a ModelItemCollection.
410                 Fx.Assert(modelItemCollection != null, "modelItemCollection should not be null");
411
412                 return modelItemCollection;
413             }
414
415             Debug.Fail("A new class was introduced that derives from PropertyEntry.  Need to update ModelPropertyValueCollection code as well.");
416             return null;
417         }
418
419         // <summary>
420         // Gets the enumerator over this collection
421         // </summary>
422         // <returns>Enumerator over this collection</returns>
423         public override IEnumerator<PropertyValue> GetEnumerator() 
424         {
425             if (_values == null)
426             {
427                 yield break;
428             }
429
430             foreach (ModelPropertyIndexer value in _values) 
431             {
432                 yield return value.PropertyValue;
433             }
434         }
435
436         // Handler for all collection changed events that happen through the model
437         private void OnUnderlyingCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
438         {
439             if (!_listenToCollectionChanges)
440             {
441                 return;
442             }
443
444             switch (e.Action) 
445             {
446                 case NotifyCollectionChangedAction.Add:
447                     int startingIndex = e.NewStartingIndex < 0 ? this.Count : e.NewStartingIndex;
448                     foreach (ModelItem item in e.NewItems) 
449                     {
450                         this.InsertExternal(item, startingIndex++);
451                     }
452                     break;
453
454                 case NotifyCollectionChangedAction.Remove:
455                     foreach (ModelItem item in e.OldItems) 
456                     {
457                         this.RemoveExternal(item);
458                     }
459                     break;
460
461                 case NotifyCollectionChangedAction.Move:
462                     int oldIndex = e.OldStartingIndex, newIndex = e.NewStartingIndex;
463                     for (int i = 0; i < e.OldItems.Count; i++) 
464                     {
465                         this.SetIndexExternal(oldIndex++, newIndex++);
466                     }
467
468                     break;
469
470                 case NotifyCollectionChangedAction.Replace:
471                     for (int i = 0; i < e.OldItems.Count; i++) 
472                     {
473                         ModelItem oldItem = e.OldItems[i] as ModelItem;
474                         ModelItem newItem = e.NewItems[i] as ModelItem;
475                         this.ReplaceExternal(oldItem, newItem);
476                     }
477                     break;
478
479                 case NotifyCollectionChangedAction.Reset:
480                     this.ClearExternal();
481                     break;
482             }
483         }
484
485         // Activates the CollectionChanged event handler
486         private void StartListeningToCollectionChanges(bool previousValue) 
487         {
488             _listenToCollectionChanges = previousValue;
489         }
490
491         // Suspends the CollectionChanged event handler
492         private bool SnoozeListeningToCollectionChanges() 
493         {
494             bool previousValue = _listenToCollectionChanges;
495             _listenToCollectionChanges = false;
496             return previousValue;
497         }
498
499         [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
500         private void VerifyExistingIndex(int index) 
501         {
502             if (_values == null || _values.Count <= index || index < 0)
503             {
504                 throw FxTrace.Exception.AsError(new IndexOutOfRangeException(index.ToString(CultureInfo.InvariantCulture)));
505             }
506         }
507
508         [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
509         private void VerifyNewIndex(int index) 
510         {
511             if ((_values == null && index != 0) ||
512                 (_values != null && _values.Count < index) ||
513                 index < 0)
514             {
515                 throw FxTrace.Exception.AsError(new IndexOutOfRangeException(index.ToString(CultureInfo.InvariantCulture)));
516             }
517         }
518
519         private ModelPropertyIndexer CreateModelPropertyIndexer(ModelItem item, int index) 
520         {
521             ModelPropertyIndexer indexer = new ModelPropertyIndexer(item, index, this);
522             return indexer;
523         }
524
525         private static void DestroyModelPropertyIndexer(ModelPropertyIndexer indexer) 
526         {
527             indexer.Index = -1;
528         }
529     }
530 }