b8535a217f5af28ee4c2b122ff97e4ed398974c2
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / Base / Core / Internal / PropertyEditing / Model / ModelCategoryEntry.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.ObjectModel;
9     using System.Collections.Specialized;
10     using System.Diagnostics;
11     using System.Activities.Presentation.PropertyEditing;
12     using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework.Data;
13     using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework.PropertyInspector;
14     using System.Activities.Presentation;
15
16     // <summary>
17     // Cider's concrete implementation of CategoryEntry (CategoryBase comes from Sparkle
18     // and it has a few extra goodies that we want to reuse).  This class implements
19     // INotifyCollectionChanged.  We need to push this implementation to the base class
20     // in v2.
21     // </summary>
22     internal class ModelCategoryEntry : CategoryBase, INotifyCollectionChanged 
23     {
24
25         private ObservableCollectionWorkaround<PropertyEntry> _basicProperties;
26         private ObservableCollectionWorkaround<PropertyEntry> _advancedProperties;
27
28         // <summary>
29         // Basic ctor
30         // </summary>
31         // <param name="categoryName">Localized name for this category</param>
32         public ModelCategoryEntry(string categoryName) : base(categoryName) 
33         {
34             _basicProperties = new ObservableCollectionWorkaround<PropertyEntry>();
35             _advancedProperties = new ObservableCollectionWorkaround<PropertyEntry>();
36         }
37
38         public event NotifyCollectionChangedEventHandler CollectionChanged;
39
40         // <summary>
41         // Gets the advanced properties contained in this category
42         // </summary>
43         public override ObservableCollection<PropertyEntry> AdvancedProperties 
44         {
45             get {
46                 return _advancedProperties;
47             }
48         }
49
50         // <summary>
51         // Gets the basic properties contained in this category
52         // </summary>
53         public override ObservableCollection<PropertyEntry> BasicProperties 
54         {
55             get {
56                 return _basicProperties;
57             }
58         }
59
60         // <summary>
61         // Gets a flag indicating whether this category contains any properties
62         // </summary>
63         internal bool IsEmpty 
64         {
65             get {
66                 return _advancedProperties.Count + _basicProperties.Count == 0;
67             }
68         }
69
70         // <summary>
71         // Returns either the basic or the advanced bucket based on the IsAdvanced flag
72         // set in the PropertyEntry itself
73         // </summary>
74         // <param name="property">Property to examine</param>
75         // <returns>The corresponding basic or advanced bucket</returns>
76         internal ObservableCollectionWorkaround<PropertyEntry> GetBucket(PropertyEntry property) 
77         {
78             if (property == null) 
79             {
80                 throw FxTrace.Exception.ArgumentNull("property");
81             }
82             return property.IsAdvanced ? _advancedProperties : _basicProperties;
83         }
84
85         // <summary>
86         // Adds the given property to the specified property bucket (use
87         // ModelCategoryEntry.BasicProperties, ModelCategoryEntry.AdvancedProperties, or
88         // ModelCategoryEntry.GetBucket()) sorted using the specified comparer.
89         // </summary>
90         // <param name="property">Property to add</param>
91         // <param name="bucket">Property bucket to populate</param>
92         // <param name="comparer">Sort algorithm to use</param>
93         // <param name="fireCollectionChangedEvent">If set to true, NotifyCollectionChanged event is fired</param>
94         internal void Add(
95             PropertyEntry property,
96             ObservableCollection<PropertyEntry> bucket,
97             IComparer<PropertyEntry> comparer) 
98         {
99             Add(property, bucket, comparer, true);
100         }
101
102         //
103         // Adds the given property to the specified property bucket (use
104         // ModelCategoryEntry.BasicProperties, ModelCategoryEntry.AdvancedProperties, or
105         // ModelCategoryEntry.GetBucket()) sorted using the specified comparer.
106         //
107         private void Add(
108             PropertyEntry property,
109             ObservableCollection<PropertyEntry> bucket,
110             IComparer<PropertyEntry> comparer,
111             bool fireCollectionChangedEvent) 
112         {
113
114             if (property == null) 
115             {
116                 throw FxTrace.Exception.ArgumentNull("property");
117             }
118             if (bucket == null) 
119             {
120                 throw FxTrace.Exception.ArgumentNull("bucket");
121             }
122             if (comparer == null) 
123             {
124                 throw FxTrace.Exception.ArgumentNull("comparer");
125             }
126
127             ObservableCollectionWorkaround<PropertyEntry> castBucket = bucket as ObservableCollectionWorkaround<PropertyEntry>;
128             int insertionIndex = 0;
129
130             if (castBucket == null) 
131             {
132                 Debug.Fail("Invalid property bucket.  The property sort order will be broken.");
133             }
134             else 
135             {
136                 insertionIndex = castBucket.BinarySearch(property, comparer);
137                 if (insertionIndex < 0) 
138                 {
139                     insertionIndex = ~insertionIndex;
140                 }
141             }
142
143             bucket.Insert(insertionIndex, property);
144
145             if (fireCollectionChangedEvent)
146             {
147                 FirePropertiesChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property));
148             }
149         }
150
151         // <summary>
152         // Removes and re-adds the specified property from this category, if it existed
153         // there to begin with.  Noop otherwise.
154         //
155         // Use this method to refresh the cate----zation of a property if it suddenly
156         // becomes Advanced if it was Basic before, or if its IsBrowsable status changes.
157         // </summary>
158         // <param name="property">Property to refresh</param>
159         // <param name="bucket">Property bucket to repopulate</param>
160         // <param name="sortComparer">Comparer to use to reinsert the given property in its new place</param>
161         internal void Refresh(ModelPropertyEntry property, ObservableCollection<PropertyEntry> bucket, IComparer<PropertyEntry> sortComparer) 
162         {
163             if (property == null) 
164             {
165                 throw FxTrace.Exception.ArgumentNull("property");
166             }
167             if (bucket != _basicProperties && bucket != _advancedProperties) 
168             {
169                 Debug.Fail("Invalid bucket specified.  Property was not refreshed.");
170                 return;
171             }
172
173             // Let's see if we know about this property
174             ObservableCollectionWorkaround<PropertyEntry> collection;
175             collection = _advancedProperties;
176
177             int index = collection.BinarySearch(property, null);
178             if (index < 0) 
179             {
180                 collection = _basicProperties;
181                 index = collection.BinarySearch(property, null);
182             }
183
184             // If not, noop
185             if (index < 0)
186             {
187                 return;
188             }
189
190             // We know about this property, so refresh it.  It may have changed
191             // somehow (eg. switched from basic to advanced, become hidden, etc.)
192             // so make sure it's thrown into the right bucket.
193             collection.RemoveAt(index);
194             Add(property, bucket, sortComparer, false);
195         }
196
197         // <summary>
198         // This is a work-around fix because Blend's CategoryBase does not handle null filters (valid value)
199         // correctly.  We need to ask Blend to eventually fix this issue.
200         // </summary>
201         // <param name="filter">Filter to apply, can be null</param>
202         public override void ApplyFilter(PropertyFilter filter) 
203         {
204             if (filter == null) 
205             {
206                 this.MatchesFilter = true;
207                 this.BasicPropertyMatchesFilter = true;
208                 this.AdvancedPropertyMatchesFilter = true;
209
210                 foreach (PropertyEntry property in this.BasicProperties)
211                 {
212                     property.ApplyFilter(filter);
213                 }
214
215                 foreach (PropertyEntry property in this.AdvancedProperties)
216                 {
217                     property.ApplyFilter(filter);
218                 }
219             }
220             else 
221             {
222                 base.ApplyFilter(filter);
223             }
224         }
225
226         // Another Blend work-around - we expose all properties through the OM, not just the
227         // Browsable ones.  However, as a result, we need to cull the non-browsable ones from
228         // consideration.  Otherwise, empty categories may appear.
229         protected override bool DoesPropertyMatchFilter(PropertyFilter filter, PropertyEntry property) 
230         {
231             property.ApplyFilter(filter);
232
233             bool isBrowsable = true;
234             ModelPropertyEntry modelPropertyEntry = property as ModelPropertyEntry;
235             if (modelPropertyEntry != null)
236             {
237                 //display given property if it is browsable or
238                 isBrowsable = modelPropertyEntry.IsBrowsable || 
239                     // it may not be browsable, but if there is a category editor associated - display it anyway
240                     (this.CategoryEditors != null && this.CategoryEditors.Count != 0);
241             }
242
243             return isBrowsable && property.MatchesFilter;
244         }
245
246         // <summary>
247         // Sets the Disassociated flag on all contained properties to True
248         // </summary>
249         internal void MarkAllPropertiesDisassociated() 
250         {
251             MarkAllPropertiesDisassociated(_basicProperties);
252             MarkAllPropertiesDisassociated(_advancedProperties);
253         }
254
255         // <summary>
256         // Sets the Disassociated flag on all contained attached properties to True
257         // </summary>
258         internal void MarkAttachedPropertiesDisassociated() 
259         {
260             MarkAttachedPropertiesDisassociated(_basicProperties);
261             MarkAttachedPropertiesDisassociated(_advancedProperties);
262         }
263
264         // <summary>
265         // Removes all properties from this category whose Disassociated flag is set to True
266         // </summary>
267         internal void CullDisassociatedProperties() 
268         {
269             bool propertiesCulled = false;
270             propertiesCulled |= CullDisassociatedProperties(_basicProperties);
271             propertiesCulled |= CullDisassociatedProperties(_advancedProperties);
272
273             if (propertiesCulled)
274             {
275                 FirePropertiesChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
276             }
277         }
278
279         private static void MarkAllPropertiesDisassociated(ObservableCollectionWorkaround<PropertyEntry> propertyList) 
280         {
281             foreach (ModelPropertyEntry property in propertyList)
282             {
283                 property.Disassociated = true;
284             }
285         }
286
287         private static void MarkAttachedPropertiesDisassociated(ObservableCollectionWorkaround<PropertyEntry> propertyList) 
288         {
289             foreach (ModelPropertyEntry property in propertyList)
290             {
291                 if (property.IsAttached)
292                 {
293                     property.Disassociated = true;
294                 }
295             }
296         }
297
298         private static bool CullDisassociatedProperties(ObservableCollectionWorkaround<PropertyEntry> propertyList) 
299         {
300             bool propertiesCulled = false;
301             for (int i = propertyList.Count - 1; i >= 0; i--) 
302             {
303                 ModelPropertyEntry property = (ModelPropertyEntry)propertyList[i];
304                 if (property.Disassociated) 
305                 {
306                     property.Disconnect();
307                     propertyList.RemoveAt(i);
308                     propertiesCulled = true;
309                 }
310             }
311
312             return propertiesCulled;
313         }
314
315         // INotifyCollectionChanged Members
316
317         private void FirePropertiesChanged(NotifyCollectionChangedEventArgs collectionChangedEventArgs) 
318         {
319             // Fire both "Properties" changed events
320             OnPropertyChanged("Properties");
321             OnPropertyChanged("Item[]");
322
323             // as well as the appropriate collection-changed event
324             if (CollectionChanged != null)
325             {
326                 CollectionChanged(this, collectionChangedEventArgs);
327             }
328         }
329
330     }
331 }