|
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
namespace System.Activities.Core.Presentation
{
using System;
using System.Activities.Presentation;
using System.Activities.Presentation.Model;
using System.Activities.Presentation.Services;
using System.Activities.Presentation.View;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using System.Activities.Presentation.FreeFormEditing;
using System.Activities.Statements;
using System.Runtime;
using System.Activities.Presentation.Internal.PropertyEditing;
using System.Globalization;
// The StateContainerEditor contains a FreeFormPanel that implements free form editing behaviors
// for States and Transitions among them. An instance of StateMachineDesigner and an instance of
// StateDesigner each contains an instance of StateContainerEditor to edit its child States and
// Transitions.
partial class StateContainerEditor : IAutoConnectContainer, IAutoSplitContainer
{
// Used to find the destination state when pasting transition.
private static Dictionary<ModelItem, string> modelItemToGuid = new Dictionary<ModelItem, string>();
internal static ModelItem CopiedTransitionDestinationState { get; set; }
// Flag to indicate whether the editor has been populated
bool populated = false;
// To keep track of all child state designer
Dictionary<ModelItem, UIElement> modelItemToUIElement;
// shapeLocations is useful to avoid pasting on existing shapes.
HashSet<Point> shapeLocations = null;
// To keep track of transition collections that the outmost editor listens to the CollectionChanged events.
HashSet<ModelItem> listenedTransitionCollections;
// Flag whether the view state change has already been committed in the UI.
bool internalViewStateChange = false;
// activeSrcConnectionPoint is required for connector creation gesture to store the source of the link.
ConnectionPoint activeSrcConnectionPoint;
ConnectionPoint activeDestConnectionPointForAutoSplit;
ConnectionPoint activeSrcConnectionPointForAutoSplit;
EdgeLocation entryEdgeForAutoSplit;
EdgeLocation exitEdgeForAutoSplit;
// selectedConnector is a placeholder for the last connector selected.
Connector selectedConnector = null;
bool updatingSelectedConnector;
// Used for connector creation
UIElement lastConnectionPointMouseUpElement = null;
// Only used by the outmost editor to keep track of transitions added/removed when editing scope completes
List<ModelItem> transitionModelItemsAdded;
List<ModelItem> transitionModelItemsRemoved;
// The outmost editor when the designer is populated.
// This is used to find the outmost editor when this editor has been removed from the visual tree.
StateContainerEditor stateMachineContainerEditor = null;
// To keep track of whether the initial state is changed during an EditingScope
bool initialStateChanged = false;
// The initial node symbol
UIElement initialNode = null;
// The ModelItem for the initial node
ModelItem initialModelItem = null;
// To register / unregister the editor as the default composite view on its parent
ICompositeViewEvents compositeViewEvents = null;
Size requiredSize = new Size(0, 0);
bool suppressAddingConnectorsWhenAddingStateVisuals = false;
ConnectionPointsAdorner activeConnectionPointsAdorner = null;
ConnectionPoint activeConnectionPoint = null;
SubscribeContextCallback<Selection> onSelectionChangedCallback = null;
bool? isRightToLeft;
// Constants
const double startSymbolTopMargin = 10.0;
// Default size of the state container editor when it is inside of the state designer
const double DefaultWidthForState = 100;
const double DefaultHeightForState = 25;
// Default size of the state container editor when it is inside of the state machine designer
const double DefaultWidthForStateMachine = 600;
const double DefaultHeightForStateMachine = 600;
// Default size of the state designer
const double DefaultStateDesignerWidth = 114;
const double DefaultStateDesignerHeight = 61;
// Default size of the initial / final node
const double InitialNodeWidth = 60;
const double InitialNodeHeight = 75;
const double ConnectionPointMargin = 15;
const string ShapeLocationViewStateKey = "ShapeLocation";
const string ShapeSizeViewStateKey = "ShapeSize";
internal const string ConnectorLocationViewStateKey = "ConnectorLocation";
internal const string SrcConnectionPointIndexStateKey = "SrcConnectionPointIndex";
internal const string DestConnectionPointIndexStateKey = "DestConnectionPointIndex";
internal const string StateContainerWidthViewStateKey = "StateContainerWidth";
internal const string StateContainerHeightViewStateKey = "StateContainerHeight";
internal const string DefaultStateDisplayName = "State";
internal const string DefaultFinalStateDisplayName = "FinalState";
internal const int ConnectionPointNum = 19;
private const string TriggerNameToolTip = "Trigger: {0}";
private const string TransitionNameToolTip = "Transition: {0}";
public static readonly DependencyProperty StateContainerWidthProperty = DependencyProperty.Register(
"StateContainerWidth",
typeof(double),
typeof(StateContainerEditor),
new FrameworkPropertyMetadata(DefaultWidthForState));
public static readonly DependencyProperty StateContainerHeightProperty = DependencyProperty.Register(
"StateContainerHeight",
typeof(double),
typeof(StateContainerEditor),
new FrameworkPropertyMetadata(DefaultHeightForState));
public static readonly DependencyProperty PanelMinWidthProperty = DependencyProperty.Register(
"PanelMinWidth",
typeof(double),
typeof(StateContainerEditor),
new FrameworkPropertyMetadata());
public static readonly DependencyProperty PanelMinHeightProperty = DependencyProperty.Register(
"PanelMinHeight",
typeof(double),
typeof(StateContainerEditor),
new FrameworkPropertyMetadata());
public static readonly DependencyProperty ConnectorModelItemProperty = DependencyProperty.RegisterAttached(
"ConnectorModelItem",
typeof(ModelItem),
typeof(StateContainerEditor),
new FrameworkPropertyMetadata());
public static readonly DependencyProperty ConnectionPointsProperty = DependencyProperty.RegisterAttached(
"ConnectionPoints",
typeof(List<ConnectionPoint>),
typeof(StateContainerEditor),
new FrameworkPropertyMetadata());
public static readonly DependencyProperty ModelItemProperty = DependencyProperty.Register(
"ModelItem",
typeof(ModelItem),
typeof(StateContainerEditor),
new FrameworkPropertyMetadata());
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(
"IsReadOnly",
typeof(bool), typeof(StateContainerEditor),
new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty IsStateMachineContainerProperty = DependencyProperty.Register(
"IsStateMachineContainer",
typeof(bool), typeof(StateContainerEditor),
new FrameworkPropertyMetadata(false));
public StateContainerEditor()
{
InitializeComponent();
this.modelItemToUIElement = new Dictionary<ModelItem, UIElement>();
this.shapeLocations = new HashSet<Point>();
this.listenedTransitionCollections = new HashSet<ModelItem>();
this.transitionModelItemsAdded = new List<ModelItem>();
this.transitionModelItemsRemoved = new List<ModelItem>();
Binding readOnlyBinding = new Binding();
readOnlyBinding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DesignerView), 1);
readOnlyBinding.Path = new PropertyPath(DesignerView.IsReadOnlyProperty);
readOnlyBinding.Mode = BindingMode.OneWay;
this.SetBinding(IsReadOnlyProperty, readOnlyBinding);
this.Loaded += (s, e) =>
{
if (this.ShouldInitialize())
{
WorkflowViewElement parent = VisualTreeUtils.FindVisualAncestor<WorkflowViewElement>(this);
this.ModelItem = parent.ModelItem;
this.StateMachineModelItem = StateContainerEditor.GetStateMachineModelItem(this.ModelItem);
this.Context = parent.Context;
this.compositeViewEvents = parent;
if (this.compositeViewEvents != null)
{
this.compositeViewEvents.RegisterDefaultCompositeView(this);
}
if (!this.populated)
{
this.Populate();
Selection.Subscribe(this.Context, this.OnSelectionChangedCallback);
this.populated = true;
}
}
};
this.Unloaded += (s, e) =>
{
if (this.compositeViewEvents != null)
{
(compositeViewEvents).UnregisterDefaultCompositeView(this);
this.compositeViewEvents = null;
}
if (this.populated)
{
this.Cleanup();
Selection.Unsubscribe(this.Context, this.OnSelectionChangedCallback);
this.populated = false;
}
this.StateMachineModelItem = null;
this.activeSrcConnectionPoint = null;
this.activeDestConnectionPointForAutoSplit = null;
this.activeSrcConnectionPointForAutoSplit = null;
// selectedConnector is a placeholder for the last connector selected.
this.selectedConnector = null;
// Used for connector creation
this.lastConnectionPointMouseUpElement = null;
this.activeConnectionPointsAdorner = null;
this.activeConnectionPoint = null;
this.initialNode = null;
// The ModelItem for the initial node
this.initialModelItem = null;
BindingOperations.ClearBinding(this, IsReadOnlyProperty);
};
}
internal FreeFormPanel Panel
{
get
{
return this.panel;
}
}
ViewStateService ViewStateService
{
get
{
ViewStateService viewStateService = this.Context.Services.GetService<ViewStateService>();
return viewStateService;
}
}
DesignerView DesignerView
{
get
{
return this.Context.Services.GetService<DesignerView>();
}
}
SubscribeContextCallback<Selection> OnSelectionChangedCallback
{
get
{
if (this.onSelectionChangedCallback == null)
{
this.onSelectionChangedCallback = new SubscribeContextCallback<Selection>(this.OnSelectionChanged);
}
return this.onSelectionChangedCallback;
}
}
public double StateContainerWidth
{
get { return (double)this.GetValue(StateContainerEditor.StateContainerWidthProperty); }
set { this.SetValue(StateContainerEditor.StateContainerWidthProperty, value); }
}
public double StateContainerHeight
{
get { return (double)this.GetValue(StateContainerEditor.StateContainerHeightProperty); }
set { this.SetValue(StateContainerEditor.StateContainerHeightProperty, value); }
}
public double PanelMinWidth
{
get { return (double)this.GetValue(StateContainerEditor.PanelMinWidthProperty); }
set { this.SetValue(StateContainerEditor.PanelMinWidthProperty, value); }
}
public double PanelMinHeight
{
get { return (double)this.GetValue(StateContainerEditor.PanelMinHeightProperty); }
set { this.SetValue(StateContainerEditor.PanelMinHeightProperty, value); }
}
public ModelItem ModelItem
{
get { return (ModelItem)GetValue(ModelItemProperty); }
set { SetValue(ModelItemProperty, value); }
}
protected bool IsReadOnly
{
get { return (bool)GetValue(IsReadOnlyProperty); }
private set { SetValue(IsReadOnlyProperty, value); }
}
protected bool IsStateMachineContainer
{
get { return (bool)GetValue(IsStateMachineContainerProperty); }
private set { SetValue(IsStateMachineContainerProperty, value); }
}
public EditingContext Context
{
get;
set;
}
ModelItem StateMachineModelItem
{
get;
set;
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
this.DataContext = this;
}
#region PopulateCleanup
void Populate()
{
// Keep track of the outmost editor, which may not be accessible by traversing the visual tree when the designer is deleted.
this.stateMachineContainerEditor = this.GetStateMachineContainerEditor();
this.panel.LocationChanged += new LocationChangedEventHandler(OnFreeFormPanelLocationChanged);
this.panel.ConnectorMoved += new ConnectorMovedEventHandler(OnFreeFormPanelConnectorMoved);
this.panel.LayoutUpdated += new EventHandler(OnFreeFormPanelLayoutUpdated);
this.panel.RequiredSizeChanged += new RequiredSizeChangedEventHandler(OnFreeFormPanelRequiredSizeChanged);
this.ViewStateService.ViewStateChanged += new ViewStateChangedEventHandler(OnViewStateChanged);
if (this.ModelItem.ItemType == typeof(StateMachine))
{
// Only StateMachine supports "States" collection
IsStateMachineContainer = true;
this.ModelItem.Properties[StateMachineDesigner.StatesPropertyName].Collection.CollectionChanged += new NotifyCollectionChangedEventHandler(OnStateCollectionChanged);
this.ModelItem.PropertyChanged += new PropertyChangedEventHandler(this.OnModelPropertyChanged);
ModelTreeManager modelTreeManager = this.Context.Services.GetService<ModelTreeManager>();
modelTreeManager.EditingScopeCompleted += new EventHandler<EditingScopeEventArgs>(this.OnEditingScopeCompleted);
foreach (ModelItem modelItem in this.ModelItem.Properties[StateMachineDesigner.StatesPropertyName].Collection)
{
if (modelItem.ItemType == typeof(State))
{
ModelItemCollection transitions = modelItem.Properties[StateDesigner.TransitionsPropertyName].Collection;
if (!this.listenedTransitionCollections.Contains(transitions))
{
transitions.CollectionChanged += new NotifyCollectionChangedEventHandler(this.OnTransitionCollectionChanged);
this.listenedTransitionCollections.Add(transitions);
}
}
}
}
object widthViewState = this.ViewStateService.RetrieveViewState(this.ModelItem, StateContainerWidthViewStateKey);
if (widthViewState != null)
{
this.StateContainerWidth = (double)widthViewState;
}
object heightViewState = this.ViewStateService.RetrieveViewState(this.ModelItem, StateContainerHeightViewStateKey);
if (heightViewState != null)
{
this.StateContainerHeight = (double)heightViewState;
}
this.panel.Children.Clear();
this.modelItemToUIElement.Clear();
this.shapeLocations.Clear();
if (this.ModelItem.ItemType == typeof(StateMachine))
{
this.AddInitialNode();
this.AddStateVisuals(this.ModelItem.Properties[StateMachineDesigner.StatesPropertyName].Collection);
}
}
void Cleanup()
{
this.panel.Children.Clear();
// Cleaning up the designers as they might be re-used.
foreach (UIElement element in this.modelItemToUIElement.Values)
{
element.MouseEnter -= new MouseEventHandler(OnChildElementMouseEnter);
element.MouseLeave -= new MouseEventHandler(OnChildElementMouseLeave);
((FrameworkElement)element).SizeChanged -= new SizeChangedEventHandler(OnChildElementSizeChanged);
}
this.modelItemToUIElement.Clear();
this.panel.LocationChanged -= new LocationChangedEventHandler(OnFreeFormPanelLocationChanged);
this.panel.ConnectorMoved -= new ConnectorMovedEventHandler(OnFreeFormPanelConnectorMoved);
this.panel.LayoutUpdated -= new EventHandler(OnFreeFormPanelLayoutUpdated);
this.panel.RequiredSizeChanged -= new RequiredSizeChangedEventHandler(OnFreeFormPanelRequiredSizeChanged);
this.ViewStateService.ViewStateChanged -= new ViewStateChangedEventHandler(OnViewStateChanged);
if (this.ModelItem.ItemType == typeof(StateMachine))
{
// Only StateMachine supports "States" collection
this.ModelItem.Properties[StateMachineDesigner.StatesPropertyName].Collection.CollectionChanged -= new NotifyCollectionChangedEventHandler(OnStateCollectionChanged);
this.ModelItem.PropertyChanged -= new PropertyChangedEventHandler(this.OnModelPropertyChanged);
ModelTreeManager modelTreeManager = this.Context.Services.GetService<ModelTreeManager>();
modelTreeManager.EditingScopeCompleted -= new EventHandler<EditingScopeEventArgs>(this.OnEditingScopeCompleted);
foreach (ModelItem modelItem in this.ModelItem.Properties[StateMachineDesigner.StatesPropertyName].Collection)
{
if (modelItem.ItemType == typeof(State))
{
ModelItemCollection transitions = modelItem.Properties[StateDesigner.TransitionsPropertyName].Collection;
if (this.listenedTransitionCollections.Contains(transitions))
{
transitions.CollectionChanged -= new NotifyCollectionChangedEventHandler(this.OnTransitionCollectionChanged);
this.listenedTransitionCollections.Remove(transitions);
}
}
}
}
// stateMachineContainerEditor will be null when dropping a State into a WorkflowItemPresenter.
if (this.ModelItem.ItemType == typeof(State) && this.stateMachineContainerEditor != null)
{
this.stateMachineContainerEditor = null;
}
}
#endregion
#region InitialNode
void AddInitialNode()
{
// Instantiate the initial node
StartSymbol initialNode = StartSymbol.CreateStartSymbol(this.Context);
initialNode.Text = "Start";
this.initialModelItem = initialNode.ModelItem;
this.modelItemToUIElement.Add(this.initialModelItem, initialNode);
DragDropHelper.SetCompositeView(initialNode, this);
this.initialNode = initialNode;
this.PopulateConnectionPoints(this.initialNode);
this.initialNode.MouseEnter += new MouseEventHandler(OnChildElementMouseEnter);
this.initialNode.MouseLeave += new MouseEventHandler(OnChildElementMouseLeave);
this.panel.Children.Add(this.initialNode);
this.initialNode.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
double startHeight = this.initialNode.DesiredSize.Height;
double startWidth = this.initialNode.DesiredSize.Width;
object locationOfShape = this.ViewStateService.RetrieveViewState(this.ModelItem, StateContainerEditor.ShapeLocationViewStateKey);
if (locationOfShape != null)
{
Point locationPt = (Point)locationOfShape;
FreeFormPanel.SetLocation(this.initialNode, locationPt);
}
// If the view state is missing, place the initial node in the top middle.
else
{
Point startPoint = new Point(this.panel.MinWidth / 2, startSymbolTopMargin + startHeight / 2);
Point startLocation = SnapVisualToGrid(this.initialNode, startPoint, new Point(-1, -1), false);
FreeFormPanel.SetLocation(this.initialNode, startLocation);
this.internalViewStateChange = true;
this.StoreShapeLocationViewState(this.ModelItem, startLocation);
this.internalViewStateChange = false;
}
FreeFormPanel.SetChildSize(this.initialNode, new Size(startWidth, startHeight));
}
void AddInitialNodeConnector(UIElement initialStateView)
{
Fx.Assert(this.ModelItem.ItemType == typeof(StateMachine), "Only StateMachine should have initial state.");
List<Connector> attachedConnectors = StateContainerEditor.GetAttachedConnectors(this.initialNode);
if (attachedConnectors.Count == 0)
{
ConnectionPoint sourceConnectionPoint = StateContainerEditor.GetConnectionPointClosestToEdgeMidPoint(
this.initialNode,
StateContainerEditor.GetEmptyConnectionPoints(this.initialNode),
EdgeLocation.Bottom);
Point srcLocation = FreeFormPanel.GetLocation(this.initialNode);
Size srcSize = FreeFormPanel.GetChildSize(this.initialNode);
Point destLocation = FreeFormPanel.GetLocation(initialStateView);
Size destSize = FreeFormPanel.GetChildSize(initialStateView);
// by default, the connector would connect to the top of the initial state
EdgeLocation destConnectorEdge = EdgeLocation.Top;
if (srcLocation.X > destLocation.X + destSize.Width)
{
// if the state is located at the west of the start node, should connect to the right side of initial state.
destConnectorEdge = EdgeLocation.Right;
}
else if (srcLocation.X + srcSize.Width < destLocation.X)
{
// if the state is located at the east of the start node, should connect to the left side of initial state.
destConnectorEdge = EdgeLocation.Left;
}
ConnectionPoint destinationConnectionPoint = StateContainerEditor.GetConnectionPointClosestToEdgeMidPoint(
initialStateView,
StateContainerEditor.GetEmptyConnectionPoints(initialStateView),
destConnectorEdge);
this.AddConnector(this.initialNode,
initialStateView,
this.ModelItem,
sourceConnectionPoint,
destinationConnectionPoint);
}
}
#endregion
#region StateVisuals
UIElement ProcessAndGetModelView(ModelItem model)
{
UIElement element;
if (!this.modelItemToUIElement.TryGetValue(model, out element))
{
element = this.Context.Services.GetService<VirtualizedContainerService>().GetContainer(model, this);
if (element is VirtualizedContainerService.VirtualizingContainer)
{
// Fix bug 183698 - if the container does not contain other states, the minwidth should
// be re-set to the default and let the FreeFormPanel to calculate its actual size.
// If a child state was previously expanded, the container's min size would be set
// to its expanded size via ContainerService.GetHintSize in GetContainer() method.
// But if the item is a simple state, its min size should be reset to the default minimum.
((VirtualizedContainerService.VirtualizingContainer)element).MinWidth = DefaultStateDesignerWidth;
((VirtualizedContainerService.VirtualizingContainer)element).MinHeight = DefaultStateDesignerHeight;
}
else
{
Fx.Assert(false, "We expect GetContainer always returns a VirtualizingContainer.");
}
element.MouseEnter += new MouseEventHandler(OnChildElementMouseEnter);
element.MouseLeave += new MouseEventHandler(OnChildElementMouseLeave);
((FrameworkElement)element).SizeChanged += new SizeChangedEventHandler(OnChildElementSizeChanged);
this.modelItemToUIElement.Add(model, element);
this.PopulateConnectionPoints(element);
object locationOfShape = this.ViewStateService.RetrieveViewState(model, ShapeLocationViewStateKey);
object sizeOfShape = this.ViewStateService.RetrieveViewState(model, ShapeSizeViewStateKey);
if (locationOfShape != null)
{
Point locationPt = (Point)locationOfShape;
FreeFormPanel.SetLocation(element, locationPt);
this.shapeLocations.Add(locationPt);
}
if (sizeOfShape != null)
{
Size size = (Size)sizeOfShape;
FreeFormPanel.SetChildSize(element, size);
VirtualizedContainerService.VirtualizingContainer virtualizingContainer = element as VirtualizedContainerService.VirtualizingContainer;
if (virtualizingContainer != null)
{
virtualizingContainer.MinWidth = size.Width;
virtualizingContainer.MinHeight = size.Height;
}
}
}
return element;
}
void AddStateVisuals(IList<ModelItem> modelItemCollection)
{
List<UIElement> viewsAdded = new List<UIElement>();
foreach (ModelItem modelItem in modelItemCollection)
{
if (!this.modelItemToUIElement.ContainsKey(modelItem))
{
viewsAdded.Add(ProcessAndGetModelView(modelItem));
}
else if (!this.panel.Children.Contains(this.modelItemToUIElement[modelItem]))
{
viewsAdded.Add(this.modelItemToUIElement[modelItem]);
}
}
foreach (UIElement view in viewsAdded)
{
this.panel.Children.Add(view);
}
// We need to wait until after the state visuals are added and displayed.
this.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
{
if (!suppressAddingConnectorsWhenAddingStateVisuals && this.populated)
{
ModelItem stateMachineModelItem = null;
foreach (UIElement view in viewsAdded)
{
ModelItem stateModelItem = StateContainerEditor.GetModelItemFromView(view);
this.AddChildTransitionVisualsToStateMachineEditor(stateModelItem);
if (stateMachineModelItem == null)
{
stateMachineModelItem = StateContainerEditor.GetStateMachineModelItem(stateModelItem);
}
if (stateMachineModelItem.Properties[StateMachineDesigner.InitialStatePropertyName].Value == stateModelItem)
{
this.AddInitialNodeConnector(view);
}
}
}
}));
}
void RemoveStateVisual(UIElement removedStateDesigner)
{
HashSet<Connector> connectorsToDelete = new HashSet<Connector>();
ModelService modelService = this.Context.Services.GetService<ModelService>();
List<UIElement> removedStateDesigners = new List<UIElement>();
removedStateDesigners.Add(removedStateDesigner);
StateContainerEditor stateMachineEditor = this.GetStateMachineContainerEditor();
foreach (UIElement designer in removedStateDesigners)
{
if (stateMachineEditor.activeSrcConnectionPoint != null)
{
List<ConnectionPoint> connectionPoints = GetConnectionPoints(designer);
if (connectionPoints.Contains(stateMachineEditor.activeSrcConnectionPoint))
{
stateMachineEditor.activeSrcConnectionPoint = null;
RemoveAdorner(stateMachineEditor.panel, typeof(ConnectorCreationAdorner));
}
}
if (stateMachineEditor.lastConnectionPointMouseUpElement == designer)
{
stateMachineEditor.lastConnectionPointMouseUpElement = null;
}
connectorsToDelete.UnionWith(GetAttachedConnectors(designer));
}
// Remove any connector visuals attached to this shape. This is required for the scenarios as follows:
// Copy and paste two connected States into StateMachine and undo the paste.
// The Transition is not removed as a model change. Hence the connector visual will remain dangling on the designer.
foreach (Connector connector in connectorsToDelete)
{
this.Remove(connector);
}
ModelItem modelItem = ((VirtualizedContainerService.VirtualizingContainer)removedStateDesigner).ModelItem;
this.modelItemToUIElement.Remove(modelItem);
removedStateDesigner.MouseEnter -= new MouseEventHandler(OnChildElementMouseEnter);
removedStateDesigner.MouseLeave -= new MouseEventHandler(OnChildElementMouseLeave);
((FrameworkElement)removedStateDesigner).SizeChanged -= new SizeChangedEventHandler(OnChildElementSizeChanged);
this.panel.Children.Remove(removedStateDesigner);
// deselect removed item
if (this.Context != null)
{
HashSet<ModelItem> selectedItems = new HashSet<ModelItem>(this.Context.Items.GetValue<Selection>().SelectedObjects);
if (selectedItems.Contains(modelItem))
{
Selection.Toggle(this.Context, modelItem);
}
}
object locationOfShape = this.ViewStateService.RetrieveViewState(modelItem, StateContainerEditor.ShapeLocationViewStateKey);
if (locationOfShape != null)
{
this.shapeLocations.Remove((Point)locationOfShape);
}
}
#endregion
#region TransitionVisualsAndConnector
void AddTransitionVisual(ModelItem transitionModelItem)
{
ModelItem sourceState = StateContainerEditor.GetParentStateModelItemForTransition(transitionModelItem);
ModelItem destinationState = transitionModelItem.Properties[TransitionDesigner.ToPropertyName].Value;
UIElement sourceDesigner = this.modelItemToUIElement[sourceState];
UIElement destinationDesigner = this.modelItemToUIElement[destinationState];
if (sourceDesigner.IsDescendantOf(this) && destinationDesigner.IsDescendantOf(this))
{
this.AddConnector(sourceDesigner, destinationDesigner, transitionModelItem);
}
}
void AddTransitionVisuals(IList<ModelItem> transitionModelItemCollection)
{
foreach (ModelItem transitionModelItem in transitionModelItemCollection)
{
this.AddTransitionVisual(transitionModelItem);
}
}
void AddChildTransitionVisualsToStateMachineEditor(ModelItem stateModelItem)
{
Fx.Assert(stateModelItem.ItemType == typeof(State), "The ModelItem should be a State.");
List<ModelItem> transitions = new List<ModelItem>();
transitions.AddRange(stateModelItem.Properties[StateDesigner.TransitionsPropertyName].Collection);
this.GetStateMachineContainerEditor().AddTransitionVisuals(transitions);
}
Connector CreateConnector(ConnectionPoint srcConnPoint, ConnectionPoint destConnPoint, PointCollection points, ModelItem connectorModelItem)
{
bool isTransition = connectorModelItem != null && connectorModelItem.ItemType == typeof(Transition);
Connector connector;
if (isTransition)
{
connector = new ConnectorWithStartDot();
}
else
{
connector = new ConnectorWithoutStartDot();
}
connector.FocusVisualStyle = null;
connector.Focusable = true;
DesignerView.SetCommandMenuMode(connector, CommandMenuMode.NoCommandMenu);
if (isTransition)
{
SetConnectorLabel(connector, connectorModelItem);
SetConnectorStartDotToolTip(connector.StartDot, connectorModelItem);
connector.HighlightOnHover = true;
connector.ToolTip = new StateConnectionPointToolTip();
connector.StartDot.MouseDown += new MouseButtonEventHandler(OnConnectorStartDotMouseDown);
connector.StartDot.MouseUp += new MouseButtonEventHandler(OnConnectorStartDotMouseUp);
}
AutomationProperties.SetName(connector, SR.ConnectionAutomationPropertiesName);
connector.GotKeyboardFocus += new KeyboardFocusChangedEventHandler(OnConnectorGotKeyboardFocus);
connector.RequestBringIntoView += new RequestBringIntoViewEventHandler(OnConnectorRequestBringIntoView);
connector.GotFocus += new RoutedEventHandler(OnConnectorGotFocus);
connector.MouseDoubleClick += new MouseButtonEventHandler(OnConnectorMouseDoubleClick);
connector.MouseDown += new MouseButtonEventHandler(OnConnectorMouseDown);
connector.KeyDown += new KeyEventHandler(OnConnectorKeyDown);
connector.ContextMenuOpening += new ContextMenuEventHandler(OnConnectorContextMenuOpening);
SetConnectorSrcDestConnectionPoints(connector, srcConnPoint, destConnPoint);
StateContainerEditor.SetConnectorModelItem(connector, connectorModelItem);
connector.Unloaded += new RoutedEventHandler(OnConnectorUnloaded);
connector.Points = points;
connector.AutoSplitContainer = this;
if (connectorModelItem.ItemType == typeof(Transition))
{
int srcConnectionPointIndex = StateContainerEditor.GetConnectionPoints(srcConnPoint.ParentDesigner).IndexOf(srcConnPoint);
int destConnectionPointIndex = StateContainerEditor.GetConnectionPoints(destConnPoint.ParentDesigner).IndexOf(destConnPoint);
this.ViewStateService.StoreViewState(connectorModelItem, SrcConnectionPointIndexStateKey, srcConnectionPointIndex);
this.ViewStateService.StoreViewState(connectorModelItem, DestConnectionPointIndexStateKey, destConnectionPointIndex);
}
return connector;
}
private void PopulateVirtualizingContainer(ModelItem item)
{
// For a VirtualizedContainer ModelItem, if it is newly created, its View is null.
// Otherwise its View is still the old view which does not belong to the current stateContainerEditor.
if (item != null && (item.View == null || DragDropHelper.GetCompositeView((WorkflowViewElement)item.View) != (UIElement)this))
{
UIElement element;
if (modelItemToUIElement.TryGetValue(item, out element) && element is VirtualizedContainerService.VirtualizingContainer)
{
VirtualizedContainerService.VirtualizingContainer container = element as VirtualizedContainerService.VirtualizingContainer;
container.Populate();
}
}
}
private bool IsRightToLeft
{
get
{
if (!this.isRightToLeft.HasValue)
{
this.isRightToLeft = FreeFormPanelUtilities.IsRightToLeft(this.stateContainerGrid);
}
return this.isRightToLeft.Value;
}
}
static bool IsViewStateValid(PointCollection locationPts)
{
return locationPts.All<Point>((p) => { return p.X.IsNoLessThan(0) && p.Y.IsNoLessThan(0); });
}
// Create a connector from the view state of the connector model item
Connector CreateConnectorByConnectorModelItemViewState(
UIElement source,
UIElement dest,
ModelItem connectorModelItem)
{
Connector connector = null;
object connectorLocation = this.ViewStateService.RetrieveViewState(connectorModelItem, ConnectorLocationViewStateKey);
PointCollection locationPts = connectorLocation as PointCollection;
if (locationPts != null)
{
ConnectionPoint srcConnPoint = null, destConnPoint = null;
if (connectorModelItem.ItemType == typeof(Transition))
{
object srcConnPointIndex = this.ViewStateService.RetrieveViewState(connectorModelItem, SrcConnectionPointIndexStateKey);
object destConnPointIndex = this.ViewStateService.RetrieveViewState(connectorModelItem, DestConnectionPointIndexStateKey);
if (srcConnPointIndex != null)
{
List<ConnectionPoint> srcConnPoints = GetConnectionPoints(source);
ConnectionPoint viewStateSrcConnPoint = srcConnPoints.ElementAt((int)srcConnPointIndex);
if (viewStateSrcConnPoint != null)
{
srcConnPoint = viewStateSrcConnPoint;
}
}
else if (connectorModelItem.Properties[TransitionDesigner.TriggerPropertyName].Value != null)
{
srcConnPoint = StateContainerEditor.GetSrcConnectionPointForSharedTrigger(source, connectorModelItem);
}
if (destConnPointIndex != null)
{
List<ConnectionPoint> destConnPoints = GetConnectionPoints(dest);
ConnectionPoint viewStateDestConnPoint = destConnPoints.ElementAt((int)destConnPointIndex);
if (viewStateDestConnPoint != null && GetEmptyConnectionPoints(dest).Contains(viewStateDestConnPoint))
{
destConnPoint = viewStateDestConnPoint;
}
}
}
if (srcConnPoint == null)
{
srcConnPoint = GetConnectionPoint(source, locationPts[0]);
}
if (destConnPoint == null)
{
destConnPoint = GetConnectionPoint(dest, locationPts[locationPts.Count - 1]);
}
bool shouldReroute = false;
if (srcConnPoint == null && destConnPoint == null)
{
StateContainerEditor.GetEmptySrcDestConnectionPoints(source, dest, out srcConnPoint, out destConnPoint);
shouldReroute = true;
}
else if (srcConnPoint == null && destConnPoint != null)
{
List<ConnectionPoint> srcConnectionPoints = GetEmptyConnectionPoints(source);
if (srcConnectionPoints.Count > 0)
{
srcConnPoint = StateContainerEditor.GetClosestConnectionPointNotOfType(destConnPoint, srcConnectionPoints, ConnectionPointKind.Incoming);
}
shouldReroute = true;
}
else if (destConnPoint == null && srcConnPoint != null)
{
List<ConnectionPoint> destConnectionPoints = GetEmptyConnectionPoints(dest);
if (destConnectionPoints.Count > 0)
{
destConnPoint = StateContainerEditor.GetClosestConnectionPointNotOfType(srcConnPoint, destConnectionPoints, ConnectionPointKind.Outgoing);
}
shouldReroute = true;
}
if (srcConnPoint != null && destConnPoint != null)
{
if (shouldReroute || !IsViewStateValid(locationPts))
{
PointCollection connectorPoints = new PointCollection(ConnectorRouter.Route(this.panel, srcConnPoint, destConnPoint));
this.ViewStateService.StoreViewState(connectorModelItem, ConnectorLocationViewStateKey, connectorPoints);
connector = CreateConnector(srcConnPoint, destConnPoint, connectorPoints, connectorModelItem);
}
else
{
connector = this.CreateConnector(srcConnPoint, destConnPoint, locationPts, connectorModelItem);
}
}
}
return connector;
}
PointCollection CreatePointCollectionForAutoConnectOrAutoSplit(UIElement sourceDesigner,
UIElement destinationDesigner,
ModelItem connectorModelItem,
ref ConnectionPoint sourceConnectionPoint,
ref ConnectionPoint destinationConnectionPoint)
{
PointCollection points = null;
if (this.activeSrcConnectionPointForAutoSplit != null && this.activeSrcConnectionPointForAutoSplit.ParentDesigner == sourceDesigner)
{
sourceConnectionPoint = this.activeSrcConnectionPointForAutoSplit;
this.activeSrcConnectionPointForAutoSplit = null;
destinationConnectionPoint = StateContainerEditor.GetConnectionPointClosestToEdgeMidPoint(destinationDesigner, StateContainerEditor.GetEmptyConnectionPoints(destinationDesigner), this.entryEdgeForAutoSplit);
}
else if (this.activeSrcConnectionPoint != null && this.activeSrcConnectionPoint.ParentDesigner == sourceDesigner)
{
sourceConnectionPoint = this.activeSrcConnectionPoint;
this.activeSrcConnectionPoint = null;
}
else
{
if (connectorModelItem.ItemType == typeof(Transition) && connectorModelItem.Properties[TransitionDesigner.TriggerPropertyName].Value != null)
{
sourceConnectionPoint = StateContainerEditor.GetSrcConnectionPointForSharedTrigger(sourceDesigner, connectorModelItem);
}
}
if (this.activeDestConnectionPointForAutoSplit != null && this.activeDestConnectionPointForAutoSplit.ParentDesigner == destinationDesigner)
{
destinationConnectionPoint = this.activeDestConnectionPointForAutoSplit;
this.activeDestConnectionPointForAutoSplit = null;
sourceConnectionPoint = StateContainerEditor.GetConnectionPointClosestToEdgeMidPoint(sourceDesigner, StateContainerEditor.GetEmptyConnectionPoints(sourceDesigner), this.exitEdgeForAutoSplit);
}
if (sourceConnectionPoint != null && destinationConnectionPoint == null)
{
destinationConnectionPoint = StateContainerEditor.GetClosestDestConnectionPoint(sourceConnectionPoint, destinationDesigner);
}
else if (sourceConnectionPoint == null && destinationConnectionPoint != null)
{
sourceConnectionPoint = StateContainerEditor.GetClosestSrcConnectionPoint(sourceDesigner, destinationConnectionPoint);
}
else if (sourceConnectionPoint == null && destinationConnectionPoint == null)
{
StateContainerEditor.GetEmptySrcDestConnectionPoints(sourceDesigner, destinationDesigner, out sourceConnectionPoint, out destinationConnectionPoint);
}
if (sourceConnectionPoint != null && destinationConnectionPoint != null)
{
points = new PointCollection(ConnectorRouter.Route(this.panel, sourceConnectionPoint, destinationConnectionPoint));
}
return points;
}
// Create a connector by view state of the connector model item, and if failed, just create a connector using the connection points
// of the source and destination designers. Then add the connector created to the free form panel.
void AddConnector(
UIElement sourceDesigner,
UIElement destinationDesigner,
ModelItem connectorModelItem,
ConnectionPoint sourceConnectionPoint = null,
ConnectionPoint destinationConnectionPoint = null)
{
// Check whether connector already exists.
// If users programmatically add state with transition to the stateMachine, AddConnector will be called twice for one connectorModelItem.
if (GetConnectorInStateMachine(connectorModelItem) != null)
{
return;
}
Connector connector = CreateConnectorByConnectorModelItemViewState(sourceDesigner, destinationDesigner, connectorModelItem);
if (connector == null)
{
PointCollection connectorPoints = this.CreatePointCollectionForAutoConnectOrAutoSplit(sourceDesigner, destinationDesigner, connectorModelItem, ref sourceConnectionPoint, ref destinationConnectionPoint);
if (connectorPoints != null)
{
connector = CreateConnector(sourceConnectionPoint, destinationConnectionPoint, connectorPoints, connectorModelItem);
this.ViewStateService.StoreViewState(connectorModelItem, ConnectorLocationViewStateKey, connectorPoints);
}
}
if (connector != null)
{
this.panel.Children.Add(connector);
}
}
void Remove(Connector connector)
{
ConnectionPoint srcConnectionPoint = FreeFormPanel.GetSourceConnectionPoint(connector);
ConnectionPoint destConnectionPoint = FreeFormPanel.GetDestinationConnectionPoint(connector);
// Update ConnectionPoints
srcConnectionPoint.AttachedConnectors.Remove(connector);
destConnectionPoint.AttachedConnectors.Remove(connector);
StateContainerEditor stateMachineContainer = this.GetStateMachineContainerEditor();
stateMachineContainer.panel.Children.Remove(connector);
if (stateMachineContainer.selectedConnector == connector)
{
stateMachineContainer.ClearSelectedConnector();
}
if (connector is ConnectorWithStartDot)
{
connector.StartDot.MouseDown -= new MouseButtonEventHandler(OnConnectorStartDotMouseDown);
connector.StartDot.MouseUp -= new MouseButtonEventHandler(OnConnectorStartDotMouseUp);
}
connector.GotKeyboardFocus -= new KeyboardFocusChangedEventHandler(OnConnectorGotKeyboardFocus);
connector.RequestBringIntoView -= new RequestBringIntoViewEventHandler(OnConnectorRequestBringIntoView);
connector.GotFocus -= new RoutedEventHandler(OnConnectorGotFocus);
connector.MouseDoubleClick -= new MouseButtonEventHandler(OnConnectorMouseDoubleClick);
connector.MouseDown -= new MouseButtonEventHandler(OnConnectorMouseDown);
connector.KeyDown -= new KeyEventHandler(OnConnectorKeyDown);
connector.ContextMenuOpening -= new ContextMenuEventHandler(OnConnectorContextMenuOpening);
connector.Unloaded -= new RoutedEventHandler(OnConnectorUnloaded);
}
#endregion
#region ConnectionPoint
MultiBinding GetConnectionPointBinding(FrameworkElement element, double widthFraction, double heightFraction)
{
Fx.Assert(element != null, "FrameworkElement is null.");
MultiBinding bindings = new MultiBinding();
Binding sizeBinding = new Binding();
sizeBinding.Source = element;
sizeBinding.Path = new PropertyPath(FreeFormPanel.ChildSizeProperty);
Binding locationBinding = new Binding();
locationBinding.Source = element;
locationBinding.Path = new PropertyPath(FreeFormPanel.LocationProperty);
bindings.Bindings.Add(sizeBinding);
bindings.Bindings.Add(locationBinding);
bindings.Converter = new ConnectionPointConverter();
bindings.ConverterParameter = new List<Object> { widthFraction, heightFraction, element.Margin };
return bindings;
}
//widthFraction and heightFraction determine the location of the ConnectionPoint on the UIElement.
ConnectionPoint CreateConnectionPoint(UIElement element, double widthFraction, double heightFraction, EdgeLocation location, ConnectionPointKind type)
{
ConnectionPoint connectionPoint = new ConnectionPoint();
connectionPoint.EdgeLocation = location;
connectionPoint.PointType = type;
connectionPoint.ParentDesigner = element;
connectionPoint.IsEnabled = false;
BindingOperations.SetBinding(connectionPoint, ConnectionPoint.LocationProperty, GetConnectionPointBinding(element as FrameworkElement, widthFraction, heightFraction));
return connectionPoint;
}
void PopulateConnectionPoints(UIElement view)
{
view.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
List<ConnectionPoint> connectionPoints = new List<ConnectionPoint>();
const double connectionPointRatio = 0.05;
ConnectionPointKind connectionPointType = ConnectionPointKind.Default;
if (view is VirtualizedContainerService.VirtualizingContainer && IsFinalState(((VirtualizedContainerService.VirtualizingContainer)view).ModelItem))
{
connectionPointType = ConnectionPointKind.Incoming;
}
else if (view is StartSymbol)
{
connectionPointType = ConnectionPointKind.Outgoing;
}
for (int ii = 1; ii <= ConnectionPointNum; ii++)
{
connectionPoints.Add(CreateConnectionPoint(view, 1, ii * connectionPointRatio, EdgeLocation.Right, connectionPointType));
connectionPoints.Add(CreateConnectionPoint(view, 0, ii * connectionPointRatio, EdgeLocation.Left, connectionPointType));
connectionPoints.Add(CreateConnectionPoint(view, ii * connectionPointRatio, 0, EdgeLocation.Top, connectionPointType));
connectionPoints.Add(CreateConnectionPoint(view, ii * connectionPointRatio, 1, EdgeLocation.Bottom, connectionPointType));
}
StateContainerEditor.SetConnectionPoints(view, connectionPoints);
}
List<ConnectionPoint> ConnectionPointsToShow(UIElement element)
{
bool isCreatingConnector = this.IsCreatingConnector();
List<ConnectionPoint> connectionPointsToShow = new List<ConnectionPoint>();
if (element is StartSymbol)
{
// Don't allow moving the start of a transition to the initial node.
if (isCreatingConnector || this.IsMovingStartOfConnectorForTransition())
{
return connectionPointsToShow;
}
// Don't allow creating more than one connectors from the initial node.
if ((StateContainerEditor.GetOutgoingConnectors(element).Count > 0) && !this.IsMovingStartOfConnectorFromInitialNode())
{
return connectionPointsToShow;
}
}
else if (element is VirtualizedContainerService.VirtualizingContainer)
{
VirtualizedContainerService.VirtualizingContainer container = (VirtualizedContainerService.VirtualizingContainer)element;
// Don't allow setting final state as the initial state
if (IsFinalState(container.ModelItem) && this.IsCreatingConnectorFromInitialNode())
{
return connectionPointsToShow;
}
// Don't allow moving the start of the initial node connector to a state
if (this.IsMovingStartOfConnectorFromInitialNode())
{
return connectionPointsToShow;
}
}
List<ConnectionPoint> connectionPoints = StateContainerEditor.GetConnectionPoints(element);
if (isCreatingConnector)
{
connectionPointsToShow.AddRange(connectionPoints.Where<ConnectionPoint>(
(p) => { return p.PointType != ConnectionPointKind.Outgoing && p.AttachedConnectors.Count == 0; }));
}
else
{
connectionPointsToShow.AddRange(connectionPoints.Where<ConnectionPoint>(
(p) => { return p.PointType != ConnectionPointKind.Incoming && p.AttachedConnectors.Count == 0; }));
}
return connectionPointsToShow;
}
private void RemoveConnectionPointsAdorner(UIElement adornedElement)
{
IEnumerable<Adorner> adornersRemoved = RemoveAdorner(adornedElement, typeof(ConnectionPointsAdorner));
Fx.Assert(adornersRemoved.Count() <= 1, "There should be at most one ConnectionPointsAdorner");
if (adornersRemoved.Count() == 1)
{
ConnectionPointsAdorner adorner = (ConnectionPointsAdorner)adornersRemoved.First();
Fx.Assert(object.Equals(this.activeConnectionPointsAdorner, adorner), "The adorner removed should be the same as the active adorner.");
this.activeConnectionPointsAdorner = null;
foreach (ConnectionPoint connectionPoint in adorner.ConnectionPoints)
{
connectionPoint.IsEnabled = false;
if (object.Equals(this.activeConnectionPoint, connectionPoint))
{
this.activeConnectionPoint = null;
}
}
}
}
private List<ConnectionPoint> GetAvailableConnectionPoint(UIElement designer)
{
List<ConnectionPoint> connectionPoints = new List<ConnectionPoint>(StateContainerEditor.GetConnectionPoints(designer));
List<ConnectionPoint> usedConnectionPoints = new List<ConnectionPoint>();
foreach (ConnectionPoint connectionPoint in connectionPoints.Reverse<ConnectionPoint>())
{
if (connectionPoint.AttachedConnectors.Count > 0)
{
usedConnectionPoints.Add(connectionPoint);
connectionPoints.Remove(connectionPoint);
}
}
// The active connection point may haven't had connector yet
if (connectionPoints.Contains(this.activeSrcConnectionPoint))
{
connectionPoints.Remove(this.activeSrcConnectionPoint);
usedConnectionPoints.Add(this.activeSrcConnectionPoint);
}
foreach (ConnectionPoint connectionPoint in connectionPoints.Reverse<ConnectionPoint>())
{
foreach (ConnectionPoint usedConnectionPoint in usedConnectionPoints)
{
if (DesignerGeometryHelper.ManhattanDistanceBetweenPoints(usedConnectionPoint.Location, connectionPoint.Location) < ConnectionPointMargin)
{
connectionPoints.Remove(connectionPoint);
break;
}
}
}
return connectionPoints;
}
private void UpdateActiveConnectionPoint(MouseEventArgs e)
{
List<ConnectionPoint> connectionPoints = GetAvailableConnectionPoint(this.activeConnectionPointsAdorner.AdornedElement);
Point mousePosition = e.GetPosition(this.GetStateMachineContainerEditor());
double minDist;
ConnectionPoint closestConnectionPoint = ConnectionPoint.GetClosestConnectionPoint(connectionPoints, mousePosition, out minDist);
if (closestConnectionPoint == null)
{
return;
}
foreach (ConnectionPoint connectionPoint in this.activeConnectionPointsAdorner.ConnectionPoints)
{
if (object.Equals(connectionPoint, closestConnectionPoint))
{
connectionPoint.IsEnabled = true;
this.activeConnectionPoint = connectionPoint;
}
else
{
connectionPoint.IsEnabled = false;
if (object.Equals(connectionPoint, this.activeConnectionPoint))
{
this.activeConnectionPoint = null;
}
}
}
this.activeConnectionPointsAdorner.InvalidateVisual();
}
#endregion
#region ChildElementEventHandlers
void OnChildElementMouseEnter(object sender, MouseEventArgs e)
{
UIElement senderElement = sender as UIElement;
if (senderElement != null && !this.IsReadOnly)
{
if (senderElement is VirtualizedContainerService.VirtualizingContainer)
{
VirtualizedContainerService.VirtualizingContainer stateDesigner =
VisualTreeUtils.FindVisualAncestor<VirtualizedContainerService.VirtualizingContainer>(Mouse.DirectlyOver as DependencyObject);
// We don't want to show the connection points if the mouse is not directly over this state
if (stateDesigner != senderElement)
{
return;
}
}
AddConnectionPointsAdorner(senderElement);
// Remove the adorner on the state designer when entering its child
if (this.ModelItem.ItemType == typeof(State))
{
VirtualizedContainerService.VirtualizingContainer parent =
VisualTreeUtils.FindVisualAncestor<VirtualizedContainerService.VirtualizingContainer>(this);
this.RemoveConnectionPointsAdorner(parent);
}
}
}
void AddConnectionPointsAdorner(UIElement element)
{
bool isSelected = false;
if (element is VirtualizedContainerService.VirtualizingContainer)
{
isSelected = (((Selection)this.Context.Items.GetValue<Selection>()).SelectedObjects as ICollection<ModelItem>).Contains(((VirtualizedContainerService.VirtualizingContainer)element).ModelItem);
}
ConnectionPointsAdorner connectionPointsAdorner = new StateMachineConnectionPointsAdorner(element, ConnectionPointsToShow(element), isSelected);
if ((element is VirtualizedContainerService.VirtualizingContainer) && ((VirtualizedContainerService.VirtualizingContainer)element).ModelItem.ItemType == typeof(State))
{
connectionPointsAdorner.ToolTip = SR.TransitionConnectionPointTooltip;
}
else if (element is StartSymbol)
{
connectionPointsAdorner.ToolTip = SR.InitialStateConnectionPointTooltip;
}
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(element);
Fx.Assert(adornerLayer != null, "Cannot get AdornerLayer.");
adornerLayer.Add(connectionPointsAdorner);
// The outmostEditor should handle all the connection point related events for all its descendants
StateContainerEditor stateMachineContainer = this.GetStateMachineContainerEditor();
connectionPointsAdorner.MouseDown += new MouseButtonEventHandler(stateMachineContainer.OnConnectionPointMouseDown);
connectionPointsAdorner.MouseUp += new MouseButtonEventHandler(stateMachineContainer.OnConnectionPointMouseUp);
connectionPointsAdorner.MouseLeave += new MouseEventHandler(stateMachineContainer.OnConnectionPointMouseLeave);
this.activeConnectionPointsAdorner = connectionPointsAdorner;
}
void OnChildElementMouseLeave(object sender, MouseEventArgs e)
{
bool removeConnectionPointsAdorner = true;
if (Mouse.DirectlyOver != null)
{
removeConnectionPointsAdorner = !typeof(ConnectionPointsAdorner).IsAssignableFrom(Mouse.DirectlyOver.GetType());
}
if (removeConnectionPointsAdorner)
{
this.RemoveConnectionPointsAdorner(sender as UIElement);
// Add connection points adorner to its containing state
VirtualizedContainerService.VirtualizingContainer stateDesigner = VisualTreeUtils.FindVisualAncestor<VirtualizedContainerService.VirtualizingContainer>(this);
StateContainerEditor parentContainer = VisualTreeUtils.FindVisualAncestor<StateContainerEditor>(stateDesigner);
if (stateDesigner != null && parentContainer != null && !parentContainer.IsReadOnly)
{
this.AddConnectionPointsAdorner(stateDesigner);
}
}
}
void OnChildElementSizeChanged(object sender, SizeChangedEventArgs e)
{
VirtualizedContainerService.VirtualizingContainer view = sender as VirtualizedContainerService.VirtualizingContainer;
if (view != null)
{
// Such size changed events are a result of changes already committed in the UI. Hence we do not want to react to such view state changes.
// Using internalViewStateChange flag for that purpose.
this.internalViewStateChange = true;
ModelItem storageModelItem = view.ModelItem;
this.ViewStateService.StoreViewState(storageModelItem, ShapeSizeViewStateKey, ((UIElement)sender).DesiredSize);
this.internalViewStateChange = false;
}
}
#endregion
#region ConnectorEventHandlers
void OnConnectorStartDotMouseUp(object sender, MouseButtonEventArgs e)
{
this.activeSrcConnectionPoint = null;
RemoveAdorner(this.panel, typeof(ConnectorCreationAdorner));
}
void OnConnectorStartDotMouseDown(object sender, MouseButtonEventArgs e)
{
DependencyObject startDot = (DependencyObject)sender;
Connector connector = VisualTreeUtils.FindVisualAncestor<Connector>(startDot);
this.activeSrcConnectionPoint = FreeFormPanel.GetSourceConnectionPoint(connector);
e.Handled = true;
}
void OnConnectorMouseDown(object sender, MouseButtonEventArgs e)
{
Connector connector = (Connector)sender;
if (this.panel.Children.Contains(connector))
{
this.selectedConnector = connector;
}
// In order to not let WorkflowViewElement handle the event, which would cause the
// ConnectorEditor to be removed.
e.Handled = true;
}
void OnConnectorUnloaded(object sender, RoutedEventArgs e)
{
ModelItem primarySelection = this.Context.Items.GetValue<Selection>().PrimarySelection;
if (object.Equals(primarySelection, StateContainerEditor.GetConnectorModelItem(sender as DependencyObject)))
{
if (primarySelection != null)
{
Selection.Toggle(this.Context, primarySelection);
}
}
}
// Marking e.Handled = true to avoid scrolling in large workflows to bring the
// area of a connector in the center of the view region.
void OnConnectorRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
{
e.Handled = true;
}
// Marking e.Handled true for the case where a connector is clicked.
// This is to prevent WorkflowViewElement class from making StateMachine as the current selection.
void OnConnectorGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
e.Handled = true;
}
void OnConnectorGotFocus(object sender, RoutedEventArgs e)
{
Connector connector = e.Source as Connector;
if (this.panel.connectorEditor == null || !connector.Equals(this.panel.connectorEditor.Connector))
{
this.panel.RemoveConnectorEditor();
this.panel.connectorEditor = new ConnectorEditor(this.panel, connector);
}
if (this.panel.Children.Contains(connector))
{
this.updatingSelectedConnector = true;
ModelItem connectorModelItem = StateContainerEditor.GetConnectorModelItem(connector);
Selection newSelection = new Selection();
if (connectorModelItem != null && connectorModelItem.ItemType == typeof(Transition))
{
newSelection = new Selection(connectorModelItem);
}
this.Context.Items.SetValue(newSelection);
this.selectedConnector = connector;
this.updatingSelectedConnector = false;
if (connectorModelItem.ItemType == typeof(Transition))
{
// Populate the source and destination States's View if it is still in Virtualized mode.
ModelItem destinationState = connectorModelItem.Properties[TransitionDesigner.ToPropertyName].Value;
PopulateVirtualizingContainer(destinationState);
ModelItem sourceState = StateContainerEditor.GetParentStateModelItemForTransition(connectorModelItem);
PopulateVirtualizingContainer(sourceState);
// Assign its destination State's View on the connector model item for copy/paste function.
((IModelTreeItem)connectorModelItem).SetCurrentView(destinationState.View);
}
e.Handled = true;
}
}
private void OnSelectionChanged(Selection selection)
{
// If selection changed, remove ConnectorEditor if existed.
// Only if the selection changed is caused by adding ConnectorEditor when OnConnectorGotFocus.
if (!this.updatingSelectedConnector && this.panel != null && this.panel.connectorEditor != null)
{
this.panel.RemoveConnectorEditor();
}
}
void OnConnectorKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter || e.Key == Key.Return)
{
this.DesignerView.MakeRootDesigner(StateContainerEditor.GetConnectorModelItem(sender as DependencyObject));
e.Handled = true;
}
}
void OnConnectorMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
ModelItem connectorModelItem = StateContainerEditor.GetConnectorModelItem(sender as DependencyObject);
if (connectorModelItem != null && connectorModelItem.ItemType == typeof(Transition))
{
this.DesignerView.MakeRootDesigner(connectorModelItem);
e.Handled = true;
}
}
}
void OnConnectorContextMenuOpening(object sender, ContextMenuEventArgs e)
{
// Disable context menu
e.Handled = true;
}
#endregion
#region ConnectionPointEventHandlers
void OnConnectionPointMouseDown(object sender, MouseButtonEventArgs e)
{
UIElement srcElement = ((Adorner)sender).AdornedElement as UIElement;
this.activeSrcConnectionPoint = ConnectionPointHitTest(srcElement, e.GetPosition(this.panel));
if (this.activeSrcConnectionPoint != null &&
!this.activeSrcConnectionPoint.ParentDesigner.IsKeyboardFocusWithin)
{
// If a floating annotation is visible, it needs to lose the keyboard focus
// to hide itself again (bug 200739). Therefore, selecting the src connection
// point would give the keyboard focus back to its source parent state.
Keyboard.Focus(VirtualizedContainerService.TryGetVirtualizedElement(this.activeSrcConnectionPoint.ParentDesigner));
}
e.Handled = true;
}
void OnConnectionPointMouseLeave(object sender, MouseEventArgs e)
{
UIElement adornedElement = ((Adorner)sender).AdornedElement as UIElement;
this.RemoveConnectionPointsAdorner(adornedElement);
}
void OnConnectionPointMouseUp(object sender, MouseButtonEventArgs e)
{
UIElement dest = ((Adorner)sender).AdornedElement as UIElement;
if (this.activeSrcConnectionPoint != null)
{
ConnectionPoint destConnectionPoint = ConnectionPointHitTest(dest, e.GetPosition(this.panel));
if (destConnectionPoint != null && !this.activeSrcConnectionPoint.Equals(destConnectionPoint))
{
ConnectorCreationResult result = CreateConnectorGesture(this.activeSrcConnectionPoint, destConnectionPoint, null, false);
if (result != ConnectorCreationResult.Success)
{
StateContainerEditor.ReportConnectorCreationError(result);
}
}
this.activeSrcConnectionPoint = null;
RemoveAdorner(this.panel, typeof(ConnectorCreationAdorner));
}
else
{
//This will cause the FreeFormPanel to handle the event and is useful while moving a connector end point.
this.lastConnectionPointMouseUpElement = dest;
dest.RaiseEvent(e);
}
this.RemoveConnectionPointsAdorner(dest);
}
#endregion
#region FreeFormPanelEventHandlers
void OnFreeFormPanelLocationChanged(object sender, System.Activities.Presentation.FreeFormEditing.LocationChangedEventArgs e)
{
Fx.Assert(sender is UIElement, "Sender should be of type UIElement");
Connector movedConnector = sender as Connector;
if (movedConnector != null)
{
//ViewState is undoable only when a user gesture moves a connector. If the FreeFormPanel routes a connector,
//the change is not undoable.
bool isUndoableViewState = false;
ModelItem connectorModelItem = StateContainerEditor.GetConnectorModelItem(movedConnector);
PointCollection existingViewState = this.ViewStateService.RetrieveViewState(connectorModelItem, ConnectorLocationViewStateKey) as PointCollection;
if (existingViewState != null && existingViewState.Count > 0 && movedConnector.Points.Count > 0
&& existingViewState[0].Equals(movedConnector.Points[0]) && existingViewState[existingViewState.Count - 1].Equals(movedConnector.Points[movedConnector.Points.Count - 1]))
{
isUndoableViewState = true;
}
StoreConnectorLocationViewState(movedConnector, isUndoableViewState);
}
else
{
//This is called only when a shape without ViewState is auto-layout'd by the FreeFormPanel.
UIElement view = sender as UIElement;
if (view != null)
{
StoreShapeLocationViewState(view, e.NewLocation);
}
}
}
void UpdateStateMachineOnConnectorMoved(ConnectionPoint knownConnectionPoint, Point newPoint, Connector movedConnector, bool isConnectorStartMoved)
{
HitTestResult hitTestResult = VisualTreeHelper.HitTest(this.panel, newPoint);
if (hitTestResult == null)
{
return;
}
UIElement newViewElement = null;
ConnectionPoint newConnectionPoint = null;
//The case where the Connector is dropped on a ConnectionPoint.
if (this.lastConnectionPointMouseUpElement != null)
{
newConnectionPoint = StateContainerEditor.ConnectionPointHitTest(this.lastConnectionPointMouseUpElement, newPoint);
if (newConnectionPoint != null)
{
newViewElement = this.lastConnectionPointMouseUpElement;
}
this.lastConnectionPointMouseUpElement = null;
}
//The case where the link is dropped on a shape.
if (newViewElement == null)
{
newViewElement = VisualTreeUtils.FindVisualAncestor<WorkflowViewElement>(hitTestResult.VisualHit);
}
if (newViewElement != null)
{
if (this.panel.IsAncestorOf(newViewElement))
{
using (EditingScope es = (EditingScope)this.ModelItem.BeginEdit(SR.MoveLink))
{
// Remove the old connector ModelItem
this.DeleteConnectorModelItem(movedConnector, true);
// Create new connector
ConnectorCreationResult result = ConnectorCreationResult.OtherFailure;
if (!isConnectorStartMoved)
{
if (newConnectionPoint == null)
{
result = CreateConnectorGesture(knownConnectionPoint, newViewElement, movedConnector, false);
}
else
{
result = CreateConnectorGesture(knownConnectionPoint, newConnectionPoint, movedConnector, false);
}
}
else
{
// Don't allow moving the start of the initial node connector to a state
if (!(newViewElement is StateDesigner && StateContainerEditor.IsConnectorFromInitialNode(movedConnector)))
{
if (newConnectionPoint == null)
{
result = CreateConnectorGesture(newViewElement, knownConnectionPoint, movedConnector, true);
}
else
{
result = CreateConnectorGesture(newConnectionPoint, knownConnectionPoint, movedConnector, true);
}
}
}
if (result == ConnectorCreationResult.Success)
{
es.Complete();
}
else
{
StateContainerEditor.ReportConnectorCreationError(result);
es.Revert();
}
}
}
}
}
void OnFreeFormPanelConnectorMoved(object sender, ConnectorMovedEventArgs e)
{
Connector movedConnector = sender as Connector;
int movedEndConnectorPointIndex = movedConnector.Points.Count - 1;
int newEndConnectorPointIndex = e.NewConnectorLocation.Count - 1;
if (movedConnector != null)
{
Fx.Assert(e.NewConnectorLocation.Count > 0, "Invalid connector editor");
if (!e.NewConnectorLocation[0].Equals(movedConnector.Points[0]))
{
// source moved
ConnectionPoint destConnPoint = FreeFormPanel.GetDestinationConnectionPoint(movedConnector);
UpdateStateMachineOnConnectorMoved(destConnPoint, e.NewConnectorLocation[0], movedConnector, true);
}
else if (!e.NewConnectorLocation[newEndConnectorPointIndex].Equals(movedConnector.Points[movedEndConnectorPointIndex]))
{
// destination moved
ConnectionPoint srcConnPoint = FreeFormPanel.GetSourceConnectionPoint(movedConnector);
Point destPoint = e.NewConnectorLocation[newEndConnectorPointIndex];
UpdateStateMachineOnConnectorMoved(srcConnPoint, destPoint, movedConnector, false);
}
this.selectedConnector = movedConnector;
}
}
// This is to keep this.selectedConnector up to date.
// Cases included: 1. create a connector, select it and undo, 2. move a connector from one shape to another.
void OnFreeFormPanelLayoutUpdated(object sender, EventArgs e)
{
if (this.selectedConnector != null && !this.panel.Children.Contains(this.selectedConnector))
{
this.ClearSelectedConnector();
}
}
void OnFreeFormPanelRequiredSizeChanged(object sender, RequiredSizeChangedEventArgs e)
{
this.requiredSize = e.NewRequiredSize;
Dispatcher.BeginInvoke(new Action(() =>
{
if (this.requiredSize.Width > this.StateContainerWidth)
{
this.ViewStateService.StoreViewState(
this.ModelItem,
StateContainerEditor.StateContainerWidthViewStateKey,
this.requiredSize.Width);
}
if (this.requiredSize.Height > this.StateContainerHeight)
{
this.ViewStateService.StoreViewState(
this.ModelItem,
StateContainerEditor.StateContainerHeightViewStateKey,
this.requiredSize.Height);
}
}));
}
#endregion
#region StateContainerGridEventHandlers
void OnStateContainerGridMouseLeave(object sender, MouseEventArgs e)
{
bool endLinkCreation = !IsVisualHit(sender as UIElement, sender as UIElement, e.GetPosition(sender as IInputElement));
if (endLinkCreation)
{
RemoveAdorner(this.panel, typeof(ConnectorCreationAdorner));
this.activeSrcConnectionPoint = null;
}
}
void OnStateContainerGridMouseMove(object sender, MouseEventArgs e)
{
if (this.activeSrcConnectionPoint != null)
{
Point[] points = ConnectorRouter.Route(this.panel, this.activeSrcConnectionPoint, e);
if (points == null)
{
e.Handled = true;
return;
}
List<Point> segments = new List<Point>(points);
// Remove the previous adorner.
RemoveAdorner(this.panel, typeof(ConnectorCreationAdorner));
// Add new adorner.
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.panel);
Fx.Assert(adornerLayer != null, "Adorner Layer does not exist");
ConnectorCreationAdorner newAdorner = new ConnectorCreationAdorner(this.panel, segments);
adornerLayer.Add(newAdorner);
e.Handled = true;
}
}
void OnStateContainerGridPreviewMouseMove(object sender, MouseEventArgs e)
{
if (this.activeConnectionPointsAdorner != null)
{
this.UpdateActiveConnectionPoint(e);
}
// Creating connector
if (this.activeSrcConnectionPoint != null)
{
AutoScrollHelper.AutoScroll(e, this, 1);
}
// Reconnecting connector
else if (this.panel.connectorEditor != null && (this.panel.connectorEditor.IsConnectorEndBeingMoved || this.panel.connectorEditor.IsConnectorStartBeingMoved))
{
AutoScrollHelper.AutoScroll(e, this, 1);
}
}
void OnStateContainerGridPreviewMouseUp(object sender, MouseButtonEventArgs e)
{
VirtualizedContainerService.VirtualizingContainer destElement = VisualTreeUtils.FindVisualAncestor<VirtualizedContainerService.VirtualizingContainer>(e.OriginalSource as DependencyObject);
if (destElement != null && destElement.IsPopulated && destElement.Child is StateDesigner)
{
if (this.activeSrcConnectionPoint != null)
{
ConnectorCreationResult result = this.CreateConnectorGesture(this.activeSrcConnectionPoint, destElement, null, false);
if (result != ConnectorCreationResult.Success)
{
StateContainerEditor.ReportConnectorCreationError(result);
}
this.RemoveConnectionPointsAdorner(destElement);
}
}
if (this.activeSrcConnectionPoint != null)
{
this.activeSrcConnectionPoint = null;
RemoveAdorner(this.panel, typeof(ConnectorCreationAdorner));
}
}
void SetEndPointsAndInvalidateViewState(Connector connector, Vector offset, bool offsetSrc)
{
ConnectionPoint srcConnectionPoint = FreeFormPanel.GetSourceConnectionPoint(connector);
ConnectionPoint destConnectionPoint = FreeFormPanel.GetDestinationConnectionPoint(connector);
Point src, dest;
if (offsetSrc)
{
src = Point.Add(FreeFormPanel.GetLocationRelativeToOutmostPanel(srcConnectionPoint), offset);
dest = FreeFormPanel.GetLocationRelativeToOutmostPanel(destConnectionPoint);
}
else
{
src = FreeFormPanel.GetLocationRelativeToOutmostPanel(srcConnectionPoint);
dest = Point.Add(FreeFormPanel.GetLocationRelativeToOutmostPanel(destConnectionPoint), offset);
}
PointCollection points = new PointCollection();
Point invalidPoint = new Point(-1, -1);
points.Add(src);
points.Add(invalidPoint); // this invalidates the view state
points.Add(dest);
this.StoreConnectorLocationViewState(StateContainerEditor.GetConnectorModelItem(connector), points, true);
}
void OffsetConnectorViewState(UIElement view, Point oldLocation, Point newLocation, bool offsetNonContainedConnectors)
{
// There is no need to do anything for the StartSymbol
if (view is VirtualizedContainerService.VirtualizingContainer)
{
Vector offset = new Vector(newLocation.X - oldLocation.X, newLocation.Y - oldLocation.Y);
// connectors whose dest points are outside the state and the src points are inside/on the state
HashSet<Connector> outgoingConnectors = new HashSet<Connector>();
// connectors whose src points are outside the state and the dest points are inside/on the state
HashSet<Connector> incomingConnectors = new HashSet<Connector>();
// connectors whose src points and dest points are both inside/on the state
HashSet<ModelItem> containedTransitions = new HashSet<ModelItem>();
if (view != null)
{
// Here the incomingConnectors and outgoingConnectors contains connectors whose src points and dest
// points are both inside/on the state; they will be removed later on
List<Connector> connectors = StateContainerEditor.GetIncomingConnectors(view);
foreach (Connector connector in connectors)
{
incomingConnectors.Add(connector);
}
connectors = StateContainerEditor.GetOutgoingConnectors(view);
foreach (Connector connector in connectors)
{
outgoingConnectors.Add(connector);
}
}
// Add common connectors to the containedConnectors set and remove them
// from the outgoingConnectors and incomingConnectors sets
foreach (Connector connector in outgoingConnectors.Reverse<Connector>())
{
if (incomingConnectors.Contains(connector))
{
containedTransitions.Add(StateContainerEditor.GetConnectorModelItem(connector));
outgoingConnectors.Remove(connector);
incomingConnectors.Remove(connector);
}
}
// For contained connectors, we offset all the points.
this.OffsetLocationViewStates(offset, null, containedTransitions, true);
if (offsetNonContainedConnectors)
{
// For incoming connectors, we offset the end point and invalidate the view state.
// This way the start and end point will still connect to the same connection points
// on the source and destination shapes and later on the connector will be rerouted using
// those two fixed points.
foreach (Connector connector in incomingConnectors)
{
this.SetEndPointsAndInvalidateViewState(connector, offset, false);
}
// for outgoing connectors, we offset the start point and invalidate the view state.
foreach (Connector connector in outgoingConnectors)
{
this.SetEndPointsAndInvalidateViewState(connector, offset, true);
}
}
else
{
HashSet<ModelItem> nonSelfTransitions = new HashSet<ModelItem>();
foreach (Connector connector in incomingConnectors)
{
nonSelfTransitions.Add(StateContainerEditor.GetConnectorModelItem(connector));
}
foreach (Connector connector in outgoingConnectors)
{
nonSelfTransitions.Add(StateContainerEditor.GetConnectorModelItem(connector));
}
// Store ViewState for all non-self transitions to support undo/redo.
this.OffsetLocationViewStates(offset, null, nonSelfTransitions, true);
}
}
}
void OnStateContainerGridDrop(object sender, DragEventArgs e)
{
ModelItemHelper.TryCreateImmediateEditingScopeAndExecute(this.ModelItem.GetEditingContext(), System.Activities.Presentation.SR.CollectionAddEditingScopeDescription, (es) =>
{
this.DoStateContainerGridDrop(e, AutoConnectDirections.None, null);
if (es != null)
{
es.Complete();
}
});
}
// Returns the last dropped item - used for auto-connect and auto-split where only one item is allowed
ModelItem DoStateContainerGridDrop(DragEventArgs e, AutoConnectDirections autoConnectDirection, Connector connectorToSplit)
{
ModelItem droppedModelItem = null;
e.Effects = DragDropEffects.None;
IEnumerable<object> droppedObjects = DragDropHelper.GetDroppedObjects(this, e, Context);
// Marking the event as being handled. In whichever case we want to route the event, it will be unmarked explicitly.
e.Handled = true;
List<ModelItem> modelItemsToSelect = new List<ModelItem>();
Dictionary<WorkflowViewElement, Point> relativeLocations = DragDropHelper.GetDraggedViewElementRelativeLocations(e);
foreach (object droppedObject in droppedObjects)
{
if (droppedObject != null)
{
droppedModelItem = droppedObject as ModelItem;
bool isAnchorPointValid = true;
Point anchorPoint = DragDropHelper.GetDragDropAnchorPoint(e);
// This is the case of dragging from toolbox
if (anchorPoint.X < 0 && anchorPoint.Y < 0)
{
isAnchorPointValid = false;
}
// This is the case of dragging from the designer surface
else if (droppedModelItem != null)
{
WorkflowViewElement view = droppedModelItem.View as WorkflowViewElement;
anchorPoint.Offset(-relativeLocations[view].X, -relativeLocations[view].Y);
}
StateContainerEditor srcContainer = droppedModelItem != null
? DragDropHelper.GetCompositeView(droppedModelItem.View as WorkflowViewElement) as StateContainerEditor
: null;
bool externalDrop = false;
if (droppedModelItem != null && srcContainer != null && srcContainer.Equals(this))
{
// Internal move
PerformInternalMove(this.modelItemToUIElement[droppedModelItem], e.GetPosition(this.panel), anchorPoint, autoConnectDirection, connectorToSplit);
}
else
{
// External model Item drop
if (droppedModelItem != null)
{
if (droppedModelItem.ItemType == typeof(State) && this.ModelItem.ItemType == typeof(StateMachine))
{
this.InsertState(droppedModelItem);
externalDrop = true;
}
}
// Toolbox drop.
else
{
if (droppedObject.GetType() == typeof(State))
{
if (((State)droppedObject).DisplayName == null)
{
((State)droppedObject).DisplayName = GenerateStateName();
}
droppedModelItem = InsertState(droppedObject);
}
else if (droppedObject.GetType() == typeof(FinalState))
{
droppedModelItem = InsertState(new State()
{
DisplayName = DefaultFinalStateDisplayName,
IsFinal = true
});
}
}
if (droppedModelItem != null)
{
modelItemsToSelect.Add(droppedModelItem);
UIElement view = null;
if (this.modelItemToUIElement.ContainsKey(droppedModelItem))
{
view = this.modelItemToUIElement[droppedModelItem];
}
else
{
view = droppedModelItem.View as WorkflowViewElement;
if (view == null)
{
view = this.Context.Services.GetService<ViewService>().GetView(droppedModelItem) as WorkflowViewElement;
ViewUtilities.MeasureView(view as WorkflowViewElement, true);
}
}
// If drag anchor point is beyond the size of the shape being dropped,
if (anchorPoint.X > view.DesiredSize.Width || anchorPoint.Y > view.DesiredSize.Height)
{
isAnchorPointValid = false;
}
Point shapeLocation;
if (autoConnectDirection != AutoConnectDirections.None)
{
shapeLocation = this.CalculateDropLocationForAutoConnect(autoConnectDirection, new Size(DefaultStateDesignerWidth, DefaultStateDesignerHeight));
}
else
{
shapeLocation = StateContainerEditor.SnapVisualToGrid(view, e.GetPosition(this.panel), anchorPoint, isAnchorPointValid);
}
if (connectorToSplit != null)
{
shapeLocation = this.CalculateDropLocationForAutoSplit(e.GetPosition(this.panel), shapeLocation, connectorToSplit, new Size(DefaultStateDesignerWidth, DefaultStateDesignerHeight));
}
object viewState = this.ViewStateService.RetrieveViewState(droppedModelItem, ShapeLocationViewStateKey);
if (externalDrop)
{
Fx.Assert(viewState != null, "item dropped from external should already have view states");
Fx.Assert(droppedModelItem.View != null, "item dropped from extenal should already have view");
VirtualizedContainerService.VirtualizingContainer container = VisualTreeUtils.FindVisualAncestor<VirtualizedContainerService.VirtualizingContainer>(droppedModelItem.View);
Fx.Assert(container != null, "container should not be null");
Point oldLocation = (Point)viewState;
oldLocation = srcContainer.panel.GetLocationRelativeToOutmostPanel(oldLocation);
Point newLocation = this.panel.GetLocationRelativeToOutmostPanel(shapeLocation);
// To make sure the connectors are still connected to the connection points
OffsetConnectorViewState(container, oldLocation, newLocation, true);
}
this.StoreShapeLocationViewState(droppedModelItem, shapeLocation);
}
}
}
}
DragDropHelper.SetDragDropMovedViewElements(e, new WorkflowViewElement[] { });
this.Dispatcher.BeginInvoke(() =>
{
bool first = true;
foreach (ModelItem modelItem in modelItemsToSelect)
{
if (first)
{
Keyboard.Focus((IInputElement)modelItem.View);
Selection.SelectOnly(this.Context, modelItem);
first = false;
}
else
{
Selection.Union(this.Context, modelItem);
}
}
},
DispatcherPriority.ApplicationIdle);
return droppedModelItem;
}
Point CalculateDropLocationForAutoConnect(AutoConnectDirections autoConnectDirection, Size droppedSize)
{
return AutoConnectHelper.CalculateDropLocation(droppedSize, this.Panel.CurrentAutoConnectTarget, autoConnectDirection, this.shapeLocations);
}
Point CalculateDropLocationForAutoSplit(Point mousePosition, Point originalDropLocation, Connector connector, Size droppedSize)
{
return AutoSplitHelper.CalculateDropLocation(mousePosition, originalDropLocation, connector, droppedSize, this.shapeLocations);
}
void OnStateContainerGridDragEnter(object sender, DragEventArgs e)
{
OnStateContainerGridDrag(sender, e);
}
void OnStateContainerGridDragOver(object sender, DragEventArgs e)
{
OnStateContainerGridDrag(sender, e);
}
void OnStateContainerGridDrag(object sender, DragEventArgs e)
{
if (!e.Handled)
{
if (IsDropAllowed(e))
{
e.Effects |= DragDropEffects.Move;
}
else
{
e.Effects = DragDropEffects.None;
}
e.Handled = true;
}
}
private bool IsDropAllowed(DragEventArgs e)
{
// Considering multiple items drag&drop, use ModelItemsDataFormat instead.
IEnumerable<ModelItem> modelItems = e.Data.GetData(DragDropHelper.ModelItemsDataFormat) as IEnumerable<ModelItem>;
if (modelItems != null)
{
foreach (ModelItem modelItem in modelItems)
{
if (modelItem.ItemType == typeof(StartNode) && modelItem == this.initialModelItem)
{
// StartNode of current StateMachine allow to drop.
}
else if (modelItem.ItemType == typeof(State) && this.IsStateMachineContainer && StateContainerEditor.AreInSameStateMachine(modelItem, this.ModelItem))
{
// When FinalState has been dropped into a StateMachine, it becomes a State instead. So ignore FinalState type.
// State within the same StateMachine allow to drop.
}
else
{
return false;
}
}
return true;
}
else if (this.ModelItem.ItemType == typeof(StateMachine) && DragDropHelper.AllowDrop(e.Data, this.Context, typeof(State), typeof(FinalState), typeof(StartNode)))
{
// Only allow State, FinalState, StartNode to drop into a StateMachine from tool box.
return true;
}
return false;
}
void KeyboardMove(Key key)
{
Vector moveDir = FreeFormPanel.CalculateMovement(key, this.IsRightToLeft);
using (EditingScope es = (EditingScope)this.ModelItem.BeginEdit(SR.ItemMove))
{
foreach (ModelItem selectedModelItem in this.Context.Items.GetValue<Selection>().SelectedObjects)
{
UIElement shapeToMove = this.modelItemToUIElement[selectedModelItem];
Point currentLocation = FreeFormPanel.GetLocation(shapeToMove);
Point newLocation = Point.Add(currentLocation, moveDir);
// Make sure the newLocation is positive.
newLocation.X = FreeFormPanel.ZeroIfNegative(newLocation.X);
newLocation.Y = FreeFormPanel.ZeroIfNegative(newLocation.Y);
if (newLocation == currentLocation)
{
continue;
}
PerformInternalMove(shapeToMove, newLocation, null, AutoConnectDirections.None, null);
}
es.Complete();
}
}
void OnStateContainerGridKeyDown(object sender, KeyEventArgs e)
{
if (this.activeSrcConnectionPoint != null)
{
// Ignore Keyboard input when creating connector
e.Handled = true;
return;
}
Selection currentSelection = this.Context.Items.GetValue<Selection>();
if (e.Key == Key.Delete && this.selectedConnector != null && currentSelection.SelectionCount <= 1)
{
// process the delete if only the connector is selected
ModelItem primarySelection = currentSelection.PrimarySelection;
//Delete connector
ModelItem connectorModelItem = StateContainerEditor.GetConnectorModelItem(this.selectedConnector);
if (object.Equals(primarySelection, connectorModelItem) ||
// Delete initial link
primarySelection == null && connectorModelItem != null && connectorModelItem.ItemType != typeof(Transition))
{
this.DeleteConnectorModelItem(this.selectedConnector);
e.Handled = true;
}
}
else if ((new List<Key> { Key.Left, Key.Right, Key.Up, Key.Down }).Contains(e.Key)
&& currentSelection.SelectedObjects.All<ModelItem>((p) => { return this.modelItemToUIElement.ContainsKey(p); }))
{
this.KeyboardMove(e.Key);
e.Handled = true;
}
}
void OnStateContainerGridPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
this.selectedConnector = null;
}
#endregion
#region Misc
string GenerateStateName()
{
HashSet<String> existingStateNames = new HashSet<string>();
foreach (ModelItem stateModelItem in this.ModelItem.Properties[StateMachineDesigner.StatesPropertyName].Collection)
{
existingStateNames.Add(((State)stateModelItem.GetCurrentValue()).DisplayName);
}
int suffix = 0;
string name;
do
{
name = string.Format(CultureInfo.CurrentUICulture, "{0}{1}", DefaultStateDisplayName, ++suffix);
} while (existingStateNames.Contains<string>(name));
return name;
}
internal StateContainerEditor GetStateMachineContainerEditor()
{
if (this.ModelItem.ItemType == typeof(StateMachine))
{
return this;
}
else
{
Fx.Assert(this.ModelItem.ItemType == typeof(State), "ModelItem type should be State.");
StateDesigner designer = VisualTreeUtils.FindVisualAncestor<StateDesigner>(this);
FreeFormPanel panel = designer.GetStateMachineFreeFormPanel();
return VisualTreeUtils.FindVisualAncestor<StateContainerEditor>(panel);
}
}
Connector GetConnectorInStateMachine(ModelItem connectorModelItem)
{
StateContainerEditor stateMachineContainer = this.GetStateMachineContainerEditor();
foreach (UIElement element in stateMachineContainer.panel.Children)
{
Connector connector = element as Connector;
if (connector != null)
{
if (StateContainerEditor.GetConnectorModelItem(connector) == connectorModelItem)
{
return connector;
}
}
}
return null;
}
bool IsCreatingConnector()
{
StateContainerEditor stateMachineContainer = this.GetStateMachineContainerEditor();
return (stateMachineContainer.activeSrcConnectionPoint != null || (stateMachineContainer.panel.connectorEditor != null && stateMachineContainer.panel.connectorEditor.IsConnectorEndBeingMoved));
}
bool IsCreatingConnectorFromInitialNode()
{
StateContainerEditor stateMachineContainer = this.GetStateMachineContainerEditor();
return (stateMachineContainer.activeSrcConnectionPoint != null && stateMachineContainer.activeSrcConnectionPoint.ParentDesigner is StartSymbol) ||
(stateMachineContainer.panel.connectorEditor != null && stateMachineContainer.panel.connectorEditor.IsConnectorEndBeingMoved &&
stateMachineContainer.panel.connectorEditor.Connector != null &&
IsConnectorFromInitialNode(stateMachineContainer.panel.connectorEditor.Connector));
}
bool IsMovingStartOfConnectorFromInitialNode()
{
StateContainerEditor stateMachineContainer = this.GetStateMachineContainerEditor();
return (stateMachineContainer.panel.connectorEditor != null && stateMachineContainer.panel.connectorEditor.IsConnectorStartBeingMoved &&
stateMachineContainer.panel.connectorEditor.Connector != null &&
IsConnectorFromInitialNode(stateMachineContainer.panel.connectorEditor.Connector));
}
bool IsMovingStartOfConnectorForTransition()
{
StateContainerEditor stateMachineContainer = this.GetStateMachineContainerEditor();
return (stateMachineContainer.panel.connectorEditor != null && stateMachineContainer.panel.connectorEditor.IsConnectorStartBeingMoved &&
stateMachineContainer.panel.connectorEditor.Connector != null &&
GetConnectorModelItem(stateMachineContainer.panel.connectorEditor.Connector).ItemType == typeof(Transition));
}
void InvalidateMeasureForStateMachinePanel()
{
this.GetStateMachineContainerEditor().panel.InvalidateMeasure();
}
void PerformInternalMove(UIElement movedElement, Point newPoint, Point? shapeAnchorPoint, AutoConnectDirections autoConnectDirection, Connector connectorToSplit)
{
using (EditingScope es = (EditingScope)this.ModelItem.BeginEdit(SR.ItemMove))
{
StoreShapeSizeWithUndoRecursively(this.ModelItem);
this.RemoveConnectionPointsAdorner(movedElement);
Point newLocation;
Size size = FreeFormPanel.GetChildSize(movedElement);
if (autoConnectDirection != AutoConnectDirections.None)
{
newLocation = this.CalculateDropLocationForAutoConnect(autoConnectDirection, size);
}
else if (shapeAnchorPoint.HasValue)
{
newLocation = SnapVisualToGrid(movedElement, newPoint, shapeAnchorPoint.Value, true);
}
else
{
Fx.Assert(newPoint.X.IsNoLessThan(0) && newPoint.Y.IsNoLessThan(0),
"newPoint is negative");
newLocation = newPoint;
}
if (connectorToSplit != null)
{
newLocation = this.CalculateDropLocationForAutoSplit(newPoint, newLocation, connectorToSplit, size);
}
ModelItem modelItem = GetModelItemFromView(movedElement);
object viewState = this.ViewStateService.RetrieveViewState(modelItem, ShapeLocationViewStateKey);
if (viewState != null)
{
Point oldLocation = (Point)viewState;
// To make sure the connectors are still connected to the connection points.
// We don't need to offset non-contained connectors because internal move
// won't cause the connectors to be recreated and we have code in FreeFormPanel
// to guarantee that connectors will still be connected to the connection points
this.OffsetConnectorViewState(movedElement, oldLocation, newLocation, false);
}
else
{
this.StoreAttachedConnectorViewStates(movedElement);
}
this.StoreShapeLocationViewState(movedElement, newLocation);
// To make sure the connector changes are undoable
this.panel.RemoveConnectorEditor();
es.Complete();
}
}
public void StoreAttachedConnectorViewStates(UIElement element)
{
foreach (Connector connector in GetAttachedConnectors(element))
{
StoreConnectorLocationViewState(connector, true);
}
}
bool ShouldInitialize()
{
WorkflowViewElement parent = VisualTreeUtils.FindVisualAncestor<WorkflowViewElement>(this);
return parent != null && parent.ModelItem != null && (parent.ModelItem.ItemType == typeof(StateMachine) && parent.ShowExpanded ||
parent.ModelItem.ItemType == typeof(State) && !parent.IsRootDesigner);
}
void ClearSelectedConnector()
{
if (this.panel.connectorEditor != null && this.panel.connectorEditor.Connector == this.selectedConnector)
{
this.panel.RemoveConnectorEditor();
}
this.selectedConnector = null;
}
#endregion
#region AutoConnect
public void DoAutoConnect(DragEventArgs e, UIElement targetElement, AutoConnectDirections direction)
{
UIElement sourceElement = targetElement;
bool immediatelyCommit = ModelItemHelper.CanCreateImmediateEditingScope(this.ModelItem);
using (EditingScope scope = (EditingScope)this.ModelItem.BeginEdit(SR.AutoConnect, immediatelyCommit))
{
ModelItem connectorModelItem = null;
Point location = e.GetPosition(sourceElement);
ModelItem droppedModelItem = this.DoStateContainerGridDrop(e, direction, null);
if (droppedModelItem != null)
{
connectorModelItem = this.DoAutoConnect(sourceElement, droppedModelItem, null);
}
if (connectorModelItem != null)
{
EdgeLocation edgeLocation = AutoConnectHelper.AutoConnectDirection2EdgeLocation(direction);
this.GetStateMachineContainerEditor().activeSrcConnectionPoint = this.GetSourceConnectionPointForAutoConnect(sourceElement, edgeLocation);
ModelItem sourceModelItem = TryGetModelItemFromView(sourceElement);
Fx.Assert(sourceModelItem != null, "sourceModelItem");
// add a custom change inside a new editing scope since current editing scope an immediate editing scope
using (EditingScope es = (EditingScope)this.ModelItem.BeginEdit(SR.AutoConnect, false))
{
es.Changes.Add(new StoreAutoConnectorViewStateChange(
this.ModelItem, sourceModelItem, droppedModelItem, connectorModelItem, edgeLocation));
es.Complete();
}
scope.Complete();
}
else
{
scope.Revert();
}
}
}
private ModelItem DoAutoConnect(UIElement sourceElement, ModelItem droppedModelItem, Transition transitionToCopy, int insertIndex = InvalidIndex)
{
ModelItem sourceModelItem = TryGetModelItemFromView(sourceElement);
if (sourceModelItem != null && droppedModelItem.ItemType == typeof(State))
{
if (sourceModelItem.ItemType == typeof(State))
{
ModelItem stateMachineModelItem = GetStateMachineModelItem(sourceModelItem);
Transition transition = new Transition
{
DisplayName = StateContainerEditor.GenerateTransitionName(stateMachineModelItem),
To = droppedModelItem.GetCurrentValue() as State
};
if (transitionToCopy != null)
{
transition.Action = transitionToCopy.Action;
transition.Condition = transitionToCopy.Condition;
transition.DisplayName = transitionToCopy.DisplayName;
transition.Trigger = transitionToCopy.Trigger;
}
ModelItem trasitionModelItem = null;
if (insertIndex >= 0)
{
trasitionModelItem = sourceModelItem.Properties[StateDesigner.TransitionsPropertyName].Collection.Insert(insertIndex, transition);
}
else
{
trasitionModelItem = sourceModelItem.Properties[StateDesigner.TransitionsPropertyName].Collection.Add(transition);
}
Fx.Assert(trasitionModelItem != null, "trasitionModelItem");
return trasitionModelItem;
}
// auto-connect from the initial node
else if (sourceModelItem.ItemType == typeof(StartNode))
{
this.ModelItem.Properties[StateMachineDesigner.InitialStatePropertyName].SetValue(droppedModelItem);
return this.ModelItem;
}
}
return null;
}
public AutoConnectDirections GetDirectionsAllowed(DragEventArgs e, UIElement targetElement)
{
if (!this.IsDropAllowed(e))
{
return AutoConnectDirections.None;
}
if (StateContainerEditor.GetEmptyConnectionPoints(targetElement).Count < 1)
{
return AutoConnectDirections.None;
}
List<Type> types = DragDropHelper.GetDraggedTypes(e.Data);
if (types.Count != 1 || (types[0] != typeof(State) && types[0] != typeof(FinalState)))
{
return AutoConnectDirections.None;
}
if (targetElement is VirtualizedContainerService.VirtualizingContainer && IsFinalState(((VirtualizedContainerService.VirtualizingContainer)targetElement).ModelItem))
{
return AutoConnectDirections.None;
}
if (targetElement is StartSymbol && this.ModelItem.ItemType == typeof(StateMachine))
{
if (this.ModelItem.Properties[StateMachineDesigner.InitialStatePropertyName].Value != null)
{
return AutoConnectDirections.None;
}
// Should not allow auto-connecting a final state to the start symbol
IEnumerable<ModelItem> draggedModelItems = DragDropHelper.GetDraggedModelItems(e);
if ((draggedModelItems.Count<ModelItem>() == 1 && StateContainerEditor.IsFinalState(draggedModelItems.First<ModelItem>()))
|| types[0] == typeof(FinalState))
{
return AutoConnectDirections.None;
}
}
return AutoConnectDirections.Top | AutoConnectDirections.Bottom | AutoConnectDirections.Left | AutoConnectDirections.Right;
}
private ConnectionPoint GetSourceConnectionPointForAutoConnect(UIElement designer, EdgeLocation edgeLocation)
{
List<ConnectionPoint> connectionPoints = this.GetAvailableConnectionPoint(designer);
return GetConnectionPointClosestToEdgeMidPoint(designer, connectionPoints, edgeLocation);
}
static ConnectionPoint GetConnectionPointClosestToEdgeMidPoint(UIElement designer, List<ConnectionPoint> connectionPoints, EdgeLocation edgeLocation)
{
Point midPoint = new Point(-1, -1);
Point location = FreeFormPanel.GetLocation(designer);
Size size = FreeFormPanel.GetChildSize(designer);
switch (edgeLocation)
{
case EdgeLocation.Left:
midPoint = new Point(location.X, location.Y + size.Height / 2);
break;
case EdgeLocation.Right:
midPoint = new Point(location.X + size.Width, location.Y + size.Height / 2);
break;
case EdgeLocation.Top:
midPoint = new Point(location.X + size.Width / 2, location.Y);
break;
case EdgeLocation.Bottom:
midPoint = new Point(location.X + size.Width / 2, location.Y + size.Height);
break;
}
if (connectionPoints.Count > 0)
{
double dist;
return ConnectionPoint.GetClosestConnectionPoint(connectionPoints, midPoint, out dist);
}
return null;
}
private static ModelItem TryGetModelItemFromView(UIElement sourceElement)
{
Fx.Assert(sourceElement != null, "sourceElement != null");
ModelItem sourceModelItem = null;
if (sourceElement is WorkflowViewElement)
{
sourceModelItem = ((WorkflowViewElement)sourceElement).ModelItem;
}
else if (sourceElement is VirtualizedContainerService.VirtualizingContainer)
{
sourceModelItem = ((VirtualizedContainerService.VirtualizingContainer)sourceElement).ModelItem;
}
return sourceModelItem;
}
private class StoreAutoConnectorViewStateChange : Change
{
private EdgeLocation EdgeLocation { get; set; }
private ModelItem StateMachine { get; set; }
private ModelItem SrcModelItem { get; set; }
private ModelItem DstModelItem { get; set; }
private ModelItem ViewStateOwnerModelItem { get; set; }
private object OldViewState { get; set; }
private object NewViewState { get; set; }
private bool ShouldCreateConnector { get; set; }
public override string Description { get { return SR.AutoConnect; } }
public StoreAutoConnectorViewStateChange(
ModelItem stateMachine, ModelItem srcModelItem, ModelItem desModelItem, ModelItem addedModelItem, EdgeLocation edgeLocatioin)
{
this.StateMachine = stateMachine;
this.SrcModelItem = srcModelItem;
this.DstModelItem = desModelItem;
this.EdgeLocation = edgeLocatioin;
this.ViewStateOwnerModelItem = addedModelItem;
this.ShouldCreateConnector = true;
}
private StoreAutoConnectorViewStateChange()
{
}
public override bool Apply()
{
StateMachineDesigner designer = this.StateMachine.View as StateMachineDesigner;
Fx.Assert(designer != null, "designer");
StateContainerEditor editor = designer.StateContainerEditor;
Fx.Assert(editor != null, "editor");
// only the first time we need to calculate the ViewState,
// later, we just use the old one.
if (this.ShouldCreateConnector)
{
UIElement srcElement = editor.modelItemToUIElement[this.SrcModelItem];
UIElement desElement = editor.modelItemToUIElement[this.DstModelItem];
ConnectionPoint srcConnectionPoint = null;
ConnectionPoint desConnectionPoint = null;
PointCollection points = editor.CreatePointCollectionForAutoConnectOrAutoSplit(
srcElement, desElement, this.SrcModelItem, ref srcConnectionPoint, ref desConnectionPoint);
this.OldViewState = editor.ViewStateService.RetrieveViewState(this.ViewStateOwnerModelItem, ConnectorLocationViewStateKey);
this.NewViewState = points;
// compare old and new values, if they're the same, return false
if (this.OldViewState == null && this.NewViewState == null)
{
return false;
}
if (this.OldViewState != null
&& this.NewViewState != null
&& points.SequenceEqual(this.OldViewState as PointCollection))
{
return false;
}
this.ShouldCreateConnector = false;
}
editor.ViewStateService.StoreViewState(this.ViewStateOwnerModelItem, ConnectorLocationViewStateKey, this.NewViewState);
return true;
}
public override Change GetInverse()
{
return new StoreAutoConnectorViewStateChange
{
StateMachine = this.StateMachine,
ViewStateOwnerModelItem = this.ViewStateOwnerModelItem,
EdgeLocation = this.EdgeLocation,
SrcModelItem = this.SrcModelItem,
DstModelItem = this.DstModelItem,
OldViewState = this.NewViewState,
NewViewState = this.OldViewState,
ShouldCreateConnector = false
};
}
}
#endregion
#region AutoSplit
public bool CanAutoSplit(DragEventArgs e)
{
if (!this.IsDropAllowed(e))
{
return false;
}
ModelItem draggedModelItem = e.Data.GetData(DragDropHelper.ModelItemDataFormat) as ModelItem;
if (draggedModelItem != null && this.modelItemToUIElement.ContainsKey(draggedModelItem))
{
if (StateContainerEditor.GetAttachedConnectors(this.modelItemToUIElement[draggedModelItem]).Count > 0)
{
return false;
}
if (StateContainerEditor.IsFinalState(draggedModelItem))
{
return false;
}
}
List<Type> draggedTypes = DragDropHelper.GetDraggedTypes(e.Data);
if (draggedTypes.Count != 1 || draggedTypes[0] != typeof(State))
{
return false;
}
return true;
}
public void DoAutoSplit(DragEventArgs e, Connector connector)
{
bool immediatelyCommit = ModelItemHelper.CanCreateImmediateEditingScope(this.ModelItem);
using (EditingScope scope = (EditingScope)this.ModelItem.BeginEdit(SR.AutoSplit, immediatelyCommit))
{
ModelItem droppedModelItem = this.DoStateContainerGridDrop(e, AutoConnectDirections.None, connector);
bool autoSplit = false;
ConnectionPoint sourceConnectionPoint = FreeFormPanel.GetSourceConnectionPoint(connector);
ConnectionPoint destinationConnectionPoint = FreeFormPanel.GetDestinationConnectionPoint(connector);
if (droppedModelItem != null)
{
ModelItem oldConnectorModelItem = StateContainerEditor.GetConnectorModelItem(connector);
int index = this.DeleteConnectorModelItem(connector);
bool autoConnected = this.DoAutoConnect(sourceConnectionPoint.ParentDesigner,
droppedModelItem, oldConnectorModelItem.GetCurrentValue() as Transition, index) != null;
if (autoConnected)
{
ModelItem destinationModelItem = ((VirtualizedContainerService.VirtualizingContainer)destinationConnectionPoint.ParentDesigner).ModelItem;
ModelItem stateMachineModelItem = GetStateMachineModelItem(destinationModelItem);
droppedModelItem.Properties[StateDesigner.TransitionsPropertyName].Collection.Add(new Transition()
{
DisplayName = StateContainerEditor.GenerateTransitionName(stateMachineModelItem),
To = destinationModelItem.GetCurrentValue() as State
});
autoSplit = true;
}
}
if (autoSplit)
{
// Auto-split generates 4 changes: 1) drop state, 2) remove the old transition, 3) create a transition from the source state
// to the dropped state, and 4) create a transition from the dropped state to the destination state.
// Step 1 may result in creating the visual of all outgoing transition from the dropped state. Step 4) also creates the visual
// of the new transition from the dropped state. So the visual of the transition will be created twice. To solve that problem,
// we need to suppress adding connector when adding state visual (in the UI reaction for step 1).
// And to support redo, we must place the suppression in the undo stack.
this.Context.Services.GetService<ModelTreeManager>().AddToCurrentEditingScope(new SuppressAddingConnectorWhenAddingStateVisual());
this.activeSrcConnectionPointForAutoSplit = sourceConnectionPoint;
this.activeDestConnectionPointForAutoSplit = destinationConnectionPoint;
AutoSplitHelper.CalculateEntryExitEdges(e.GetPosition(this.panel), connector, out this.entryEdgeForAutoSplit, out this.exitEdgeForAutoSplit);
scope.Complete();
}
else
{
scope.Revert();
}
}
}
private const int InvalidIndex = -1;
#endregion
class SuppressAddingConnectorWhenAddingStateVisual : Change
{
public override string Description
{
get
{
return null;
}
}
public override bool Apply()
{
return false;
}
public override Change GetInverse()
{
return new SuppressAddingConnectorWhenAddingStateVisual();
}
}
internal enum ConnectorCreationResult
{
Success,
CannotCreateTransitionToCompositeState,
CannotCreateTransitionFromAncestorToDescendant,
CannotSetCompositeStateAsInitialState,
CannotSetFinalStateAsInitialState,
OtherFailure
}
}
}
|