[reflection] Coop handles icalls in System.Reflection and System.RuntimeTypeHandle...
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / Base / Core / Internal / PropertyEditing / Editors / SubPropertyEditor.cs
1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //----------------------------------------------------------------
4 namespace System.Activities.Presentation.Internal.PropertyEditing.Editors 
5 {
6     using System;
7     using System.Collections.Generic;
8     using System.Collections.ObjectModel;
9     using System.ComponentModel;
10     using System.Globalization;
11     using System.Windows;
12     using System.Windows.Automation.Peers;
13     using System.Windows.Controls;
14     using System.Windows.Data;
15
16     using System.Runtime;
17     using System.Activities.Presentation.Model;
18     using System.Activities.Presentation.PropertyEditing;
19
20     using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework.PropertyInspector;
21
22     using System.Activities.Presentation.Internal.PropertyEditing.Automation;
23     using System.Activities.Presentation.Internal.PropertyEditing.Model;
24     using ModelUtilities = System.Activities.Presentation.Internal.PropertyEditing.Model.ModelUtilities;
25     using System.Activities.Presentation.Internal.PropertyEditing.Resources;
26     using System.Activities.Presentation.Internal.PropertyEditing.Selection;
27     using System.Activities.Presentation.Internal.PropertyEditing.State;
28
29     // <summary>
30     // We use the SubPropertyEditor to replace the entire property row
31     // when the property exposes its subproperties.  This control is _not_
32     // just used within the value-editing portion of a property row.
33     // We cheat because we can.
34     // </summary>
35     internal class SubPropertyEditor : Control, INotifyPropertyChanged, ISelectionStop 
36     {
37
38         // <summary>
39         // PropertyEntry is used to store the currently displayed PropertyEntry
40         // </summary>
41         public static readonly DependencyProperty PropertyEntryProperty = DependencyProperty.Register(
42             "PropertyEntry",
43             typeof(PropertyEntry),
44             typeof(SubPropertyEditor),
45             new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyEntryChanged)));
46
47         // <summary>
48         // Boolean used to indicate whether the sub-properties are being shown or not.  As an optimization,
49         // we don't actually expose the PropertyValue's sub-properties through SelectiveSubProperties until
50         // the sub-property expando-pane has been open at least once.
51         // </summary>
52         public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(
53             "IsExpanded",
54             typeof(bool),
55             typeof(SubPropertyEditor),
56             new PropertyMetadata(false, new PropertyChangedCallback(OnIsExpandedChanged)));
57
58         // <summary>
59         // Exposes the currently selected QuickType in the QuickType drop-down.  Essentially,
60         // the value of this DP is plumbed through to reflect the value of _quickTypeView.CurrentItem
61         // </summary>
62         public static readonly DependencyProperty CurrentQuickTypeProperty = DependencyProperty.Register(
63             "CurrentQuickType",
64             typeof(NewItemFactoryTypeModel),
65             typeof(SubPropertyEditor),
66             new PropertyMetadata(null, new PropertyChangedCallback(OnCurrentQuickTypeChanged)));
67
68         private ICollectionView _quickTypeView;
69         private ObservableCollection<NewItemFactoryTypeModel> _quickTypeCollection;
70         private bool _ignoreInternalChanges;
71         private bool _exposedSubProperties;
72
73         private ItemsControl _subPropertyListControl;
74
75         // <summary>
76         // Basic ctor
77         // </summary>
78         public SubPropertyEditor() 
79         {
80             _quickTypeCollection = new ObservableCollection<NewItemFactoryTypeModel>();
81
82             _quickTypeView = CollectionViewSource.GetDefaultView(_quickTypeCollection);
83             _quickTypeView.CurrentChanged += new EventHandler(OnCurrentQuickTypeChanged);
84         }
85
86         // Automation
87
88         public event PropertyChangedEventHandler PropertyChanged;
89
90         // Internal event we fire for the sake of SubPropertyEditorAutomationPeer that
91         // causes it to refresh its offered set of children
92         internal event EventHandler VisualsChanged;
93
94         public PropertyEntry PropertyEntry 
95         {
96             get { return (PropertyEntry)this.GetValue(PropertyEntryProperty); }
97             set { this.SetValue(PropertyEntryProperty, value); }
98         }
99
100         public bool IsExpanded 
101         {
102             get { return (bool)this.GetValue(IsExpandedProperty); }
103             set { this.SetValue(IsExpandedProperty, value); }
104         }
105
106         public NewItemFactoryTypeModel CurrentQuickType 
107         {
108             get { return (NewItemFactoryTypeModel)this.GetValue(CurrentQuickTypeProperty); }
109             set { this.SetValue(CurrentQuickTypeProperty, value); }
110         }
111
112         // <summary>
113         // Gets a flag indicating whether QuickTypes exist
114         // </summary>
115         public bool HasQuickTypes 
116         {
117             get {
118                 return _quickTypeCollection.Count > 0;
119             }
120         }
121
122         // <summary>
123         // Returns a list of available QuickTypes (collection of NewItemFactoryTypeModel instances)
124         // </summary>
125         public ICollectionView QuickTypes 
126         {
127             get {
128                 return _quickTypeView;
129             }
130         }
131
132         // <summary>
133         // Exposes PropertyValue.SubProperties when the IsExpanded flag first gets set to true
134         // and forever thereafter (or at least until the current PropertyValue changes and we
135         // collapse the sub-properties)
136         // </summary>
137         public IEnumerable<PropertyEntry> SelectiveSubProperties 
138         {
139             get {
140                 if (!_exposedSubProperties) 
141                 {
142                     if (!this.IsExpanded)
143                     {
144                         yield break;
145                     }
146
147                     _exposedSubProperties = true;
148                 }
149
150                 PropertyEntry parent = this.PropertyEntry;
151                 if (parent == null)
152                 {
153                     yield break;
154                 }
155
156                 foreach (ModelPropertyEntry subProperty in parent.PropertyValue.SubProperties)
157                 {
158                     if (subProperty.IsBrowsable)
159                     {
160                         yield return subProperty;
161                     }
162                 }
163             }
164         }
165
166         // <summary>
167         // Gets a flag indicating whether the sub-property editor can be expanded or not.
168         // </summary>
169         public bool IsExpandable 
170         {
171             get { return this.HasQuickTypes && this.CurrentQuickType != null; }
172         }
173
174         // <summary>
175         // Gets a SelectionPath to itself.
176         // </summary>
177         public SelectionPath Path 
178         {
179             get { return PropertySelectionPathInterpreter.Instance.ConstructSelectionPath(this.PropertyEntry); }
180         }
181
182         // <summary>
183         // Gets a description of the contained property
184         // to expose through automation
185         // </summary>
186         public string Description 
187         {
188             get {
189                 PropertyEntry property = this.PropertyEntry;
190                 if (property != null) 
191                 {
192                     return string.Format(
193                         CultureInfo.CurrentCulture,
194                         Properties.Resources.PropertyEditing_SelectionStatus_Property,
195                         this.PropertyEntry.PropertyName);
196                 }
197
198                 return string.Empty;
199             }
200         }
201
202
203         // <summary>
204         // Exposes the ItemsControl used to display the list of sub-properties.  UI-specific
205         // </summary>
206         private ItemsControl SubPropertyListControl 
207         {
208             get {
209                 if (_subPropertyListControl == null) 
210                 {
211                     _subPropertyListControl = VisualTreeUtils.GetNamedChild<ItemsControl>(this, "PART_SubPropertyList");
212                     Fx.Assert(_subPropertyListControl != null, "UI for SubPropertyEditor changed.  Need to update SubPropertyEditor class logic.");
213                 }
214
215                 return _subPropertyListControl;
216             }
217         }
218
219
220         // Keyboard Navigation
221
222         protected override AutomationPeer OnCreateAutomationPeer() 
223         {
224             return new SubPropertyEditorAutomationPeer(this);
225         }
226
227
228         // Properties
229
230         // PropertyEntry DP
231
232         // When the displayed PropertyEntry changes, make sure we update the UI and hook into the
233         // new PropertyEntry's notification mechanism
234         private static void OnPropertyEntryChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
235         {
236             SubPropertyEditor theThis = obj as SubPropertyEditor;
237             if (theThis == null)
238             {
239                 return;
240             }
241
242             PropertyEntry oldValue = e.OldValue as PropertyEntry;
243             if (oldValue != null) 
244             {
245                 oldValue.PropertyValue.RootValueChanged -= new EventHandler(theThis.OnPropertyValueRootValueChanged);
246             }
247
248             PropertyEntry newValue = e.NewValue as PropertyEntry;
249             if (newValue != null) 
250             {
251                 newValue.PropertyValue.RootValueChanged += new EventHandler(theThis.OnPropertyValueRootValueChanged);
252             }
253
254             theThis.RefreshVisuals();
255         }
256
257         private void OnPropertyValueRootValueChanged(object sender, EventArgs e) 
258         {
259             if (_ignoreInternalChanges)
260             {
261                 return;
262             }
263
264             RefreshVisuals();
265         }
266
267
268         // IsExpanded DP
269
270         private static void OnIsExpandedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
271         {
272             SubPropertyEditor theThis = obj as SubPropertyEditor;
273             if (theThis == null)
274             {
275                 return;
276             }
277
278             bool newIsExpanded = (bool)e.NewValue;
279             PropertyEntry containedProperty = theThis.PropertyEntry;
280
281             // Store the new expansion state
282             if (containedProperty != null) 
283             {
284                 PropertyState state = PropertyStateContainer.Instance.GetPropertyState(
285                     ModelUtilities.GetCachedSubPropertyHierarchyPath(containedProperty));
286                 state.SubPropertiesExpanded = newIsExpanded;
287             }
288
289             // If we are expanded but we never exposed the sub-properties to anyone before,
290             // fire a signal saying that a list of sub-properties may be now available, so that
291             // UI DataBindings refresh themselves
292             if (newIsExpanded == true &&
293                 theThis._exposedSubProperties == false) 
294             {
295                 theThis.FireSubPropertiesListChangedEvents();
296             }
297         }
298
299         // CurrentQuickType DP
300
301         // This method gets called when the DP changes
302         private static void OnCurrentQuickTypeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
303         {
304             SubPropertyEditor theThis = obj as SubPropertyEditor;
305             if (theThis == null)
306             {
307                 return;
308             }
309
310             if (theThis._ignoreInternalChanges)
311             {
312                 return;
313             }
314
315             theThis._quickTypeView.MoveCurrentTo(e.NewValue);
316
317             theThis.ExpandSubProperties();
318             theThis.FireSubPropertiesListChangedEvents();
319         }
320
321         // This method gets called when the CurrentItem on _quickTypeView changes
322         private void OnCurrentQuickTypeChanged(object sender, EventArgs e) 
323         {
324             if (_ignoreInternalChanges)
325             {
326                 return;
327             }
328
329             NewItemFactoryTypeModel selectedTypeModel = _quickTypeView.CurrentItem as NewItemFactoryTypeModel;
330
331             if (selectedTypeModel == null)
332             {
333                 return;
334             }
335
336             Fx.Assert(this.PropertyEntry != null, "PropertyEntry should not be null");
337             if (this.PropertyEntry == null)
338             {
339                 return;
340             }
341
342             bool previousValue = IgnoreInternalChanges();
343             try 
344             {
345                 this.PropertyEntry.PropertyValue.Value = selectedTypeModel.CreateInstance();
346             }
347             finally 
348             {
349                 NoticeInternalChanges(previousValue);
350             }
351         }
352
353
354         protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) 
355         {
356             if (e.Property == PropertyContainer.OwningPropertyContainerProperty) 
357             {
358
359                 // A quick and dirty way to register this instance as the implementation of 
360                 // ISelectionBranchPoint that controls the expansion / collapse of this control
361                 //
362                 OnOwningPropertyContainerChanged((PropertyContainer)e.OldValue, (PropertyContainer)e.NewValue);
363
364             }
365
366             base.OnPropertyChanged(e);
367         }
368
369         private void OnOwningPropertyContainerChanged(PropertyContainer oldValue, PropertyContainer newValue) 
370         {
371             if (oldValue != null) 
372             {
373                 PropertySelection.ClearSelectionStop(oldValue);
374                 PropertySelection.ClearIsSelectionStopDoubleClickTarget(oldValue);
375             }
376
377             if (newValue != null) 
378             {
379                 PropertySelection.SetSelectionStop(newValue, this);
380                 PropertySelection.SetIsSelectionStopDoubleClickTarget(newValue, true);
381             }
382         }
383
384
385         // Visual Lookup Helpers
386
387         // <summary>
388         // Looks for and returns the specified sub-property
389         // </summary>
390         // <param name="propertyName">Sub-property to look up</param>
391         // <returns>Corresponding PropertyEntry if found, null otherwise.</returns>
392         internal PropertyEntry FindSubPropertyEntry(string propertyName) 
393         {
394             if (string.IsNullOrEmpty(propertyName)) 
395             {
396                 return null;
397             }
398
399             foreach (PropertyEntry property in SelectiveSubProperties)
400             {
401                 if (property.PropertyName.Equals(propertyName))
402                 {
403                     return property;
404                 }
405             }
406
407             return null;
408         }
409
410         // <summary>
411         // Looks for and returns the PropertyContainer used to display
412         // the specified PropertyEntry
413         // </summary>
414         // <param name="property">Property to look for</param>
415         // <returns>Corresponding PropertyContainer if found, null otherwise.</returns>
416         internal PropertyContainer FindSubPropertyEntryVisual(PropertyEntry property) 
417         {
418             if (property == null) 
419             {
420                 return null;
421             }
422
423             ItemsControl subPropertyListControl = this.SubPropertyListControl;
424             if (subPropertyListControl == null)
425             {
426                 return null;
427             }
428
429             return subPropertyListControl.ItemContainerGenerator.ContainerFromItem(property) as PropertyContainer;
430         }
431
432
433         // Helpers
434
435         private void RefreshVisuals() 
436         {
437             RefreshQuickTypes();
438             RestoreIsExpandedState();
439             FireVisualsChangedEvents();
440         }
441
442         private void RefreshQuickTypes() 
443         {
444             bool previousValue = IgnoreInternalChanges();
445             try 
446             {
447                 _quickTypeCollection.Clear();
448
449                 PropertyEntry containedProperty = this.PropertyEntry;
450                 if (containedProperty == null)
451                 {
452                     return;
453                 }
454
455                 ModelProperty property = ((ModelPropertyEntry)containedProperty).FirstModelProperty;
456                 Type containerValueType = ((ModelPropertyEntryBase)containedProperty).CommonValueType;
457                 NewItemFactoryTypeModel selectedFactoryModel = null;
458                 Type defaultItemType = GetDefaultItemType(property);
459
460                 // Find all elligible NewItemFactoryTypes declared through metadata
461                 IEnumerable<NewItemFactoryTypeModel> factoryModels =
462                     ExtensibilityAccessor.GetNewItemFactoryTypeModels(
463                     property,
464                     ResourceUtilities.GetDesiredTypeIconSize(this));
465
466                 if (factoryModels != null) 
467                 {
468                     foreach (NewItemFactoryTypeModel factoryModel in factoryModels) 
469                     {
470                         _quickTypeCollection.Add(factoryModel);
471
472                         if (selectedFactoryModel == null) 
473                         {
474                             if (object.Equals(containerValueType, factoryModel.Type)) 
475                             {
476                                 selectedFactoryModel = factoryModel;
477                             }
478                         }
479
480                         if (defaultItemType != null &&
481                             object.Equals(defaultItemType, factoryModel.Type)) 
482                         {
483                             defaultItemType = null;
484                         }
485                     }
486                 }
487
488                 //add a null value - user should always have an option to clear property value
489                 NewItemFactoryTypeModel nullTypeFactoryTypeModel =
490                     new NewItemFactoryTypeModel(null, new NullItemFactory());
491
492                 // Add a default item type based on the property type (if it wasn't also added through
493                 // metadata)
494                 if (defaultItemType != null) 
495                 {
496                     NewItemFactoryTypeModel defaultItemFactoryTypeModel = new NewItemFactoryTypeModel(defaultItemType, new NewItemFactory());
497                     _quickTypeCollection.Add(defaultItemFactoryTypeModel);
498
499                     if (selectedFactoryModel == null) 
500                     {
501                         if (object.Equals(containerValueType, defaultItemFactoryTypeModel.Type)) 
502                         {
503                             selectedFactoryModel = defaultItemFactoryTypeModel;
504                         }
505                         else if (containerValueType == null)
506                         {
507                             selectedFactoryModel = nullTypeFactoryTypeModel;
508                         }
509                     }
510                 }
511                 
512                 _quickTypeCollection.Add(nullTypeFactoryTypeModel);
513
514                 // Make sure the currently selected value on the CollectionView reflects the
515                 // actual value of the property
516                 _quickTypeView.MoveCurrentTo(selectedFactoryModel);
517                 this.CurrentQuickType = selectedFactoryModel;
518             }
519             finally 
520             {
521                 NoticeInternalChanges(previousValue);
522             }
523         }
524
525         private static Type GetDefaultItemType(ModelProperty property) 
526         {
527             if (property == null)
528             {
529                 return null;
530             }
531
532             Type propertyType = property.PropertyType;
533             if (EditorUtilities.IsConcreteWithDefaultCtor(propertyType))
534             {
535                 return propertyType;
536             }
537
538             return null;
539         }
540
541         private void RestoreIsExpandedState() 
542         {
543             bool newIsExpanded = false;
544             PropertyEntry property = this.PropertyEntry;
545
546             if (property != null) 
547             {
548                 PropertyState state = PropertyStateContainer.Instance.GetPropertyState(
549                     ModelUtilities.GetCachedSubPropertyHierarchyPath(property));
550                 newIsExpanded = state.SubPropertiesExpanded;
551             }
552
553             this.IsExpanded = newIsExpanded;
554             _exposedSubProperties = false;
555         }
556
557         private void ExpandSubProperties() 
558         {
559             this.IsExpanded = true;
560         }
561
562
563         // Change Notification Helpers
564
565         private bool IgnoreInternalChanges() 
566         {
567             bool previousValue = _ignoreInternalChanges;
568             _ignoreInternalChanges = true;
569             return previousValue;
570         }
571
572         private void NoticeInternalChanges(bool previousValue) 
573         {
574             _ignoreInternalChanges = previousValue;
575         }
576
577         private void FireVisualsChangedEvents() 
578         {
579             // Fire updated events
580             OnPropertyChanged("HasQuickTypes");
581             OnPropertyChanged("QuickTypes");
582             FireSubPropertiesListChangedEvents();
583         }
584
585         private void FireSubPropertiesListChangedEvents() 
586         {
587             OnPropertyChanged("IsExpandable");
588             OnPropertyChanged("SelectiveSubProperties");
589
590             if (VisualsChanged != null)
591             {
592                 VisualsChanged(this, EventArgs.Empty);
593             }
594         }
595
596
597         // INotifyPropertyChanged Members
598
599         private void OnPropertyChanged(string propertyName) 
600         {
601             Fx.Assert(!string.IsNullOrEmpty(propertyName), "Can't raise OnPropertyChanged event without a valid property name.");
602
603             if (PropertyChanged != null)
604             {
605                 this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
606             }
607         }
608
609         // ISelectionStop Members
610
611         //NullItemFactory - this class is used to provide a null entry in quick types list - it is required to allow user 
612         //to clear value of an object.
613         sealed class NullItemFactory : NewItemFactory
614         {
615             public override object CreateInstance(Type type)
616             {
617                 //no input type is allowed - we never create instance of anything
618                 Fx.Assert(type == null, "NullItemFactory supports only null as type parameter");
619                 return null;
620             }
621
622             public override string GetDisplayName(Type type)
623             {
624                 //no input type is allowed - we always return (null) string
625                 Fx.Assert(type == null, "NullItemFactory supports only null as type parameter");
626                 return "(null)";
627             }
628         }
629     }
630 }