[corlib] Avoid unnecessary ephemeron array resizes
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / WorkflowDesigner.cs
1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //----------------------------------------------------------------
4
5 namespace System.Activities.Presentation
6 {
7     using System;
8     using System.Activities.Debugger;
9     using System.Activities.Debugger.Symbol;
10     using System.Activities.Presentation.Debug;
11     using System.Activities.Presentation.Documents;
12     using System.Activities.Presentation.Hosting;
13     using System.Activities.Presentation.Internal.PropertyEditing;
14     using System.Activities.Presentation.Internal.PropertyEditing.Metadata;
15     using System.Activities.Presentation.Metadata;
16     using System.Activities.Presentation.Model;
17     using System.Activities.Presentation.Services;
18     using System.Activities.Presentation.Sqm;
19     using System.Activities.Presentation.Validation;
20     using System.Activities.Presentation.View;
21     using System.Activities.Presentation.View.TreeView;
22     using System.Activities.Presentation.Xaml;
23     using System.Collections;
24     using System.Collections.Generic;
25     using System.Diagnostics.CodeAnalysis;
26     using System.Globalization;
27     using System.IO;
28     using System.Runtime;
29     using System.Runtime.Versioning;
30     using System.Security;
31     using System.Security.Policy;
32     using System.Text;
33     using System.Windows;
34     using System.Windows.Controls;
35     using System.Windows.Input;
36     using System.Windows.Interop;
37     using System.Windows.Threading;
38     using System.Xml;
39     using Microsoft.Activities.Presentation;
40     using Microsoft.Activities.Presentation.Xaml;
41
42     // This is the workflow designer context class.
43     // it provides two views the primary workflow view in View property and the property browser view in the
44     // propertyInspectorView property.
45     // Load takes a objects instance or Xaml ( in the future) to load the designer from
46     public partial class WorkflowDesigner
47     {
48         EditingContext context;
49         ModelTreeManager modelTreeManager;
50         Grid view;
51         Grid outlineView;
52         PropertyInspector propertyInspector;
53         string text;
54         ViewStateIdManager idManager;
55         string loadedFile;
56         DebuggerService debuggerService;
57         UndoEngine undoEngine;
58         ViewManager viewManager;
59         ValidationService validationService;
60         ObjectReferenceService objectReferenceService;
61         DesignerPerfEventProvider perfEventProvider;
62         bool isLoaded = false;
63         bool isModelChanged = false;
64
65         IXamlLoadErrorService xamlLoadErrorService;
66         WorkflowDesignerXamlSchemaContext workflowDesignerXamlSchemaContext;
67
68         public event TextChangedEventHandler TextChanged;
69         public event EventHandler ModelChanged;
70         WorkflowSymbol lastWorkflowSymbol;
71         ObjectToSourceLocationMapping objectToSourceLocationMapping;
72
73         internal class PreviewLoadEventArgs : EventArgs
74         {
75             object instance;
76             EditingContext context;
77
78             public PreviewLoadEventArgs(object instance, EditingContext context)
79             {
80                 this.instance = instance;
81                 this.context = context;
82             }
83
84             public object Instance
85             {
86                 get { return this.instance; }
87             }
88
89             public EditingContext Context
90             {
91                 get { return this.context; }
92             }
93         }
94         internal event EventHandler<PreviewLoadEventArgs> PreviewLoad;
95
96         [SuppressMessage(FxCop.Category.Performance, FxCop.Rule.InitializeReferenceTypeStaticFieldsInline,
97             Justification = "The static constructor is required to initialize the PropertyInspector metadata.")]
98         static WorkflowDesigner()
99         {
100             InitializePropertyInspectorMetadata();
101             DesignerMetadata metaData = new DesignerMetadata();
102             metaData.Register();
103         }
104
105         public WorkflowDesigner()
106         {
107             // create our perf trace provider first
108             this.perfEventProvider = new DesignerPerfEventProvider();            
109             this.idManager = new ViewStateIdManager();
110             this.context = new EditingContext();
111             this.ModelSearchService = new ModelSearchServiceImpl(this);
112             this.context.Items.SetValue(new ReadOnlyState { IsReadOnly = false });
113             this.view = new Grid();
114             this.view.Focusable = false;
115             
116             //add the resource dictionary to application resource so every component could reference it
117             if (Application.Current == null)
118             {
119                 //create an application if it doesn't exist, make sure it will not shutdown after windows being shut down
120                 Application app = new Application();
121                 app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
122             }
123             Fx.Assert(Application.Current != null, "Application and resources must be there");
124             Application.Current.Resources.MergedDictionaries.Add(WorkflowDesignerColors.FontAndColorResources);
125             Application.Current.Resources.MergedDictionaries.Add(WorkflowDesignerIcons.IconResourceDictionary);
126             AttachedPropertiesService propertiesService = new AttachedPropertiesService();
127             this.context.Services.Publish(typeof(AttachedPropertiesService), propertiesService);
128
129             undoEngine = new UndoEngine(context);
130             this.context.Services.Publish(typeof(UndoEngine), undoEngine);
131             undoEngine.UndoCompleted += new EventHandler<UndoUnitEventArgs>(OnUndoCompleted);
132
133             this.context.Services.Publish<ValidationService>(this.ValidationService);
134             this.context.Services.Publish<ObjectReferenceService>(this.ObjectReferenceService);
135             this.context.Services.Publish<DesignerPerfEventProvider>(this.perfEventProvider);
136             this.context.Services.Publish<FeatureManager>(new FeatureManager(this.context));
137             this.context.Services.Publish<DesignerConfigurationService>(new DesignerConfigurationService());
138
139             this.context.Services.Subscribe<ICommandService>((s) =>
140             {
141                 const string addinTypeName = "Microsoft.VisualStudio.Activities.AddIn.WorkflowDesignerAddIn";
142                 if (s != null && s.GetType().FullName.Equals(addinTypeName))
143                 {
144                     DesignerConfigurationService service = this.context.Services.GetService<DesignerConfigurationService>();
145                     if (service != null)
146                     {
147                         service.WorkflowDesignerHostId = WorkflowDesignerHostId.Dev10;
148                     }
149                 }
150             });
151
152             this.context.Services.Subscribe<IVSSqmService>((service) =>
153             {
154                 const string serviceTypeName = "Microsoft.VisualStudio.Activities.AddIn.VSSqmService";
155                 if (service != null && service.GetType().FullName.Equals(serviceTypeName))
156                 {
157                     DesignerConfigurationService configurationService = this.context.Services.GetService<DesignerConfigurationService>();
158                     if (configurationService != null)
159                     {
160                         configurationService.WorkflowDesignerHostId = WorkflowDesignerHostId.Dev11;
161                     }
162                 }
163             });
164
165             this.Context.Items.Subscribe<ErrorItem>(delegate(ErrorItem errorItem)
166             {
167                 ErrorView errorView = new ErrorView();
168                 errorView.Message = errorItem.Message;
169                 errorView.Details = errorItem.Details;
170                 errorView.Context = this.Context;
171
172                 // Clear views
173                 this.view.Children.Clear();
174                 this.view.Children.Add(errorView);
175                 if (this.outlineView != null)
176                 {
177                     this.outlineView.Children.Clear();
178                 }
179             }
180                 );
181
182             this.context.Items.Subscribe<ReadOnlyState>(new SubscribeContextCallback<ReadOnlyState>(OnReadonlyStateChanged));
183
184             this.context.Services.Subscribe<IXamlLoadErrorService>(s => this.xamlLoadErrorService = s);
185
186             this.PreviewLoad += NamespaceSettingsHandler.PreviewLoadRoot;
187             this.view.Loaded += (s, e) =>
188             {
189                 //when view is loaded, check if user did provide his own WindowHelperService - if not, provide a default one
190                 if (!this.context.Services.Contains<WindowHelperService>())
191                 {
192                     IntPtr hWND = IntPtr.Zero;
193                     Window ownerWindow = Window.GetWindow(this.view);
194                     if (null != ownerWindow)
195                     {
196                         WindowInteropHelper helper = new WindowInteropHelper(ownerWindow);
197                         hWND = helper.Handle;
198                     }
199                     this.Context.Services.Publish<WindowHelperService>(new WindowHelperService(hWND));
200                 }
201                 WindowHelperService whs = this.context.Services.GetService<WindowHelperService>();
202                 whs.View = this.view;
203
204                 //check if workflow command extension item is available - if not, provide default one
205                 if (!this.context.Items.Contains<WorkflowCommandExtensionItem>())
206                 {
207                     WorkflowCommandExtensionItem item = new WorkflowCommandExtensionItem(new DefaultCommandExtensionCallback());
208                     this.context.Items.SetValue(item);
209                 }
210
211                 ComponentDispatcher.EnterThreadModal += new EventHandler(ComponentDispatcher_EnterThreadModal);
212                 ComponentDispatcher.LeaveThreadModal += new EventHandler(ComponentDispatcher_LeaveThreadModal);
213             };
214
215             this.view.Unloaded += (s, e) =>
216             {
217                 ComponentDispatcher.EnterThreadModal -= new EventHandler(ComponentDispatcher_EnterThreadModal);
218                 ComponentDispatcher.LeaveThreadModal -= new EventHandler(ComponentDispatcher_LeaveThreadModal);
219             };
220
221             this.view.IsKeyboardFocusWithinChanged += (s, e) =>
222             {
223                 // The ModelTreeManager is null when there is an active ErrorItem.
224                 // We have nothing to write to text in this case.
225                 if (this.modelTreeManager != null && (bool)e.NewValue == false)
226                 {
227                     if ((FocusManager.GetFocusedElement(this.view) as TextBox) != null)
228                     {
229                         FocusManager.SetFocusedElement(this.view, null);
230                         this.NotifyModelChanged();
231                     }
232                 }
233             };
234         }
235
236         internal ValidationService ValidationService
237         {
238             get
239             {
240                 if (this.validationService == null)
241                 {
242                     this.validationService = new ValidationService(this.context);
243                     this.validationService.ErrorsMarked += ActivityArgumentHelper.UpdateInvalidArgumentsIfNecessary;
244                 }
245
246                 return this.validationService;
247             }
248         }
249
250         internal ObjectReferenceService ObjectReferenceService
251         {
252             get
253             {
254                 if (this.objectReferenceService == null)
255                 {
256                     this.objectReferenceService = new ObjectReferenceService(this.context);
257                 }
258
259                 return this.objectReferenceService;
260             }
261         }
262
263         public UIElement View
264         {
265             get
266             {
267                 return this.view;
268             }
269         }
270
271         public UIElement PropertyInspectorView
272         {
273             get
274             {
275                 if (this.propertyInspector == null)
276                 {
277                     // We change WorkflowDesigner.PropertyInspectorView to be lazy load because the propertyinspector hosted in
278                     // Winform elementhost will not get the resource change notification from Application level resource dictionary.
279                     // So we have to have all colors be ready before propertyinspector gets initialized.
280                     this.propertyInspector = new PropertyInspector();
281                     this.propertyInspector.DesignerContextItemManager = this.context.Items;
282                     this.propertyInspector.EditingContext = this.context;
283                     this.InitializePropertyInspectorResources();
284                     this.InitializePropertyInspectorCommandHandling();
285                 }
286
287                 return this.propertyInspector;
288             }
289         }
290
291         public UIElement OutlineView
292         {
293             get
294             {
295                 if (this.outlineView == null)
296                 {
297                     this.outlineView = new Grid();
298                     this.outlineView.Focusable = false;
299                     AddOutlineView();
300                 }
301
302                 return this.outlineView;
303             }
304         }
305
306         void AddOutlineView()
307         {
308             DesignerTreeView treeView = new DesignerTreeView();
309             treeView.Initialize(context);
310             this.context.Services.Subscribe<ModelService>(delegate(ModelService modelService)
311             {
312                 if (modelService.Root != null)
313                 {
314                     treeView.SetRootDesigner(modelService.Root);
315
316                 }
317                 treeView.RestoreDesignerStates();
318             });
319             this.outlineView.Children.Add(treeView);
320         }
321
322
323         public EditingContext Context
324         {
325             get
326             {
327                 return this.context;
328             }
329         }
330
331         public ContextMenu ContextMenu
332         {
333             get
334             {
335                 if (null != this.context)
336                 {
337                     DesignerView designerView = this.context.Services.GetService<DesignerView>();
338                     if (null != designerView)
339                     {
340                         return designerView.ContextMenu;
341                     }
342                 }
343                 return null;
344             }
345         }
346
347         public string Text
348         {
349             get { return this.text; }
350             set { this.text = value; }
351         }
352
353         [SuppressMessage(FxCop.Category.Design, "CA1044:PropertiesShouldNotBeWriteOnly",
354             Justification = "The host just sets this property for the designer to know which colors to display.")]
355         public string PropertyInspectorFontAndColorData
356         {
357             set
358             {
359                 StringReader stringReader = new StringReader(value);
360                 XmlReader xmlReader = XmlReader.Create(stringReader);
361                 Hashtable fontAndColorDictionary = (Hashtable)System.Windows.Markup.XamlReader.Load(xmlReader);
362                 foreach (string key in fontAndColorDictionary.Keys)
363                 {
364                     WorkflowDesignerColors.FontAndColorResources[key] = fontAndColorDictionary[key];
365                 }
366             }
367         }
368
369
370         public bool IsInErrorState()
371         {
372             ErrorItem errorItem = this.context.Items.GetValue<ErrorItem>();
373             return errorItem.Message != null && errorItem.Details != null ? true : false;
374         }
375
376         // Load using Xaml.
377         [SuppressMessage(FxCop.Category.Design, FxCop.Rule.DoNotCatchGeneralExceptionTypes,
378             Justification = "Deserializer might throw if it fails to deserialize. Catching all exceptions to avoid VS Crash.")]
379         [SuppressMessage("Reliability", "Reliability108",
380             Justification = "Deserializer might throw if it fails to deserialize. Catching all exceptions to avoid VS crash.")]
381         public void Load()
382         {
383             this.perfEventProvider.WorkflowDesignerLoadStart();
384             if (!string.IsNullOrEmpty(this.text))
385             {
386                 try
387                 {
388                     this.perfEventProvider.WorkflowDesignerDeserializeStart();
389
390                     IList<XamlLoadErrorInfo> loadErrors;
391                     Dictionary<object, SourceLocation> sourceLocations;
392                     object deserializedObject = DeserializeString(this.text, out loadErrors, out sourceLocations);
393
394                     this.perfEventProvider.WorkflowDesignerDeserializeEnd();
395
396                     if (deserializedObject != null)
397                     {
398                         this.Load(deserializedObject);
399                         this.ValidationService.ValidateWorkflow(ValidationReason.Load);
400                     }
401                     else
402                     {
403                         StringBuilder details = new StringBuilder();
404                         foreach (XamlLoadErrorInfo error in loadErrors)
405                         {
406                             details.AppendLine(error.Message);
407                         }
408                         this.Context.Items.SetValue(new ErrorItem() { Message = SR.SeeErrorWindow, Details = details.ToString() });
409                     }
410                     if (loadErrors != null)
411                     {
412                         RaiseLoadErrors(loadErrors);
413                     }
414                     this.isModelChanged = false;
415                 }
416                 catch (Exception e)
417                 {
418                     this.Context.Items.SetValue(new ErrorItem() { Message = e.Message, Details = e.ToString() });
419                     RaiseLoadError(e);
420                 }
421             }
422             else
423             {
424                 this.Context.Items.SetValue(new ErrorItem() { Message = string.Empty, Details = string.Empty });
425             }
426             if (this.IsInErrorState())
427             {
428                 // Clear workflow symbol in case ErrorState changes during validation
429                 this.lastWorkflowSymbol = null;
430             }
431             this.perfEventProvider.WorkflowDesignerLoadComplete();
432         }
433
434         public void Load(string fileName)
435         {
436             if (string.IsNullOrEmpty(fileName))
437             {
438                 throw FxTrace.Exception.AsError(new ArgumentNullException("fileName"));
439             }
440             
441             DesignerConfigurationService service = this.Context.Services.GetService<DesignerConfigurationService>();
442             service.SetDefaultOfLoadingFromUntrustedSourceEnabled();
443             if (!service.LoadingFromUntrustedSourceEnabled && !IsFromUnrestrictedPath(fileName))
444             {
445                 throw FxTrace.Exception.AsError(new SecurityException(string.Format(CultureInfo.CurrentUICulture, SR.UntrustedSourceDetected, fileName)));
446             }
447
448             try
449             {
450                 IDocumentPersistenceService documentPersistenceService = this.Context.Services.GetService<IDocumentPersistenceService>();
451                 if (documentPersistenceService != null)
452                 {
453                     this.Load(documentPersistenceService.Load(fileName));
454                 }
455                 else
456                 {
457                     using (StreamReader fileStream = new StreamReader(fileName))
458                     {
459                         this.loadedFile = fileName;
460                         WorkflowFileItem fileItem = new WorkflowFileItem();
461                         fileItem.LoadedFile = fileName;
462                         this.context.Items.SetValue(fileItem);
463                         this.Text = fileStream.ReadToEnd();
464                         this.Load();
465                     }
466                 }
467             }
468             catch (Exception e)
469             {
470                 if (Fx.IsFatal(e))
471                 {
472                     throw;
473                 }
474                 else
475                 {
476                     this.Context.Items.SetValue(new ErrorItem() { Message = e.Message, Details = e.ToString() });
477                 }
478             }
479             if (!this.IsInErrorState())
480             {
481                 if (this.debuggerService != null)
482                 {
483                     this.debuggerService.InvalidateSourceLocationMapping(fileName);
484                 }
485             }
486         }
487
488         // This supports loading objects instead of xaml into the designer 
489         public void Load(object instance)
490         {
491             if (isLoaded)
492             {
493                 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.WorkflowDesignerLoadShouldBeCalledOnlyOnce));
494             }
495
496             isLoaded = true;
497
498             if (instance == null)
499             {
500                 throw FxTrace.Exception.AsError(new ArgumentNullException("instance"));
501             }
502
503             DesignerConfigurationService configurationService = this.context.Services.GetService<DesignerConfigurationService>();
504             configurationService.ApplyDefaultPreference();
505
506             // Because we want AutoConnect/AutoSplit to be on even in Dev10 if PU1 is installed.
507             // But we cannot know whether PU1 is installed or not, we decide to enable these 2 features for all Dev10.
508             if (configurationService.WorkflowDesignerHostId == WorkflowDesignerHostId.Dev10)
509             {
510                 configurationService.AutoConnectEnabled = true;
511                 configurationService.AutoSplitEnabled = true;
512             }
513
514             configurationService.IsWorkflowLoaded = true;
515             configurationService.Validate();
516
517             if (this.PreviewLoad != null)
518             {
519                 this.PreviewLoad(this, new PreviewLoadEventArgs(instance, this.context));
520             }
521
522             if (configurationService.TargetFrameworkName.IsLessThan45())
523             {
524                 TargetFrameworkPropertyFilter.FilterOut45Properties();
525             }
526
527             modelTreeManager = new ModelTreeManager(this.context);
528             modelTreeManager.Load(instance);
529             this.context.Services.Publish(typeof(ModelTreeManager), modelTreeManager);
530             viewManager = GetViewManager(this.modelTreeManager.Root);
531             this.context.Services.Publish<ModelSearchService>(this.ModelSearchService);
532             view.Children.Add((UIElement)viewManager.View);
533
534             modelTreeManager.EditingScopeCompleted += new EventHandler<EditingScopeEventArgs>(OnEditingScopeCompleted);
535
536             this.view.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,
537                              new Action(() => { this.perfEventProvider.WorkflowDesignerApplicationIdleAfterLoad(); }));
538
539             //Subscribe to the ViewStateChanged event of ViewStateService to show document dirty. It would be published in the call to GetViewManager().
540             WorkflowViewStateService wfViewStateService = this.Context.Services.GetService(typeof(ViewStateService)) as WorkflowViewStateService;
541             if (wfViewStateService != null)
542             {
543                 wfViewStateService.UndoableViewStateChanged += new ViewStateChangedEventHandler(OnViewStateChanged);
544             }
545             this.isModelChanged = false;
546             if (!this.IsInErrorState())
547             {
548                 this.lastWorkflowSymbol = GetAttachedWorkflowSymbol();
549             }
550         }
551
552         public void Save(string fileName)
553         {
554             this.isModelChanged = true; // ensure flushing any viewstate changes that does not imply model changed.
555             try
556             {
557                 // Cancel pervious validation and suppress validation work.
558                 this.ValidationService.DeactivateValidation();
559                 Flush(fileName);
560             }
561             finally
562             {
563                 this.ValidationService.ActivateValidation();
564             }
565
566             using (StreamWriter fileStreamWriter = new StreamWriter(fileName, false, Encoding.UTF8))
567             {
568                 fileStreamWriter.Write(this.Text);
569                 fileStreamWriter.Flush();
570             }
571
572             if (this.Context.Services.GetService<ModelService>() != null)
573             {
574                 this.ValidationService.ValidateWorkflow(ValidationReason.Save);
575             }
576
577             if (this.debuggerService != null)
578             {
579                 this.debuggerService.InvalidateSourceLocationMapping(fileName);
580             }
581         }
582
583         public void Flush()
584         {
585             Flush(null);
586         }
587
588         private bool IsFromUnrestrictedPath(string fileName)
589         {
590             Evidence folderEvidence = new Evidence();
591             folderEvidence.AddHostEvidence(Zone.CreateFromUrl(fileName));
592
593             PermissionSet standardFolderSandbox = SecurityManager.GetStandardSandbox(folderEvidence);
594             return standardFolderSandbox.IsUnrestricted();
595         }
596
597         void Flush(string fileName)
598         {
599             if (this.modelTreeManager == null)
600             {
601                 // It's possible for modelTreeManager to be null if Load is called but the xaml file being loaded is invalid.
602                 // We only want to throw exception if Load hasn't been called yet.
603                 if (IsInErrorState() == false)
604                 {
605                     throw FxTrace.Exception.AsError(new InvalidOperationException(SR.WorkflowDesignerLoadShouldBeCalledFirst));
606                 }
607             }
608             else
609             {
610                 this.FlushEdits();
611                 IDocumentPersistenceService documentPersistenceService = this.Context.Services.GetService<IDocumentPersistenceService>();
612                 if (documentPersistenceService != null)
613                 {
614                     documentPersistenceService.Flush(this.modelTreeManager.Root.GetCurrentValue());
615                 }
616                 else
617                 {
618                     this.WriteModelToText(fileName);
619                 }
620             }
621         }
622
623         void FlushEdits()
624         {
625             UIElement oldFocus = null;
626             //check if property grid has keyboard focus within, if yes - get focused control
627             if (null != this.propertyInspector && this.propertyInspector.IsKeyboardFocusWithin)
628             {
629                 oldFocus = FocusManager.GetFocusedElement(this.propertyInspector) as UIElement;
630             }
631             //check if view has keyboard focus within, if yes - get focused control
632             if (null != this.view && this.view.IsKeyboardFocusWithin)
633             {
634                 oldFocus = FocusManager.GetFocusedElement(this.view) as UIElement;
635             }
636             if (null != oldFocus)
637             {
638                 RoutedCommand cmd = DesignerView.CommitCommand as RoutedCommand;
639                 if (cmd != null)
640                 {
641                     cmd.Execute(null, oldFocus);
642                 }
643             }
644
645             //commit changes within arguments and variables editor
646             var designerView = this.Context.Services.GetService<DesignerView>();
647             if (null != designerView)
648             {
649                 if (null != designerView.arguments1)
650                 {
651                     DataGridHelper.CommitPendingEdits(designerView.arguments1.argumentsDataGrid);
652                 }
653                 if (null != designerView.variables1)
654                 {
655                     DataGridHelper.CommitPendingEdits(designerView.variables1.variableDataGrid);
656                 }
657             }
658         }
659
660         static void InitializePropertyInspectorMetadata()
661         {
662             PropertyInspectorMetadata.Initialize();
663         }
664
665         static Activity GetRootWorkflowElement(object rootModelObject)
666         {
667             return WorkflowDesignerXamlHelper.GetRootWorkflowElement(rootModelObject);
668         }
669     }
670 }