|
using System;
using System.IO;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;
using System.Diagnostics;
using System.ComponentModel.Design.Serialization;
using System.Workflow.ComponentModel.Serialization;
namespace System.Workflow.ComponentModel.Design
{
#region Class ConnectionPointHitTestInfo
internal sealed class ConnectionPointHitTestInfo : HitTestInfo
{
private ConnectionPoint connectionPoint;
internal ConnectionPointHitTestInfo(ConnectionPoint connectionPoint)
: base(connectionPoint.AssociatedDesigner, HitTestLocations.Designer | HitTestLocations.Connector)
{
this.connectionPoint = connectionPoint;
}
internal ConnectionPoint ConnectionPoint
{
get
{
return this.connectionPoint;
}
}
}
#endregion
#region ConnectorEvent
public delegate void ConnectorEventHandler(object sender, ConnectorEventArgs e);
#region Class ConnectorEventArgs
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
public class ConnectorEventArgs : EventArgs
{
private Connector connector;
internal ConnectorEventArgs(Connector connector)
{
this.connector = connector;
}
public Connector Connector
{
get
{
return this.connector;
}
}
}
#endregion
#endregion
#region Class ConnectionPoint
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
public class ConnectionPoint
{
private ActivityDesigner associatedDesigner;
private DesignerEdges designerEdge;
private int connectionIndex;
public ConnectionPoint(ActivityDesigner associatedDesigner, DesignerEdges designerEdge, int connectionIndex)
{
if (associatedDesigner == null)
throw new ArgumentNullException("associatedDesigner");
if (connectionIndex < 0 || connectionIndex >= associatedDesigner.GetConnections(designerEdge).Count)
throw new ArgumentException(DR.GetString(DR.Error_ConnectionPoint), "connectionIndex");
this.associatedDesigner = associatedDesigner;
this.designerEdge = designerEdge;
this.connectionIndex = connectionIndex;
}
public ActivityDesigner AssociatedDesigner
{
get
{
return this.associatedDesigner;
}
}
public int ConnectionIndex
{
get
{
return this.connectionIndex;
}
}
public DesignerEdges ConnectionEdge
{
get
{
DesignerEdges designerEdge = this.designerEdge;
if (designerEdge != DesignerEdges.Left && designerEdge != DesignerEdges.Right &&
designerEdge != DesignerEdges.Top && designerEdge != DesignerEdges.Bottom)
{
designerEdge = DesignerGeometryHelper.ClosestEdgeToPoint(Location, this.associatedDesigner.Bounds, designerEdge);
}
return designerEdge;
}
}
public virtual Point Location
{
get
{
IList<Point> connections = this.associatedDesigner.GetConnections(this.designerEdge);
if (this.connectionIndex < connections.Count)
return connections[this.connectionIndex];
else
return Point.Empty;
}
}
public virtual Rectangle Bounds
{
get
{
IList<Point> connections = this.associatedDesigner.GetConnections(DesignerEdges.All);
if (connections.Count > 0)
{
Point location = Location;
Size size = DefaultSize;
Rectangle enclosingBounds = new Rectangle(new Point(location.X - size.Width / 2, location.Y - size.Height / 2), size);
return enclosingBounds;
}
else
{
return Rectangle.Empty;
}
}
}
public override bool Equals(object obj)
{
ConnectionPoint connectionPoint = obj as ConnectionPoint;
if (connectionPoint == null)
return false;
if (connectionPoint.AssociatedDesigner == this.associatedDesigner &&
connectionPoint.designerEdge == this.designerEdge &&
connectionPoint.ConnectionIndex == this.connectionIndex)
return true;
else
return false;
}
public override int GetHashCode()
{
return (this.associatedDesigner.GetHashCode() ^ this.designerEdge.GetHashCode() ^ this.connectionIndex.GetHashCode());
}
public void OnPaint(ActivityDesignerPaintEventArgs e, bool drawHighlighted)
{
Draw(e, Bounds);
}
internal static void Draw(ActivityDesignerPaintEventArgs e, Rectangle bounds)
{
bounds.Inflate(-1, -1);
e.Graphics.FillEllipse(Brushes.White, bounds);
e.Graphics.DrawEllipse(e.AmbientTheme.SelectionForegroundPen, bounds);
bounds.Inflate(-1, -1);
e.Graphics.FillEllipse(e.AmbientTheme.SelectionForegroundBrush, bounds);
}
private Size DefaultSize
{
get
{
Size defaultSize = WorkflowTheme.CurrentTheme.AmbientTheme.Margin;
defaultSize.Width += defaultSize.Width / 2;
defaultSize.Height += defaultSize.Height / 2;
if (this.associatedDesigner != null)
defaultSize = new Size(Math.Max(defaultSize.Width, (int)this.associatedDesigner.DesignerTheme.ForegroundPen.Width * 4), Math.Max(defaultSize.Height, (int)this.associatedDesigner.DesignerTheme.ForegroundPen.Width * 4));
return defaultSize;
}
}
}
#endregion
#region Class Connector
[DesignerSerializer(typeof(ConnectorLayoutSerializer), typeof(WorkflowMarkupSerializer))]
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
public class Connector : IDisposable
{
#region Members, Construction, Dispose
private AccessibleObject accessibilityObject;
private FreeformActivityDesigner parentDesigner;
private ConnectionPoint source = null;
private ConnectionPoint target = null;
private List<Point> segments = new List<Point>();
private bool connectorModified = false;
public Connector(ConnectionPoint source, ConnectionPoint target)
{
if (source == null)
throw new ArgumentNullException("source");
if (target == null)
throw new ArgumentNullException("target");
if (ConnectionManager.GetConnectorContainer(source.AssociatedDesigner) != ConnectionManager.GetConnectorContainer(target.AssociatedDesigner))
throw new ArgumentException(DR.GetString(DR.Error_Connector1));
this.source = source;
this.target = target;
}
void IDisposable.Dispose()
{
}
#endregion
#region Properties and Methods
public virtual AccessibleObject AccessibilityObject
{
get
{
if (this.accessibilityObject == null)
this.accessibilityObject = new ConnectorAccessibleObject(this);
return this.accessibilityObject;
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ConnectionPoint Source
{
get
{
return this.source;
}
set
{
if (value == null)
throw new ArgumentNullException("value");
if (this.source.Equals(value))
return;
if (ConnectionManager.GetConnectorContainer(value.AssociatedDesigner) != ConnectionManager.GetConnectorContainer(this.target.AssociatedDesigner))
throw new ArgumentException(SR.GetString(SR.Error_InvalidConnectorSource), "value");
this.source = value;
PerformLayout();
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ConnectionPoint Target
{
get
{
return this.target;
}
set
{
if (value == null)
throw new ArgumentNullException("value");
if (this.target.Equals(value))
return;
if (ConnectionManager.GetConnectorContainer(value.AssociatedDesigner) != ConnectionManager.GetConnectorContainer(this.source.AssociatedDesigner))
throw new ArgumentException(SR.GetString(SR.Error_InvalidConnectorSource), "value");
this.target = value;
PerformLayout();
}
}
public virtual ReadOnlyCollection<Point> ConnectorSegments
{
get
{
List<Point> connectorSegments = new List<Point>();
if (this.source != null && this.target != null)
{
if (this.segments.Count == 0 || this.segments[0] != this.source.Location)
connectorSegments.Add(this.source.Location);
connectorSegments.AddRange(this.segments);
if (this.segments.Count == 0 || this.segments[this.segments.Count - 1] != this.target.Location)
connectorSegments.Add(this.target.Location);
}
return connectorSegments.AsReadOnly();
}
}
public Rectangle Bounds
{
get
{
Rectangle bounds = DesignerGeometryHelper.RectangleFromLineSegments(new List<Point>(ConnectorSegments).ToArray());
bounds.Inflate(1, 1);
return bounds;
}
}
public FreeformActivityDesigner ParentDesigner
{
get
{
return this.parentDesigner;
}
}
public virtual bool HitTest(Point point)
{
Size selectionSize = WorkflowTheme.CurrentTheme.AmbientTheme.SelectionSize;
//We go thru the line seagments and hittest
ReadOnlyCollection<Point> segments = ConnectorSegments;
for (int i = 1; i < segments.Count; i++)
{
if (DesignerGeometryHelper.PointOnLineSegment(point, new Point[] { segments[i - 1], segments[i] }, selectionSize))
return true;
}
return false;
}
public virtual void Offset(Size size)
{
for (int i = 0; i < this.segments.Count; i++)
this.segments[i] = new Point(this.segments[i].X + size.Width, this.segments[i].Y + size.Height);
}
public override bool Equals(object obj)
{
Connector connector = obj as Connector;
if (connector == null)
return false;
return (connector.Source == this.source && connector.target == this.target);
}
public override int GetHashCode()
{
if (this.source != null && this.target != null)
return this.source.GetHashCode() ^ this.target.GetHashCode();
else
return base.GetHashCode();
}
public void Invalidate()
{
WorkflowView workflowView = ParentView;
if (workflowView != null)
workflowView.InvalidateLogicalRectangle(Bounds);
}
public bool ConnectorModified
{
get
{
return this.connectorModified;
}
}
protected WorkflowView ParentView
{
get
{
return GetService(typeof(WorkflowView)) as WorkflowView;
}
}
protected void PerformLayout()
{
WorkflowView workflowView = ParentView;
if (workflowView != null)
workflowView.PerformLayout(false);
}
protected internal virtual ICollection<Rectangle> ExcludedRoutingRectangles
{
get
{
return new Rectangle[] { };
}
}
//We want to allow framework or the derived classes only to set the connector segments
//We do not want any outside entity to set the connector segments randomly
protected internal void SetConnectorSegments(ICollection<Point> segments)
{
//Called by derived classes when they want to change connector routing
//
if (segments == null)
throw new ArgumentNullException("segments");
this.connectorModified = (this.parentDesigner != null && segments.Count > 0);
if (this.connectorModified)
Invalidate();
this.segments.Clear();
this.segments.AddRange(segments);
if (this.connectorModified)
Invalidate();
}
protected internal virtual void OnLayout(ActivityDesignerLayoutEventArgs e)
{
if (this.segments.Count > 0 &&
(this.segments[0] != Source.Location || this.segments[this.segments.Count - 1] != Target.Location))
this.connectorModified = false;
if (!connectorModified && ParentDesigner != null)
{
Point[] newSegments = ActivityDesignerConnectorRouter.Route(Source.AssociatedDesigner.Activity.Site, Source, Target, ExcludedRoutingRectangles);
this.segments.Clear();
this.segments.AddRange(newSegments);
}
}
protected internal virtual void OnPaint(ActivityDesignerPaintEventArgs e)
{
CompositeDesignerTheme theme = e.DesignerTheme as CompositeDesignerTheme;
if (theme != null)
{
Size arrowCapSize = new Size(theme.ConnectorSize.Width / 5, theme.ConnectorSize.Height / 5);
Size maxCapSize = theme.ConnectorSize;
ActivityDesignerPaint.DrawConnectors(e.Graphics, e.DesignerTheme.ForegroundPen, new List<Point>(ConnectorSegments).ToArray(), arrowCapSize, maxCapSize, theme.ConnectorStartCap, theme.ConnectorEndCap);
}
}
protected internal virtual void OnPaintSelected(ActivityDesignerPaintEventArgs e, bool primarySelection, Point[] segmentEditPoints)
{
CompositeDesignerTheme theme = e.DesignerTheme as CompositeDesignerTheme;
if (theme == null)
return;
using (Pen lineSelectionPen = new Pen(WorkflowTheme.CurrentTheme.AmbientTheme.SelectionForeColor, 1))
{
Size arrowCapSize = new Size(theme.ConnectorSize.Width / 5, theme.ConnectorSize.Height / 5);
Size maxCapSize = theme.ConnectorSize;
ActivityDesignerPaint.DrawConnectors(e.Graphics, lineSelectionPen, new List<Point>(ConnectorSegments).ToArray(), arrowCapSize, maxCapSize, theme.ConnectorStartCap, theme.ConnectorEndCap);
}
if (this.source != null)
this.source.OnPaint(e, false);
ReadOnlyCollection<Point> endSegmentEditPoints = ConnectorSegments;
for (int i = 1; i < endSegmentEditPoints.Count - 1; i++)
PaintEditPoints(e, endSegmentEditPoints[i], false);
for (int i = 0; i < segmentEditPoints.Length; i++)
PaintEditPoints(e, segmentEditPoints[i], true);
if (this.target != null)
this.target.OnPaint(e, false);
}
protected internal virtual void OnPaintEdited(ActivityDesignerPaintEventArgs e, Point[] segments, Point[] segmentEditPoints)
{
CompositeDesignerTheme theme = e.DesignerTheme as CompositeDesignerTheme;
if (theme == null)
return;
using (Pen editableConnectorPen = new Pen(e.AmbientTheme.SelectionForegroundPen.Color, e.AmbientTheme.SelectionForegroundPen.Width))
{
editableConnectorPen.DashStyle = DashStyle.Dash;
Size arrowCapSize = new Size(theme.ConnectorSize.Width / 5, theme.ConnectorSize.Height / 5);
Size maxCapSize = theme.ConnectorSize;
ActivityDesignerPaint.DrawConnectors(e.Graphics, editableConnectorPen, segments, arrowCapSize, maxCapSize, theme.ConnectorStartCap, theme.ConnectorEndCap);
}
if (this.source != null)
this.source.OnPaint(e, false);
for (int i = 1; i < segments.Length - 1; i++)
PaintEditPoints(e, segments[i], false);
for (int i = 0; i < segmentEditPoints.Length; i++)
PaintEditPoints(e, segmentEditPoints[i], true);
if (this.target != null)
this.target.OnPaint(e, false);
}
protected virtual object GetService(Type serviceType)
{
object service = null;
if (this.parentDesigner != null && this.parentDesigner.Activity != null && this.parentDesigner.Activity.Site != null)
service = this.parentDesigner.Activity.Site.GetService(serviceType);
return service;
}
#endregion
#region Helpers
private void PaintEditPoints(ActivityDesignerPaintEventArgs e, Point point, bool drawMidSegmentEditPoint)
{
Size size = (this.source != null) ? this.source.Bounds.Size : Size.Empty;
if (!size.IsEmpty)
{
Rectangle bounds = new Rectangle(point.X - size.Width / 2, point.Y - size.Height / 2, size.Width, size.Height);
if (drawMidSegmentEditPoint)
{
using (GraphicsPath path = new GraphicsPath())
{
path.AddLine(new Point(bounds.Left + bounds.Width / 2, bounds.Top), new Point(bounds.Right, bounds.Top + bounds.Height / 2));
path.AddLine(new Point(bounds.Right, bounds.Top + bounds.Height / 2), new Point(bounds.Left + bounds.Width / 2, bounds.Bottom));
path.AddLine(new Point(bounds.Left + bounds.Width / 2, bounds.Bottom), new Point(bounds.Left, bounds.Top + bounds.Height / 2));
path.AddLine(new Point(bounds.Left, bounds.Top + bounds.Height / 2), new Point(bounds.Left + bounds.Width / 2, bounds.Top));
e.Graphics.FillPath(Brushes.White, path);
e.Graphics.DrawPath(e.AmbientTheme.SelectionForegroundPen, path);
}
}
else
{
bounds.Inflate(-1, -1);
e.Graphics.FillEllipse(e.AmbientTheme.SelectionForegroundBrush, bounds);
}
}
}
internal void SetConnectorModified(bool modified)
{
this.connectorModified = modified;
}
internal FreeformActivityDesigner RenderingOwner
{
get
{
if (this.source == null || this.target == null)
return null;
//
List<FreeformActivityDesigner> targetParents = new List<FreeformActivityDesigner>();
ActivityDesigner designer = this.target.AssociatedDesigner;
while (designer != null)
{
FreeformActivityDesigner parentFreeFormDesigner = designer as FreeformActivityDesigner;
if (parentFreeFormDesigner != null)
targetParents.Add(parentFreeFormDesigner);
designer = designer.ParentDesigner;
}
//Go through the parent freeforms of source and match it with target, the first common
//parent freeform is the rendering designer
designer = this.source.AssociatedDesigner;
while (designer != null)
{
FreeformActivityDesigner parentFreeFormDesigner = designer as FreeformActivityDesigner;
if (parentFreeFormDesigner != null && targetParents.Contains(parentFreeFormDesigner))
break;
designer = designer.ParentDesigner;
}
return designer as FreeformActivityDesigner;
}
}
internal void SetParent(FreeformActivityDesigner parentDesigner)
{
//Make sure that this is in parent chain of both source and the target connection points
WorkflowView workflowView = ParentView;
if (this.parentDesigner != null && workflowView != null)
workflowView.InvalidateLogicalRectangle(this.parentDesigner.Bounds);
this.parentDesigner = parentDesigner;
if (this.parentDesigner != null && workflowView != null)
workflowView.InvalidateLogicalRectangle(this.parentDesigner.Bounds);
}
internal static Connector GetConnectorFromSelectedObject(object selectedObject)
{
Connector connector = null;
ConnectorHitTestInfo connectorHitTestInfo = selectedObject as ConnectorHitTestInfo;
if (connectorHitTestInfo != null)
{
FreeformActivityDesigner connectorContainer = connectorHitTestInfo.AssociatedDesigner as FreeformActivityDesigner;
int index = connectorHitTestInfo.MapToIndex();
if (connectorContainer != null && index >= 0 && index < connectorContainer.Connectors.Count)
connector = connectorContainer.Connectors[index];
}
return connector;
}
#region Properties used during serialization only
//NOTE THAT THIS WILL ONLY BE USED FOR SERIALIZATION PURPOSES
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
internal string SourceActivity
{
get
{
string activityName = String.Empty;
if (this.source != null)
activityName = this.source.AssociatedDesigner.Activity.QualifiedName;
return activityName;
}
set
{
}
}
//NOTE THAT THIS WILL ONLY BE USED FOR SERIALIZATION PURPOSES
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
internal int SourceConnectionIndex
{
get
{
int connectionIndex = 0;
if (this.source != null)
connectionIndex = this.source.ConnectionIndex;
return connectionIndex;
}
set
{
}
}
//NOTE THAT THIS WILL ONLY BE USED FOR SERIALIZATION PURPOSES
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
internal DesignerEdges SourceConnectionEdge
{
get
{
DesignerEdges connectionEdge = DesignerEdges.None;
if (this.source != null)
connectionEdge = this.source.ConnectionEdge;
return connectionEdge;
}
set
{
}
}
//NOTE THAT THIS WILL ONLY BE USED FOR SERIALIZATION PURPOSES
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
internal string TargetActivity
{
get
{
string activityName = String.Empty;
if (this.target != null)
activityName = this.target.AssociatedDesigner.Activity.QualifiedName;
return activityName;
}
set
{
}
}
//NOTE THAT THIS WILL ONLY BE USED FOR SERIALIZATION PURPOSES
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
internal int TargetConnectionIndex
{
get
{
int connectionIndex = 0;
if (this.target != null)
connectionIndex = this.target.ConnectionIndex;
return connectionIndex;
}
set
{
}
}
//NOTE THAT THIS WILL ONLY BE USED FOR SERIALIZATION PURPOSES
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
internal DesignerEdges TargetConnectionEdge
{
get
{
DesignerEdges connectionEdge = DesignerEdges.None;
if (this.target != null)
connectionEdge = this.target.ConnectionEdge;
return connectionEdge;
}
set
{
}
}
//NOTE THAT THIS WILL ONLY BE USED FOR SERIALIZATION PURPOSES
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
internal List<Point> Segments
{
get
{
return this.segments;
}
}
#endregion
#endregion
}
#endregion
#region Class ConnectorAccessibleObject
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
public class ConnectorAccessibleObject : AccessibleObject
{
private Connector connector;
public ConnectorAccessibleObject(Connector connector)
{
if (connector == null)
throw new ArgumentNullException("connector");
this.connector = connector;
}
public override Rectangle Bounds
{
get
{
WorkflowView parentView = this.connector.ParentDesigner.ParentView;
Rectangle bounds = this.connector.Bounds;
return new Rectangle(parentView.LogicalPointToScreen(bounds.Location), parentView.LogicalSizeToClient(bounds.Size));
}
}
public override AccessibleObject HitTest(int x, int y)
{
WorkflowView parentView = this.connector.ParentDesigner.ParentView;
if (this.connector.HitTest(parentView.ScreenPointToLogical(new Point(x, y))))
return this;
else
return null;
}
public override string Name
{
get
{
return this.connector.GetType().Name;
}
set
{
}
}
public override AccessibleObject Parent
{
get
{
return connector.ParentDesigner.AccessibilityObject;
}
}
public override AccessibleRole Role
{
get
{
return AccessibleRole.Diagram;
}
}
}
#endregion Class ConnectorAccessibleObject
#region Class ConnectorEditor
internal sealed class ConnectorEditor
{
private IServiceProvider serviceProvider;
private Connector editedConnector;
private EditPoint activeEditPoint;
private List<EditPoint> editPoints = new List<EditPoint>();
public ConnectorEditor(Connector connectorEdited)
{
this.editedConnector = connectorEdited;
this.serviceProvider = this.editedConnector.Source.AssociatedDesigner.Activity.Site;
CreateEditPoints();
}
public ConnectionPoint EditedConectionPoint
{
get
{
if (this.activeEditPoint != null)
return this.activeEditPoint.EditedConnectionPoint;
else
return null;
}
}
public Connector EditedConnector
{
get
{
return this.editedConnector;
}
}
public Cursor GetCursor(Point cursorPoint)
{
Cursor cursor = Cursors.Default;
if (this.activeEditPoint != null)
{
cursor = ConnectionManager.NewConnectorCursor;
}
else
{
foreach (EditPoint editPoint in this.editPoints)
{
if (editPoint.Bounds.Contains(cursorPoint))
{
cursor = ConnectionManager.SnappedConnectionCursor;
break;
}
}
}
return cursor;
}
public bool OnBeginEditing(Point point)
{
//Get all the editable points
CreateEditPoints();
EditPoint editPointHit = null;
for (int i = this.editPoints.Count - 1; i >= 0; i--)
{
if (this.editPoints[i].Bounds.Contains(point))
{
editPointHit = this.editPoints[i];
break;
}
}
if (editPointHit != null &&
(editPointHit.EditedConnectionPoint == null ||
ConnectionManager.GetConnectorContainer(editPointHit.EditedConnectionPoint.AssociatedDesigner) != null))
{
editPointHit.Location = point;
this.activeEditPoint = editPointHit;
}
Invalidate();
return (this.activeEditPoint != null);
}
public void OnContinueEditing(Point point)
{
if (this.activeEditPoint == null)
return;
Invalidate();
UpdateEditPoints(point);
Invalidate();
#if DISPLAYESCAPEREGIONS
WorkflowView workflowView = GetService(typeof(WorkflowView)) as WorkflowView;
if (workflowView != null)
workflowView.InvalidateClientRectangle(Rectangle.Empty);
#endif
}
public void OnEndEditing(Point point, bool commitChanges)
{
if (this.activeEditPoint == null)
return;
Invalidate();
if (commitChanges)
{
//This is to update the edit points based on the activepoint
UpdateEditPoints(point);
EditPoint activeEditPoint = this.activeEditPoint;
this.activeEditPoint = null;
//This call is to optimize the segments
UpdateEditPoints(point);
//If we were creating a new connector or modifying the connection end points
bool updateSegments = false;
if (activeEditPoint.Type == EditPoint.EditPointTypes.ConnectionEditPoint)
{
ConnectionManager connectionManager = GetService(typeof(ConnectionManager)) as ConnectionManager;
FreeformActivityDesigner connectorContainer = ConnectionManager.GetConnectorContainer(activeEditPoint.EditedConnectionPoint.AssociatedDesigner);
if (connectionManager != null && connectionManager.SnappedConnectionPoint != null && connectorContainer != null)
{
//Get the original source and targets
ConnectionPoint source = this.editedConnector.Source;
ConnectionPoint target = this.editedConnector.Target;
//Make sure that we set the source and target correctly
if (target.Equals(activeEditPoint.EditedConnectionPoint))
target = connectionManager.SnappedConnectionPoint;
else if (source.Equals(activeEditPoint.EditedConnectionPoint))
source = connectionManager.SnappedConnectionPoint;
//Check if it is a valid connection ie source can be connected to target
if (connectorContainer == ConnectionManager.GetConnectorContainer(target.AssociatedDesigner) &&
connectorContainer.CanConnectContainedDesigners(source, target))
{
this.editedConnector.Source = source;
this.editedConnector.Target = target;
if (this.editedConnector.ParentDesigner == null)
{
this.editedConnector = connectorContainer.AddConnector(source, target);
WorkflowDesignerLoader loader = GetService(typeof(WorkflowDesignerLoader)) as WorkflowDesignerLoader;
if (loader != null)
loader.SetModified(true);
}
connectorContainer.OnContainedDesignersConnected(source, target);
}
updateSegments = true;
}
}
else
{
updateSegments = true;
}
//Make sure that we apply the edit points to the connector
if (updateSegments)
{
this.editedConnector.SetConnectorSegments(GetPointsFromEditPoints(this.editPoints));
if (this.editedConnector.ParentDesigner != null)
{
this.editedConnector.ParentDesigner.OnConnectorChanged(new ConnectorEventArgs(this.editedConnector));
WorkflowDesignerLoader loader = GetService(typeof(WorkflowDesignerLoader)) as WorkflowDesignerLoader;
if (loader != null)
loader.SetModified(true);
}
}
PerformLayout();
}
Invalidate();
}
public bool HitTest(Point point)
{
for (int i = 0; i < this.editPoints.Count; i++)
{
EditPoint editPoint = this.editPoints[i];
if (editPoint.Bounds.Contains(point))
return true;
}
return false;
}
public void OnPaint(ActivityDesignerPaintEventArgs e, bool drawSelected, bool drawPrimarySelection)
{
List<Point> segments = new List<Point>();
List<Point> segmentEditPoints = new List<Point>();
for (int i = 0; i < this.editPoints.Count; i++)
{
EditPoint editPoint = this.editPoints[i];
if (editPoint.Type == EditPoint.EditPointTypes.ConnectionEditPoint ||
editPoint.Type == EditPoint.EditPointTypes.MultiSegmentEditPoint)
segments.Add(editPoint.Location);
else
segmentEditPoints.Add(editPoint.Location);
}
//If the editing is in progress then we want to draw the dashed lines
if (drawSelected)
this.editedConnector.OnPaintSelected(e, drawPrimarySelection, segmentEditPoints.ToArray());
if (this.activeEditPoint != null)
this.editedConnector.OnPaintEdited(e, segments.ToArray(), segmentEditPoints.ToArray());
//
#if DISPLAYESCAPEREGIONS
if (this.activeEditPoint != null && this.activeEditPoint.Type == EditPoint.EditPointTypes.ConnectionEditPoint)
{
object source = null, target = null;
if (this.activeEditPoint.EditedConnectionPoint.Equals(Target))
{
target = this.activeEditPoint.Location;
source = Source;
}
else
{
source = this.activeEditPoint.Location;
target = Target;
}
List<Rectangle> rectanglesToExclude;
List<Point> linesToExclude, pointsToExclude;
ActivityDesignerConnectorRouter.GetRoutingObstacles(this.serviceProvider, source, target, out rectanglesToExclude, out linesToExclude, out pointsToExclude);
ICollection<Rectangle> userDefinedObstacles = this.editedConnector.ExcludedRoutingRectangles;
if (userDefinedObstacles != null)
{
foreach (Rectangle rectangle in userDefinedObstacles)
e.Graphics.DrawRectangle(Pens.DarkGreen, rectangle);
}
foreach (Rectangle rectangle in rectanglesToExclude)
e.Graphics.DrawRectangle(Pens.Red, rectangle);
for (int i = 0; i < linesToExclude.Count / 2; i++)
e.Graphics.DrawLine(Pens.Red, linesToExclude[i * 2], linesToExclude[(i * 2) + 1]);
}
#endif
}
#region Helpers
#region EditPointUpdation Logic
private ConnectionPoint Source
{
get
{
return this.editedConnector.Source;
}
}
private ConnectionPoint Target
{
get
{
return this.editedConnector.Target;
}
}
private void PerformLayout()
{
WorkflowView workflowView = GetService(typeof(WorkflowView)) as WorkflowView;
if (workflowView != null)
workflowView.PerformLayout(false);
}
private void Invalidate()
{
WorkflowView workflowView = GetService(typeof(WorkflowView)) as WorkflowView;
if (workflowView != null)
{
Rectangle bounds = DesignerGeometryHelper.RectangleFromLineSegments(GetPointsFromEditPoints(this.editPoints).ToArray());
bounds.Inflate(1, 1);
workflowView.InvalidateLogicalRectangle(bounds);
}
}
private object GetService(Type serviceType)
{
object service = null;
if (this.serviceProvider != null)
service = this.serviceProvider.GetService(serviceType);
return service;
}
private void CreateEditPoints()
{
this.editPoints.Clear();
AddEditPoints(EditPoint.EditPointTypes.ConnectionEditPoint);
AddEditPoints(EditPoint.EditPointTypes.MultiSegmentEditPoint);
AddEditPoints(EditPoint.EditPointTypes.MidSegmentEditPoint);
bool validEditPoints = ValidateEditPoints();
Debug.Assert(validEditPoints);
}
private void UpdateEditPoints(Point newPoint)
{
if (this.editPoints.Count < 2 ||
this.editPoints[0].Type != EditPoint.EditPointTypes.ConnectionEditPoint ||
this.editPoints[this.editPoints.Count - 1].Type != EditPoint.EditPointTypes.ConnectionEditPoint)
{
Debug.Assert(false);
return;
}
//STEP1: First we delete all the midsegmentpoints except the one which is being edited for simplicity
RemoveEditPoints(EditPoint.EditPointTypes.MidSegmentEditPoint);
//STEP2: Update points as per the type of edit point
if (this.activeEditPoint != null)
{
int activeEditPointIndex = this.editPoints.IndexOf(this.activeEditPoint);
EditPoint previous = (activeEditPointIndex > 0) ? this.editPoints[activeEditPointIndex - 1] : null;
EditPoint next = (activeEditPointIndex < this.editPoints.Count - 1) ? this.editPoints[activeEditPointIndex + 1] : null;
//Note that extra edit points are only added if we are connected to connection point
if (previous != null && previous.Type == EditPoint.EditPointTypes.ConnectionEditPoint)
{
float slopeOfLine = DesignerGeometryHelper.SlopeOfLineSegment(previous.Location, this.activeEditPoint.Location);
Orientation orientation = (Math.Abs(slopeOfLine) < 1) ? Orientation.Horizontal : Orientation.Vertical;
int editPointOffset = Convert.ToInt32(DesignerGeometryHelper.DistanceBetweenPoints(previous.Location, (next != null) ? next.Location : this.activeEditPoint.Location)) / 4;
if (orientation == Orientation.Horizontal)
editPointOffset *= (previous.Location.X < this.activeEditPoint.Location.X) ? 1 : -1;
else
editPointOffset *= (previous.Location.Y < this.activeEditPoint.Location.X) ? 1 : -1;
activeEditPointIndex = this.editPoints.IndexOf(this.activeEditPoint);
Point editPointLocation = (orientation == Orientation.Horizontal) ? new Point(previous.Location.X + editPointOffset, previous.Location.Y) : new Point(previous.Location.X, previous.Location.Y + editPointOffset);
previous = new EditPoint(this, EditPoint.EditPointTypes.MultiSegmentEditPoint, editPointLocation);
this.editPoints.InsertRange(activeEditPointIndex, new EditPoint[] { new EditPoint(this, EditPoint.EditPointTypes.MultiSegmentEditPoint, editPointLocation), previous });
}
if (next != null && next.Type == EditPoint.EditPointTypes.ConnectionEditPoint)
{
float slopeOfLine = DesignerGeometryHelper.SlopeOfLineSegment(this.activeEditPoint.Location, next.Location);
Orientation orientation = (Math.Abs(slopeOfLine) < 1) ? Orientation.Horizontal : Orientation.Vertical;
int editPointOffset = Convert.ToInt32(DesignerGeometryHelper.DistanceBetweenPoints((previous != null) ? previous.Location : this.activeEditPoint.Location, next.Location)) / 4;
if (orientation == Orientation.Horizontal)
editPointOffset *= (this.activeEditPoint.Location.X < next.Location.X) ? -1 : 1;
else
editPointOffset *= (this.activeEditPoint.Location.Y < next.Location.Y) ? -1 : 1;
activeEditPointIndex = this.editPoints.IndexOf(this.activeEditPoint);
Point editPointLocation = (orientation == Orientation.Horizontal) ? new Point(next.Location.X + editPointOffset, next.Location.Y) : new Point(next.Location.X, next.Location.Y + editPointOffset);
next = new EditPoint(this, EditPoint.EditPointTypes.MultiSegmentEditPoint, editPointLocation);
this.editPoints.InsertRange(activeEditPointIndex + 1, new EditPoint[] { next, new EditPoint(this, EditPoint.EditPointTypes.MultiSegmentEditPoint, editPointLocation) });
}
//STEP2: UPDATE THE EDIT POINTS
if (this.activeEditPoint.Type == EditPoint.EditPointTypes.ConnectionEditPoint)
{
this.activeEditPoint.Location = newPoint;
//When we start editing the end point we need to clear the slate and start over
RemoveEditPoints(EditPoint.EditPointTypes.MultiSegmentEditPoint);
object source = null, target = null;
if (this.activeEditPoint.EditedConnectionPoint.Equals(Target))
{
target = newPoint;
source = Source;
}
else
{
source = newPoint;
target = Target;
}
int newEditPointIndex = (this.editPoints.Count == 2) ? 1 : 0;
List<EditPoint> newEditPoints = new List<EditPoint>();
Point[] points = ActivityDesignerConnectorRouter.Route(this.serviceProvider, source, target, this.editedConnector.ExcludedRoutingRectangles);
for (int i = newEditPointIndex; i < points.Length - newEditPointIndex; i++)
newEditPoints.Add(new EditPoint(this, EditPoint.EditPointTypes.MultiSegmentEditPoint, points[i]));
this.editPoints.InsertRange(1, newEditPoints.ToArray());
}
else if (this.activeEditPoint.Type == EditPoint.EditPointTypes.MultiSegmentEditPoint)
{
if (previous != null && previous.Type != EditPoint.EditPointTypes.ConnectionEditPoint && next != null && next.Type != EditPoint.EditPointTypes.ConnectionEditPoint)
{
//Update the previous point
float slopeOfLine = DesignerGeometryHelper.SlopeOfLineSegment(previous.Location, this.activeEditPoint.Location);
Orientation orientation = (Math.Abs(slopeOfLine) < 1) ? Orientation.Horizontal : Orientation.Vertical;
previous.Location = (orientation == Orientation.Horizontal) ? new Point(previous.Location.X, newPoint.Y) : new Point(newPoint.X, previous.Location.Y);
//Update the next point
slopeOfLine = DesignerGeometryHelper.SlopeOfLineSegment(this.activeEditPoint.Location, next.Location);
orientation = (Math.Abs(slopeOfLine) < 1) ? Orientation.Horizontal : Orientation.Vertical;
next.Location = (orientation == Orientation.Horizontal) ? new Point(next.Location.X, newPoint.Y) : new Point(newPoint.X, next.Location.Y);
//Update the current point
this.activeEditPoint.Location = newPoint;
}
else
{
Debug.Assert(false);
}
}
else if (this.activeEditPoint.Type == EditPoint.EditPointTypes.MidSegmentEditPoint)
{
if (previous != null && previous.Type != EditPoint.EditPointTypes.ConnectionEditPoint && next != null && next.Type != EditPoint.EditPointTypes.ConnectionEditPoint)
{
float slopeOfLine = DesignerGeometryHelper.SlopeOfLineSegment(previous.Location, next.Location);
Orientation orientation = (Math.Abs(slopeOfLine) < 1) ? Orientation.Horizontal : Orientation.Vertical;
//If the orientation is horizontal then we need to move the points vertically else we need to move the points horizontally
if (orientation == Orientation.Horizontal)
{
previous.Location = new Point(previous.Location.X, newPoint.Y);
next.Location = new Point(next.Location.X, newPoint.Y);
this.activeEditPoint.Location = new Point(this.activeEditPoint.Location.X, newPoint.Y);
}
else
{
previous.Location = new Point(newPoint.X, previous.Location.Y);
next.Location = new Point(newPoint.X, next.Location.Y);
this.activeEditPoint.Location = new Point(newPoint.X, this.activeEditPoint.Location.Y);
}
}
else
{
Debug.Assert(false);
}
}
}
//STEP3: Remove all the redundant edit points
RemoveCoincidingEditPoints();
//STEP4: Add back the segment mid points
AddEditPoints(EditPoint.EditPointTypes.MidSegmentEditPoint);
bool validEditPoints = ValidateEditPoints();
Debug.Assert(validEditPoints);
}
//Add edit points of specified type
private void AddEditPoints(EditPoint.EditPointTypes editPointType)
{
if (editPointType == EditPoint.EditPointTypes.ConnectionEditPoint)
{
if (this.editPoints.Count == 0 || !this.editPoints[0].EditedConnectionPoint.Equals(Source))
this.editPoints.Insert(0, new EditPoint(this, Source));
if (this.editPoints.Count < 2 || !this.editPoints[this.editPoints.Count - 1].EditedConnectionPoint.Equals(Target))
editPoints.Add(new EditPoint(this, Target));
}
else if (editPointType == EditPoint.EditPointTypes.MidSegmentEditPoint)
{
int minLengthForSegmentEditPoint = Source.Bounds.Width * 4;
for (int i = 0; i < this.editPoints.Count - 1; i++)
{
if (this.editPoints[i].Type != EditPoint.EditPointTypes.MidSegmentEditPoint &&
this.editPoints[i + 1].Type != EditPoint.EditPointTypes.MidSegmentEditPoint &&
DesignerGeometryHelper.DistanceOfLineSegments(new Point[] { this.editPoints[i].Location, this.editPoints[i + 1].Location }) > minLengthForSegmentEditPoint)
{
Point midPoint = DesignerGeometryHelper.MidPointOfLineSegment(this.editPoints[i].Location, this.editPoints[i + 1].Location);
this.editPoints.Insert(i + 1, new EditPoint(this, EditPoint.EditPointTypes.MidSegmentEditPoint, midPoint));
}
}
}
else if (editPointType == EditPoint.EditPointTypes.MultiSegmentEditPoint)
{
if (this.editPoints.Count == 2)
{
List<Point> segments = new List<Point>(this.editedConnector.ConnectorSegments);
if (segments.Count > 0 && segments[0] == Source.Location)
segments.RemoveAt(0);
if (segments.Count > 0 && segments[segments.Count - 1] == Target.Location)
segments.RemoveAt(segments.Count - 1);
List<EditPoint> editPointsToAdd = new List<EditPoint>();
for (int i = 0; i < segments.Count; i++)
editPointsToAdd.Add(new EditPoint(this, EditPoint.EditPointTypes.MultiSegmentEditPoint, segments[i]));
this.editPoints.InsertRange(this.editPoints.Count - 1, editPointsToAdd.ToArray());
}
else
{
Debug.Assert(false);
}
}
}
//Remove edit points of specified type
private void RemoveEditPoints(EditPoint.EditPointTypes editPointType)
{
List<EditPoint> editPointsToRemove = new List<EditPoint>();
for (int i = 0; i < this.editPoints.Count; i++)
{
EditPoint editPoint = this.editPoints[i];
if (editPoint.Type == editPointType)
editPointsToRemove.Add(editPoint);
}
for (int i = 0; i < editPointsToRemove.Count; i++)
{
EditPoint editPoint = editPointsToRemove[i];
if (editPoint != this.activeEditPoint)
this.editPoints.Remove(editPoint);
}
}
//Remove points with same slope
private void RemoveCoincidingEditPoints()
{
if (this.editPoints.Count < 2 ||
this.editPoints[0].Type != EditPoint.EditPointTypes.ConnectionEditPoint ||
this.editPoints[this.editPoints.Count - 1].Type != EditPoint.EditPointTypes.ConnectionEditPoint ||
(this.activeEditPoint != null && this.activeEditPoint.Type == EditPoint.EditPointTypes.ConnectionEditPoint))
{
return;
}
//Just make sure that there are no mid segmment edit points or algorithm will fail
RemoveEditPoints(EditPoint.EditPointTypes.MidSegmentEditPoint);
//Create list of points to retain
List<EditPoint> editPointsToRetain = new List<EditPoint>();
for (int i = 0; i < this.editPoints.Count; i++)
{
if (this.editPoints[i].Type != EditPoint.EditPointTypes.MultiSegmentEditPoint ||
this.editPoints[i] == this.activeEditPoint ||
(i > 0 && this.editPoints[i - 1].Type == EditPoint.EditPointTypes.MidSegmentEditPoint) ||
(i < this.editPoints.Count - 1 && this.editPoints[i + 1].Type == EditPoint.EditPointTypes.MidSegmentEditPoint))
{
editPointsToRetain.Add(this.editPoints[i]);
}
}
//Step1: Get rid of all the line segments which are within tolerance range
for (int i = 1; i < this.editPoints.Count - 1; i++)
{
EditPoint previous = this.editPoints[i - 1];
EditPoint current = this.editPoints[i];
EditPoint next = this.editPoints[i + 1];
if (!editPointsToRetain.Contains(current))
{
double distance = DesignerGeometryHelper.DistanceOfLineSegments(new Point[] { previous.Location, current.Location });
if ((distance < current.Bounds.Width || distance < current.Bounds.Height) && next.Type == EditPoint.EditPointTypes.MultiSegmentEditPoint)
{
float slope = DesignerGeometryHelper.SlopeOfLineSegment(current.Location, next.Location);
next.Location = (slope < 1) ? new Point(next.Location.X, previous.Location.Y) : new Point(previous.Location.X, next.Location.Y);
this.editPoints.Remove(current);
i -= 1;
}
else
{
distance = DesignerGeometryHelper.DistanceOfLineSegments(new Point[] { current.Location, next.Location });
if ((distance < current.Bounds.Width || distance < current.Bounds.Height) && previous.Type == EditPoint.EditPointTypes.MultiSegmentEditPoint)
{
float slope = DesignerGeometryHelper.SlopeOfLineSegment(previous.Location, current.Location);
previous.Location = (slope < 1) ? new Point(previous.Location.X, next.Location.Y) : new Point(next.Location.X, previous.Location.Y);
this.editPoints.Remove(current);
i -= 1;
}
}
}
}
//Step2: We should make sure that the active edit point is always retained but those points which are coincidental are always removed
for (int i = 1; i < this.editPoints.Count - 1; i++)
{
EditPoint current = this.editPoints[i];
EditPoint previous = this.editPoints[i - 1];
EditPoint next = this.editPoints[i + 1];
if (!editPointsToRetain.Contains(current))
{
float slope1 = DesignerGeometryHelper.SlopeOfLineSegment(previous.Location, current.Location);
float slope2 = DesignerGeometryHelper.SlopeOfLineSegment(current.Location, next.Location);
if (Math.Abs(slope1) == Math.Abs(slope2))
{
this.editPoints.Remove(current);
i -= 1;
}
}
}
//Step3: Go thorugh each segment and ensure that there all the segments are either vertical or horizontal
for (int i = 0; i < this.editPoints.Count - 1; i++)
{
EditPoint current = this.editPoints[i];
EditPoint next = this.editPoints[i + 1];
float slope = DesignerGeometryHelper.SlopeOfLineSegment(current.Location, next.Location);
if (slope != 0 && slope != float.MaxValue)
{
Point location = (slope < 1) ? new Point(next.Location.X, current.Location.Y) : new Point(current.Location.X, next.Location.Y);
this.editPoints.Insert(i + 1, new EditPoint(this, EditPoint.EditPointTypes.MultiSegmentEditPoint, location));
}
}
}
private bool ValidateEditPoints()
{
if (this.editPoints.Count < 2)
return false;
ConnectionPoint sourceConnection = this.editPoints[0].EditedConnectionPoint;
if (sourceConnection == null || !sourceConnection.Equals(Source))
return false;
ConnectionPoint targetConnection = this.editPoints[this.editPoints.Count - 1].EditedConnectionPoint;
if (targetConnection == null || !targetConnection.Equals(Target))
return false;
for (int i = 0; i < this.editPoints.Count - 1; i++)
{
if (this.editPoints[i].Type == EditPoint.EditPointTypes.MidSegmentEditPoint &&
this.editPoints[i + 1].Type == EditPoint.EditPointTypes.MidSegmentEditPoint)
return false;
}
return true;
}
#endregion
private List<Point> GetPointsFromEditPoints(List<EditPoint> editPoints)
{
List<Point> segments = new List<Point>();
for (int i = 0; i < editPoints.Count; i++)
{
EditPoint editPoint = editPoints[i];
if (editPoint.Type == EditPoint.EditPointTypes.ConnectionEditPoint ||
editPoint.Type == EditPoint.EditPointTypes.MultiSegmentEditPoint)
segments.Add(editPoint.Location);
}
return segments;
}
#endregion
#region Class EditPoint
private sealed class EditPoint
{
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
public enum EditPointTypes { ConnectionEditPoint = 1, MultiSegmentEditPoint, MidSegmentEditPoint };
private ConnectorEditor owner;
private EditPointTypes editPointType;
private Point point;
private ConnectionPoint connectionPoint;
public EditPoint(ConnectorEditor owner, EditPointTypes editPointType, Point point)
{
this.owner = owner;
this.editPointType = editPointType;
this.point = point;
}
public EditPoint(ConnectorEditor owner, ConnectionPoint connectionPoint)
{
this.owner = owner;
this.editPointType = EditPointTypes.ConnectionEditPoint;
this.connectionPoint = connectionPoint;
this.point = connectionPoint.Location;
}
public EditPointTypes Type
{
get
{
return this.editPointType;
}
}
public Point Location
{
get
{
return this.point;
}
set
{
this.point = value;
}
}
public Rectangle Bounds
{
get
{
Size controlPointSize = this.owner.Source.Bounds.Size;
return new Rectangle(this.point.X - controlPointSize.Width / 2, this.point.Y - controlPointSize.Height / 2, controlPointSize.Width, controlPointSize.Height);
}
}
public ConnectionPoint EditedConnectionPoint
{
get
{
return this.connectionPoint;
}
}
}
#endregion
}
#endregion
#region Class ActivityDesignerConnectorRouter
internal static class ActivityDesignerConnectorRouter
{
public static Point[] Route(IServiceProvider serviceProvider, object source, object target, ICollection<Rectangle> userDefinedRoutingObstacles)
{
//Now call routing algorithm
List<Rectangle> rectanglesToExclude;
List<Point> linesToExclude, pointsToExclude;
ActivityDesignerConnectorRouter.GetRoutingObstacles(serviceProvider, source, target, out rectanglesToExclude, out linesToExclude, out pointsToExclude);
if (userDefinedRoutingObstacles != null)
rectanglesToExclude.AddRange(userDefinedRoutingObstacles);
ActivityDesigner rootDesigner = ActivityDesigner.GetSafeRootDesigner(serviceProvider);
AmbientTheme ambientTheme = WorkflowTheme.CurrentTheme.AmbientTheme;
Point sourcePoint = (source is ConnectionPoint) ? ((ConnectionPoint)source).Location : (Point)source;
Point targetPoint = (target is ConnectionPoint) ? ((ConnectionPoint)target).Location : (Point)target;
Point[] routedPoints = ConnectorRouter.Route(sourcePoint, targetPoint, new Size(2 * ambientTheme.Margin.Width, 2 * ambientTheme.Margin.Height), rootDesigner.Bounds, rectanglesToExclude.ToArray(), linesToExclude.ToArray(), pointsToExclude.ToArray());
//
if (!AreAllSegmentsVerticalOrHorizontal(routedPoints))
routedPoints = ConnectorRouter.Route(sourcePoint, targetPoint, ambientTheme.Margin, rootDesigner.Bounds, new Rectangle[] { }, linesToExclude.ToArray(), new Point[] { });
//FALLBACK1
if (!AreAllSegmentsVerticalOrHorizontal(routedPoints))
{
float slope = DesignerGeometryHelper.SlopeOfLineSegment(sourcePoint, targetPoint);
Point intermediatePoint = (slope < 1) ? new Point(targetPoint.X, sourcePoint.Y) : new Point(sourcePoint.X, targetPoint.Y);
routedPoints = new Point[] { sourcePoint, intermediatePoint, targetPoint };
}
return routedPoints;
}
public static void GetRoutingObstacles(IServiceProvider serviceProvider, object source, object target, out List<Rectangle> rectanglesToExclude, out List<Point> linesToExclude, out List<Point> pointsToExclude)
{
//Source or Targets can be either ConnectionPoint or a Point
AmbientTheme ambientTheme = WorkflowTheme.CurrentTheme.AmbientTheme;
ActivityDesigner rootDesigner = ActivityDesigner.GetSafeRootDesigner(serviceProvider);
ConnectionPoint sourceConnectionPoint = source as ConnectionPoint;
Point sourcePoint = (sourceConnectionPoint != null) ? sourceConnectionPoint.Location : (Point)source;
ActivityDesigner sourceDesigner = (sourceConnectionPoint != null) ? sourceConnectionPoint.AssociatedDesigner : rootDesigner.HitTest(sourcePoint).AssociatedDesigner;
ConnectionPoint targetConnectionPoint = target as ConnectionPoint;
Point targetPoint = (targetConnectionPoint != null) ? targetConnectionPoint.Location : (Point)target;
ActivityDesigner targetDesigner = (targetConnectionPoint != null) ? targetConnectionPoint.AssociatedDesigner : rootDesigner.HitTest(targetPoint).AssociatedDesigner;
//Collect the common parent chain of source and target
Dictionary<int, ActivityDesigner> parentDesignerDictionary = new Dictionary<int, ActivityDesigner>();
if (sourceDesigner != null)
{
//Collect designers in the source parent chain
CompositeActivityDesigner parentDesigner = sourceDesigner.ParentDesigner;
while (parentDesigner != null)
{
if (!parentDesignerDictionary.ContainsKey(parentDesigner.GetHashCode()))
parentDesignerDictionary.Add(parentDesigner.GetHashCode(), parentDesigner);
else
break;
parentDesigner = parentDesigner.ParentDesigner;
}
}
if (targetDesigner != null)
{
//Collect designer from target chain which are not in source chain
CompositeActivityDesigner parentDesigner = targetDesigner.ParentDesigner;
while (parentDesigner != null)
{
if (!parentDesignerDictionary.ContainsKey(parentDesigner.GetHashCode()))
parentDesignerDictionary.Add(parentDesigner.GetHashCode(), parentDesigner);
else
break;
parentDesigner = parentDesigner.ParentDesigner;
}
}
//Now go through the dictionary and add all the children that are not in the chain
rectanglesToExclude = new List<Rectangle>();
pointsToExclude = new List<Point>();
foreach (CompositeActivityDesigner parentDesigner in parentDesignerDictionary.Values)
{
ReadOnlyCollection<ActivityDesigner> containedDesigners = parentDesigner.ContainedDesigners;
for (int j = 0; j < containedDesigners.Count; j++)
{
ActivityDesigner activityDesigner = containedDesigners[j];
if (activityDesigner.IsVisible &&
!parentDesignerDictionary.ContainsKey(activityDesigner.GetHashCode()) &&
activityDesigner != sourceDesigner &&
activityDesigner != targetDesigner)
{
Rectangle rectangleToExclude = activityDesigner.Bounds;
rectangleToExclude.Inflate(ambientTheme.Margin);
rectanglesToExclude.Add(rectangleToExclude);
}
}
//
}
//Now get the exclusion paths for source designer and target designer
linesToExclude = new List<Point>();
if (sourceDesigner != null && sourceDesigner == targetDesigner && !sourceDesigner.IsRootDesigner)
{
linesToExclude.AddRange(GetDesignerEscapeCover(sourceDesigner, new object[] { source, target }));
}
else
{
if (sourceDesigner != null && !sourceDesigner.IsRootDesigner)
linesToExclude.AddRange(GetDesignerEscapeCover(sourceDesigner, new object[] { source }));
if (targetDesigner != null && !targetDesigner.IsRootDesigner)
{
bool needToEscapeTargetDesigner = true;
CompositeActivityDesigner sourceParentDesigner = (sourceDesigner != null) ? sourceDesigner.ParentDesigner : null;
while (sourceParentDesigner != null)
{
if (targetDesigner == sourceParentDesigner)
{
needToEscapeTargetDesigner = false;
break;
}
sourceParentDesigner = (sourceDesigner != null) ? sourceParentDesigner.ParentDesigner : null;
}
//need to escape the target designer only if the source is not inside the parent
if (needToEscapeTargetDesigner)
linesToExclude.AddRange(GetDesignerEscapeCover(targetDesigner, new object[] { target }));
}
}
}
private static IList<Point> GetDesignerEscapeCover(ActivityDesigner designer, ICollection<object> escapeLocations)
{
Rectangle bounds = designer.Bounds;
//Create dictionary of escape points
Dictionary<DesignerEdges, List<Point>> escapeEdges = new Dictionary<DesignerEdges, List<Point>>();
foreach (object escapeLocation in escapeLocations)
{
DesignerEdges escapeEdge = DesignerEdges.None;
Point escapePoint = Point.Empty;
if (escapeLocation is ConnectionPoint)
{
escapeEdge = ((ConnectionPoint)escapeLocation).ConnectionEdge;
escapePoint = ((ConnectionPoint)escapeLocation).Location;
}
else if (escapeLocation is Point)
{
escapePoint = (Point)escapeLocation;
escapeEdge = DesignerGeometryHelper.ClosestEdgeToPoint((Point)escapeLocation, bounds, DesignerEdges.All);
}
if (escapeEdge != DesignerEdges.None)
{
List<Point> escapePoints = null;
if (!escapeEdges.ContainsKey(escapeEdge))
{
escapePoints = new List<Point>();
escapeEdges.Add(escapeEdge, escapePoints);
}
else
{
escapePoints = escapeEdges[escapeEdge];
}
escapePoints.Add(escapePoint);
}
}
//Create a dictionary of four edges of the designer which will form cover which need to be escaped
Size margin = WorkflowTheme.CurrentTheme.AmbientTheme.Margin;
bounds.Inflate(margin);
Dictionary<DesignerEdges, Point[]> designerEdgeCover = new Dictionary<DesignerEdges, Point[]>();
designerEdgeCover.Add(DesignerEdges.Left, new Point[] { new Point(bounds.Left, bounds.Top), new Point(bounds.Left, bounds.Bottom) });
designerEdgeCover.Add(DesignerEdges.Top, new Point[] { new Point(bounds.Left, bounds.Top), new Point(bounds.Right, bounds.Top) });
designerEdgeCover.Add(DesignerEdges.Right, new Point[] { new Point(bounds.Right, bounds.Top), new Point(bounds.Right, bounds.Bottom) });
designerEdgeCover.Add(DesignerEdges.Bottom, new Point[] { new Point(bounds.Left, bounds.Bottom), new Point(bounds.Right, bounds.Bottom) });
List<Point> linesToExclude = new List<Point>();
foreach (DesignerEdges designerEdge in designerEdgeCover.Keys)
{
if (escapeEdges.ContainsKey(designerEdge))
{
Point[] edge = designerEdgeCover[designerEdge];
List<Point> escapePoints = escapeEdges[designerEdge];
List<Point> escapeCover = new List<Point>();
if (designerEdge == DesignerEdges.Left)
{
escapeCover.Add(new Point(edge[0].X, edge[0].Y));
for (int k = 0; k < escapePoints.Count; k++)
{
Point escapePoint = escapePoints[k];
if (escapePoint.X > edge[0].X && escapePoint.Y > edge[0].Y && escapePoint.Y < edge[1].Y)
{
escapeCover.Add(new Point(edge[0].X, escapePoint.Y - 1));
escapeCover.Add(new Point(escapePoint.X + 1, escapePoint.Y - 1));
escapeCover.Add(new Point(escapePoint.X + 1, escapePoint.Y + 1));
escapeCover.Add(new Point(edge[0].X, escapePoint.Y + 1));
}
}
escapeCover.Add(new Point(edge[0].X, edge[1].Y));
}
else if (designerEdge == DesignerEdges.Right)
{
escapeCover.Add(new Point(edge[0].X, edge[0].Y));
for (int k = 0; k < escapePoints.Count; k++)
{
Point escapePoint = escapePoints[k];
if (escapePoint.X < edge[0].X && escapePoint.Y > edge[0].Y && escapePoint.Y < edge[1].Y)
{
escapeCover.Add(new Point(edge[0].X, escapePoint.Y - 1));
escapeCover.Add(new Point(escapePoint.X - 1, escapePoint.Y - 1));
escapeCover.Add(new Point(escapePoint.X - 1, escapePoint.Y + 1));
escapeCover.Add(new Point(edge[0].X, escapePoint.Y + 1));
}
}
escapeCover.Add(new Point(edge[0].X, edge[1].Y));
}
else if (designerEdge == DesignerEdges.Top)
{
escapeCover.Add(new Point(edge[0].X, edge[0].Y));
for (int k = 0; k < escapePoints.Count; k++)
{
Point escapePoint = escapePoints[k];
if (escapePoint.Y > edge[0].Y && escapePoint.X > edge[0].X && escapePoint.X < edge[1].X)
{
escapeCover.Add(new Point(escapePoint.X - 1, edge[0].Y));
escapeCover.Add(new Point(escapePoint.X - 1, escapePoint.Y + 1));
escapeCover.Add(new Point(escapePoint.X + 1, escapePoint.Y + 1));
escapeCover.Add(new Point(escapePoint.X + 1, edge[0].Y));
}
}
escapeCover.Add(new Point(edge[1].X, edge[0].Y));
}
else if (designerEdge == DesignerEdges.Bottom)
{
//Add edge so that the connector does not pass through
escapeCover.Add(new Point(edge[0].X, edge[0].Y));
for (int k = 0; k < escapePoints.Count; k++)
{
Point escapePoint = escapePoints[k];
if (escapePoint.Y < edge[0].Y && escapePoint.X > edge[0].X && escapePoint.X < edge[1].X)
{
escapeCover.Add(new Point(escapePoint.X - 1, edge[0].Y));
escapeCover.Add(new Point(escapePoint.X - 1, escapePoint.Y - 1));
escapeCover.Add(new Point(escapePoint.X + 1, escapePoint.Y - 1));
escapeCover.Add(new Point(escapePoint.X + 1, edge[0].Y));
}
}
escapeCover.Add(new Point(edge[1].X, edge[0].Y));
}
for (int i = 1; i < escapeCover.Count; i++)
{
linesToExclude.Add(escapeCover[i - 1]);
linesToExclude.Add(escapeCover[i]);
}
}
else
{
linesToExclude.AddRange(designerEdgeCover[designerEdge]);
}
}
return linesToExclude.AsReadOnly();
}
private static bool AreAllSegmentsVerticalOrHorizontal(Point[] segments)
{
if (segments == null || segments.Length == 0)
return false;
for (int i = 1; i < segments.Length; i++)
{
if (segments[i - 1].X != segments[i].X && segments[i - 1].Y != segments[i].Y)
return false;
}
return true;
}
}
#endregion
#region Class ConnectorRouter
internal static class ConnectorRouter
{
private static readonly Size DefaultSize = new Size(4, 4);
public static Point[] Route(Point begin, Point end, Size margin, Rectangle enclosingRectangle, Rectangle[] rectanglesToExclude, Point[] linesToExclude, Point[] pointsToExclude)
{
List<Rectangle> excludedRectangles = new List<Rectangle>(rectanglesToExclude);
if (!enclosingRectangle.IsEmpty)
{
begin.X = Math.Min(Math.Max(begin.X, enclosingRectangle.Left + 1), enclosingRectangle.Right - 1);
begin.Y = Math.Min(Math.Max(begin.Y, enclosingRectangle.Top + 1), enclosingRectangle.Bottom - 1);
excludedRectangles.Insert(0, enclosingRectangle);
}
List<Point> excludedLines = new List<Point>(linesToExclude);
int width = Math.Max(margin.Width / 2, 1), height = Math.Max(margin.Height / 2, 1);
foreach (Point point in pointsToExclude)
{
excludedLines.Add(new Point(point.X - width, point.Y));
excludedLines.Add(new Point(point.X + width, point.Y));
excludedLines.Add(new Point(point.X, point.Y - height));
excludedLines.Add(new Point(point.X, point.Y + height));
}
return GetRoutedLineSegments(begin, end, margin, excludedRectangles.ToArray(), excludedLines.ToArray());
}
private static Point[] GetRoutedLineSegments(Point begin, Point end, Size margin, Rectangle[] rectanglesToExclude, Point[] linesToExclude)
{
if (rectanglesToExclude == null)
throw new ArgumentNullException("rectanglesToExclude");
if (linesToExclude == null)
throw new ArgumentNullException("linesToExclude");
if ((linesToExclude.Length % 2) > 0)
throw new ArgumentException(DR.GetString(DR.Error_Connector2));
CoverSet coverSet = new CoverSet(rectanglesToExclude, linesToExclude);
coverSet.ClearUsedLines();
Point A = begin;
Point B = end;
//escape points
List<Point> LeA = new List<Point>(); //escape points from begin to end
List<Point> LeB = new List<Point>(); //escape points from end to begin
//horizontal/vertical escape segments from A
List<ConnectorSegment> LhA = new List<ConnectorSegment>();
List<ConnectorSegment> LvA = new List<ConnectorSegment>();
//horizontal/vertical escape segments from B
List<ConnectorSegment> LhB = new List<ConnectorSegment>();
List<ConnectorSegment> LvB = new List<ConnectorSegment>();
Orientation orientationA = Orientation.Horizontal;
Orientation orientationB = Orientation.Horizontal;
//P0
LeA.Add(begin);
LeB.Add(end);
bool noEscapeA = false;
bool noEscapeB = false;
Nullable<Point> intersection = null;
ConnectorSegment intersectionSegmentA = null;
ConnectorSegment intersectionSegmentB = null;
try
{
do
{
//P1
if (noEscapeA)
{
if (noEscapeB)
{
//we failed to find the point
break;
}
else
{
//P2
#region swap A and B (with all appropriate lists and flags...)
List<Point> tempList = LeA;
LeA = LeB;
LeB = tempList;
Point tempPoint = A;
A = B;
B = tempPoint;
bool tempBool = noEscapeA;
noEscapeA = noEscapeB;
noEscapeB = tempBool;
Orientation tempOrientation = orientationA;
orientationA = orientationB;
orientationB = tempOrientation;
List<ConnectorSegment> tempListSegm = LhA;
LhA = LhB;
LhB = tempListSegm;
tempListSegm = LvA;
LvA = LvB;
LvB = tempListSegm;
#endregion
continue;
}
}
Point objectPoint = LeA[LeA.Count - 1];
Point targetPoint = B;
intersection = EscapeAlgorithm(coverSet, objectPoint, targetPoint,
ref LeA, ref LhA, ref LvA, ref LhB, ref LvB, ref orientationA,
out intersectionSegmentA, out intersectionSegmentB, margin, ref noEscapeA);
if (intersection != null)
{
break;
}
else
{
#region swap A and B (with all appropriate lists and flags...)
List<Point> tempList = LeA;
LeA = LeB;
LeB = tempList;
Point tempPoint = A;
A = B;
B = tempPoint;
bool tempBool = noEscapeA;
noEscapeA = noEscapeB;
noEscapeB = tempBool;
Orientation tempOrientation = orientationA;
orientationA = orientationB;
orientationB = tempOrientation;
List<ConnectorSegment> tempListSegm = LhA;
LhA = LhB;
LhB = tempListSegm;
tempListSegm = LvA;
LvA = LvB;
LvB = tempListSegm;
#endregion
}
} while (true);
//we failed
if (intersection == null)
return null;
//{
// //
// return newPath.ToArray();
//}
List<Point> refinedPath = new List<Point>();
#region P3 apply refinement algorithms
//first refinement algorithm
LeA = FirstRefinementAlgorithm(LeA, (Point)intersection, intersectionSegmentA);
LeB = FirstRefinementAlgorithm(LeB, (Point)intersection, intersectionSegmentB);
//before going into the second refinement, construct the full path
for (int j = LeA.Count - 1; j >= 0; j--)
refinedPath.Add(LeA[j]);
refinedPath.Add((Point)intersection);
for (int j = 0; j < LeB.Count; j++)
refinedPath.Add(LeB[j]);
//perform second refinement algorithm on the full path
SecondRefinementAlgorithm(coverSet, ref refinedPath, margin);
#endregion
if (refinedPath.Count > 1 && refinedPath[refinedPath.Count - 1] == begin)
refinedPath.Reverse();
return refinedPath.ToArray();
}
catch
{
//
return null;
}
}
#region Escape Algorithm
private static Nullable<Point> EscapeAlgorithm(CoverSet coverSet, Point Z, Point targetPoint,
ref List<Point> LeA, ref List<ConnectorSegment> LhA, ref List<ConnectorSegment> LvA, ref List<ConnectorSegment> LhB, ref List<ConnectorSegment> LvB,
ref Orientation orientationA, out ConnectorSegment intersectionSegmentA, out ConnectorSegment intersectionSegmentB, Size margin, ref bool noEscapeA)
{
Nullable<Point> intersection = null;
intersectionSegmentA = null;
intersectionSegmentB = null;
ConnectorSegment leftCover = coverSet.GetCover(Z, DesignerEdges.Left);
ConnectorSegment rightCover = coverSet.GetCover(Z, DesignerEdges.Right);
ConnectorSegment bottomCover = coverSet.GetCover(Z, DesignerEdges.Bottom);
ConnectorSegment topCover = coverSet.GetCover(Z, DesignerEdges.Top);
#region P1: construct escape line(s) - on the beginning of the algorithm it will create two lines
ConnectorSegment h = ConnectorSegment.SegmentFromLeftToRightCover(coverSet, Z);
LhA.Add(h);
ConnectorSegment v = ConnectorSegment.SegmentFromBottomToTopCover(coverSet, Z);
LvA.Add(v);
#endregion
#region P2 check if the new escape line(s) intersect with the existing ones
for (int i = 0; i < LvB.Count; i++)
{
ConnectorSegment segment = LvB[i];
intersection = h.Intersect(segment);
if (intersection != null)
{
intersectionSegmentA = h;
intersectionSegmentB = segment;
return intersection;
}
}
for (int i = 0; i < LhB.Count; i++)
{
ConnectorSegment segment = LhB[i];
intersection = v.Intersect(segment);
if (intersection != null)
{
intersectionSegmentA = v;
intersectionSegmentB = segment;
return intersection;
}
}
#endregion
//there was no intersection found above, continue on
//P3 find an escape point
#region Call Escape Process I
//Escape process I P1
Nullable<Point> escapePoint = EscapeProcessI(coverSet, Z, v, Orientation.Horizontal, margin);
if (escapePoint != null)
{
orientationA = Orientation.Vertical;
LeA.Add((Point)escapePoint);
return null;
}
//Escape process I P2
escapePoint = EscapeProcessI(coverSet, Z, h, Orientation.Vertical, margin);
if (escapePoint != null)
{
orientationA = Orientation.Horizontal;
LeA.Add((Point)escapePoint);
return null;
}
#endregion
#region Call Escape process II
bool intersectionFlag = false;
//flags indicating if we can still continue in the given directions
bool continue1, continue2, continue3, continue4;
Point r1 = Point.Empty, r2 = Point.Empty, r3 = Point.Empty, r4 = Point.Empty;
if (topCover != null)
r1 = new Point(Z.X, topCover.A.Y);
if (rightCover != null)
r2 = new Point(rightCover.A.X, Z.Y);
if (bottomCover != null)
r3 = new Point(Z.X, bottomCover.A.Y);
if (leftCover != null)
r4 = new Point(leftCover.A.X, Z.Y);
do
{
continue1 = continue2 = continue3 = continue4 = false;
if (topCover != null)
{
r1.Y -= margin.Height;
if (r1.Y > Z.Y)
{
continue1 = true;
Nullable<Point> escape = EscapeProcessII(coverSet, Orientation.Vertical,
ref LeA, ref LhA, ref LvA, ref LhB, ref LvB, Z, r1, margin, out intersectionFlag, out intersectionSegmentA, out intersectionSegmentB);
if (escape != null)
{
LvA.Add(v);
if (intersectionFlag)
return escape;
orientationA = Orientation.Horizontal;
coverSet.AddUsedEscapeLine(new ConnectorSegment(Z, r1));
coverSet.AddUsedEscapeLine(new ConnectorSegment(r1, (Point)escape));
LeA.Add((Point)escape);
return null;
}
}
}
if (rightCover != null)
{
r2.X -= margin.Width;
if (r2.X > Z.X)
{
continue2 = true;
Nullable<Point> escape = EscapeProcessII(coverSet, Orientation.Horizontal,
ref LeA, ref LhA, ref LvA, ref LhB, ref LvB, Z, r2, margin, out intersectionFlag, out intersectionSegmentA, out intersectionSegmentB);
if (escape != null)
{
LhA.Add(h);
if (intersectionFlag)
return escape;
orientationA = Orientation.Vertical;
coverSet.AddUsedEscapeLine(new ConnectorSegment(Z, r2));
coverSet.AddUsedEscapeLine(new ConnectorSegment(r2, (Point)escape));
LeA.Add((Point)escape);
return null;
}
}
}
if (bottomCover != null)
{
r3.Y += margin.Height;
if (r3.Y < Z.Y)
{
continue3 = true;
Nullable<Point> escape = EscapeProcessII(coverSet, Orientation.Vertical,
ref LeA, ref LhA, ref LvA, ref LhB, ref LvB, Z, r3, margin, out intersectionFlag, out intersectionSegmentA, out intersectionSegmentB);
if (escape != null)
{
LvA.Add(v);
if (intersectionFlag)
return escape;
orientationA = Orientation.Horizontal;
coverSet.AddUsedEscapeLine(new ConnectorSegment(Z, r3));
coverSet.AddUsedEscapeLine(new ConnectorSegment(r3, (Point)escape));
LeA.Add((Point)escape);
return null;
}
}
}
if (leftCover != null)
{
r4.X += margin.Width;
if (r4.X < Z.X)
{
continue4 = true;
Nullable<Point> escape = EscapeProcessII(coverSet, Orientation.Horizontal,
ref LeA, ref LhA, ref LvA, ref LhB, ref LvB, Z, r4, margin, out intersectionFlag, out intersectionSegmentA, out intersectionSegmentB);
if (escape != null)
{
LhA.Add(h);
if (intersectionFlag)
return escape;
orientationA = Orientation.Vertical;
coverSet.AddUsedEscapeLine(new ConnectorSegment(Z, r4));
coverSet.AddUsedEscapeLine(new ConnectorSegment(r4, (Point)escape));
LeA.Add((Point)escape);
return null;
}
}
}
//continue the loop until there is a place to go in either of the directions
} while (continue1 || continue2 || continue3 || continue4);
#endregion
noEscapeA = true;
return null;
}
#region Escape Process I
private static Nullable<Point> EscapeProcessI(CoverSet coverSet, Point Z,
ConnectorSegment escapeLine, Orientation orientation, Size margin)
{
//perform extremity point permutation by the distance from the object point
//when sorting points store to which segments they belong (this is needed further when
//deciding to side of the point to move if the extremity coincide with the object point
//on abscissa or ordinata axis)
List<DistanceFromPoint> extremitiesList = new List<DistanceFromPoint>(4); //at most four points
ConnectorSegment lesserCover = coverSet.GetCover(Z, (orientation == Orientation.Horizontal) ? DesignerEdges.Left : DesignerEdges.Bottom);
if (lesserCover != null)
{
AddBoundPoint(ref extremitiesList, lesserCover.A, lesserCover, Z);
AddBoundPoint(ref extremitiesList, lesserCover.B, lesserCover, Z);
}
ConnectorSegment higherCover = coverSet.GetCover(Z, (orientation == Orientation.Horizontal) ? DesignerEdges.Right : DesignerEdges.Top);
if (higherCover != null)
{
AddBoundPoint(ref extremitiesList, higherCover.A, higherCover, Z);
AddBoundPoint(ref extremitiesList, higherCover.B, higherCover, Z);
}
if (extremitiesList.Count == 0)
return null;
DistanceSorter.Sort(ref extremitiesList);
for (int i = 0; i < extremitiesList.Count; i++)
{
Point p = extremitiesList[i].P;
Point direction = new Point(Math.Sign(p.X - Z.X), Math.Sign(p.Y - Z.Y));
if (((orientation == Orientation.Vertical) ? direction.X : direction.Y) == 0)
{
//if extremity is on the same abscissa(ordinata) line with the object point, need to be more careful
ConnectorSegment segment = extremitiesList[i].ConnectorSegment;
p = segment.ExtendPointOutwards(p);
direction = new Point(Math.Sign(p.X - Z.X), Math.Sign(p.Y - Z.Y));
p = extremitiesList[i].P;
}
DesignerEdges side; //which side is the cover we are trying to escape
if (orientation == Orientation.Vertical)
//we are escaping either top or bottom
side = (direction.Y < 0) ? DesignerEdges.Bottom : DesignerEdges.Top;
else
//we are escaping either left or right
side = (direction.X < 0) ? DesignerEdges.Left : DesignerEdges.Right;
Point escapePoint;
if ((orientation == Orientation.Vertical))
escapePoint = new Point(p.X + direction.X * margin.Width, Z.Y);
else
escapePoint = new Point(Z.X, p.Y + direction.Y * margin.Height);
//the new escape point should
//1) lay on the given escape line (except the bounding points since they belong to covers)
//2) not lay on any of the already tested escape segments - all points belonging to them are already worthless
ConnectorSegment newEscapeLine = new ConnectorSegment(Z, escapePoint);
if (!coverSet.EscapeLineHasBeenUsed(newEscapeLine, escapePoint) &&
escapeLine.IsPointOnSegment(escapePoint) && escapeLine.A != escapePoint && escapeLine.B != escapePoint &&
coverSet.IsEscapePoint(Z, escapePoint, side))
{
coverSet.AddUsedEscapeLine(newEscapeLine);
return escapePoint;
}
}
return null;
}
private static void AddBoundPoint(ref List<DistanceFromPoint> extremitiesList, Point p, ConnectorSegment segment, Point Z)
{
if (p.X != int.MinValue && p.X != int.MaxValue && p.Y != int.MinValue && p.Y != int.MaxValue)
extremitiesList.Add(new DistanceFromPoint(segment, Z, p));
}
#endregion
#region Escape Process II
private static Nullable<Point> EscapeProcessII(CoverSet coverSet, Orientation orientation, ref List<Point> LeA,
ref List<ConnectorSegment> LhA, ref List<ConnectorSegment> LvA, ref List<ConnectorSegment> LhB, ref List<ConnectorSegment> LvB,
Point Z, Point R, Size margin, out bool intersectionFlag, out ConnectorSegment intersectionSegmentA, out ConnectorSegment intersectionSegmentB)
{
intersectionFlag = false;
intersectionSegmentA = null;
intersectionSegmentB = null;
//rebuild invalidated covers
ConnectorSegment h = ConnectorSegment.SegmentFromLeftToRightCover(coverSet, R);
ConnectorSegment v = ConnectorSegment.SegmentFromBottomToTopCover(coverSet, R);
//check intersections
for (int i = 0; i < LvB.Count; i++)
{
ConnectorSegment segment = LvB[i];
Nullable<Point> itersection = h.Intersect(segment);
if (itersection != null)
{
intersectionFlag = true;
intersectionSegmentA = h;
intersectionSegmentB = segment;
LeA.Add(R);
return itersection;
}
}
for (int i = 0; i < LhB.Count; i++)
{
ConnectorSegment segment = LhB[i];
Nullable<Point> itersection = v.Intersect(segment);
if (itersection != null)
{
intersectionFlag = true;
intersectionSegmentA = v;
intersectionSegmentB = segment;
LeA.Add(R);
return itersection;
}
}
Nullable<Point> escapePointI = null;
//now do both horizontal and vertical escape processes I from that point...
//the order is important based on 'orientation' argument
if (orientation == Orientation.Horizontal)
{
escapePointI = EscapeProcessI(coverSet, R, v, Orientation.Horizontal, margin);
if (escapePointI != null)
{
LvA.Add(v);
LeA.Add(R);
return escapePointI;
}
escapePointI = EscapeProcessI(coverSet, R, h, Orientation.Vertical, margin);
if (escapePointI != null)
{
LhA.Add(h);
LeA.Add(R);
return escapePointI;
}
}
else
{
escapePointI = EscapeProcessI(coverSet, R, h, Orientation.Vertical, margin);
if (escapePointI != null)
{
LhA.Add(h);
LeA.Add(R);
return escapePointI;
}
escapePointI = EscapeProcessI(coverSet, R, v, Orientation.Horizontal, margin);
if (escapePointI != null)
{
LvA.Add(v);
LeA.Add(R);
return escapePointI;
}
}
return null;
}
#endregion
#endregion
#region ConnectorSegment Refinement Algorithms
//remove all non-corner points
private static List<Point> FirstRefinementAlgorithm(List<Point> Le, Point intersection, ConnectorSegment intersectionSegment)
{
List<Point> refinedSet = new List<Point>();
ConnectorSegment k = intersectionSegment;
while (Le.Count > 0)
{
Point pi;
int i = Le.Count - 1;
//find the first point that lies on k
while (!k.PointLiesOnThisLine(Le[i]) && i > 0)
i--;
//find the least point that lies on k
while (i > 0 && k.PointLiesOnThisLine(Le[i - 1]))
i--;
//save the point
pi = Le[i];
refinedSet.Add(pi);
//remove all the points up to the found one from the original list
while (Le.Count > i)
Le.RemoveAt(i);
//continue with the points lying on the line perpendecular to the previous one and passing through the
//found point
k = k.PeprendecularThroughPoint(pi);
}
return refinedSet;
}
//remove superflous parts from the path
private static void SecondRefinementAlgorithm(CoverSet coverSet, ref List<Point> refinedPath, Size margin)
{
List<Point> newPath = new List<Point>();
#region Part I: extend every segment in the path to see if it intersects any other segment
int currentSegment = 0;
while (currentSegment < refinedPath.Count - 1)
{
Point a1 = refinedPath[currentSegment];
Point a2 = refinedPath[currentSegment + 1];
//need to construct a segment through the points that is limited by the covers
ConnectorSegment a = ConnectorSegment.ConstructBoundSegment(coverSet, a1, a2);
//try to intersect it with every segment after the current one
//and the next one (which we know does intersect with the current one)
int intersectingSegment = currentSegment + 2;
while (intersectingSegment < refinedPath.Count - 1)
{
Point b1 = refinedPath[intersectingSegment];
Point b2 = refinedPath[intersectingSegment + 1];
ConnectorSegment b = ConnectorSegment.ConstructBoundSegment(coverSet, b1, b2);
Nullable<Point> intersection = a.Intersect(b);
if (intersection != null)
{
newPath.Clear();
//need to remove all points between a1 and b2 and insert the point of intersectin there
for (int i = 0; i <= currentSegment; i++)
newPath.Add(refinedPath[i]);
newPath.Add((Point)intersection);
for (int i = intersectingSegment + 1; i < refinedPath.Count; i++)
newPath.Add(refinedPath[i]);
List<Point> temp = refinedPath;
refinedPath = newPath;
newPath = temp;
newPath.Clear();
//reset the second segment number and go through all segments once more
//until there are no intersections left
intersectingSegment = currentSegment + 2;
}
else
{
intersectingSegment++;
}
}
//we need to keep looking for intersections until we reach the very last one
currentSegment++;
}
#endregion
#region Part II: construct segments perpendecular to every segment and see if they intersect any other segment
currentSegment = 0;
while (currentSegment < refinedPath.Count - 1)
{
Point a1 = refinedPath[currentSegment];
Point a2 = refinedPath[currentSegment + 1];
//need to construct a segment through the points that is limited by the covers
bool intersected = false;
ConnectorSegment a = ConnectorSegment.ConstructBoundSegment(coverSet, a1, a2);
if (a != null)
{
Point direction = new Point(a2.X - a1.X, a2.Y - a1.Y);
//see how many intemediate points we can construct
int steps = Math.Max(Math.Abs(direction.X / margin.Width), Math.Abs(direction.Y / margin.Height)); //one of the values will be null
direction.X = Math.Sign(direction.X);
direction.Y = Math.Sign(direction.Y);
for (int i = 1; i <= steps; i++)
{
Point k = new Point(a1.X + i * margin.Width * direction.X, a1.Y + i * margin.Height * direction.Y);
if (k == a2)
break;
ConnectorSegment b = ConnectorSegment.ConstructBoundSegment(coverSet, k, (a.Orientation == Orientation.Horizontal) ? Orientation.Vertical : Orientation.Horizontal);
//now try to intersect that segment with every segment after the current one and the one after it
int intersectingSegment = currentSegment + 2;
while (intersectingSegment < refinedPath.Count - 1 && !intersected)
{
Point c1 = refinedPath[intersectingSegment];
Point c2 = refinedPath[intersectingSegment + 1];
ConnectorSegment c = new ConnectorSegment(c1, c2);
Nullable<Point> intersection = b.Intersect(c);
if (intersection != null && c.IsPointOnSegment((Point)intersection))
{
intersected = true;
newPath.Clear();
//need to remove all points between a1 and b2 and insert k and the point of intersection there
for (int j = 0; j <= currentSegment; j++)
newPath.Add(refinedPath[j]);
newPath.Add(k);
newPath.Add((Point)intersection);
for (int j = intersectingSegment + 1; j < refinedPath.Count; j++)
newPath.Add(refinedPath[j]);
List<Point> temp = refinedPath;
refinedPath = newPath;
newPath = temp;
newPath.Clear();
break;
}
intersectingSegment++;
}
//if there was an intersection, exit the for loop
if (intersected)
break;
}
}
//if there was an intersection, run the same segment again to see if there are other intersecting segments
if (!intersected)
currentSegment++;
}
#endregion
}
#endregion
#region Struct DistanceFromPoint
//sorting of extremities by closedness to point Z
private struct DistanceFromPoint
{
public ConnectorSegment ConnectorSegment;
public Point P;
public double Distance;
public DistanceFromPoint(ConnectorSegment segment, Point z, Point p)
{
this.ConnectorSegment = segment;
this.P = p;
this.Distance = ConnectorSegment.DistanceBetweenPoints(z, p);
}
}
#endregion
#region Class DistanceSorter
private sealed class DistanceSorter : IComparer<DistanceFromPoint>
{
private DistanceSorter() { }
public static void Sort(ref List<DistanceFromPoint> distances)
{
DistanceSorter sorter = new DistanceSorter();
distances.Sort(sorter);
}
int IComparer<DistanceFromPoint>.Compare(DistanceFromPoint lhs, DistanceFromPoint rhs)
{
if (lhs.Distance == rhs.Distance)
return 0;
else if (lhs.Distance > rhs.Distance)
return 1;
else return -1;
}
}
#endregion
#region Class ConnectorSegment
// represents a segment - the main entity in the routing algorithm
[DebuggerDisplay("Segment ( {A.X}, {A.Y} ) - ( {B.X},{B.Y} ), {Orientation}")]
private sealed class ConnectorSegment
{
private Point point1;
private Point point2;
private Orientation orientation;
public ConnectorSegment(Point point1, Point point2)
{
if (point1.X != point2.X && point1.Y != point2.Y)
throw new InvalidOperationException(SR.GetString(SR.Error_InvalidConnectorSegment));
this.point1 = point1;
this.point2 = point2;
this.orientation = ((this.point1.X == this.point2.X) ? Orientation.Vertical : Orientation.Horizontal);
}
public Point A
{
get
{
return this.point1;
}
}
public Point B
{
get
{
return this.point2;
}
}
public Orientation Orientation
{
get
{
return this.orientation;
}
}
//"segment l covers a point p, if the perpendicular from p to the line of which l is a segment intersects l"
//since a) we only have horizotal or vertical segments and b) some segments will be unbound, we have to store and use orientation
//flag to do analyzis
public bool Covers(Point p)
{
return (this.orientation == Orientation.Horizontal) ? (p.X >= Math.Min(this.point1.X, this.point2.X) && p.X <= Math.Max(this.point1.X, this.point2.X)) : (p.Y >= Math.Min(this.point1.Y, this.point2.Y) && p.Y <= Math.Max(this.point1.Y, this.point2.Y));
}
//see if the two segments intersect
//greatly simplified by the fact that we only have vertical or horizontal segments
//should work fine with {Max, Min}Value values
public Nullable<Point> Intersect(ConnectorSegment segment)
{
if (this.orientation == segment.Orientation)
return null;
ConnectorSegment vertical = (this.orientation == Orientation.Vertical) ? this : segment;
ConnectorSegment horizontal = (this.orientation == Orientation.Vertical) ? segment : this;
if (vertical.A.X < Math.Min(horizontal.A.X, horizontal.B.X) || vertical.A.X > Math.Max(horizontal.A.X, horizontal.B.X))
return null;
if (horizontal.A.Y < Math.Min(vertical.A.Y, vertical.B.Y) || horizontal.A.Y > Math.Max(vertical.A.Y, vertical.B.Y))
return null;
return new Point(vertical.A.X, horizontal.A.Y);
}
//we consider the whole line to which this segment belongs for this test
public bool PointLiesOnThisLine(Point p)
{
return (this.orientation == Orientation.Horizontal) ? p.Y == this.point1.Y : p.X == this.point1.X;
}
//we consider just the segment for this test
public bool IsPointOnSegment(Point p)
{
if ((this.orientation == Orientation.Horizontal && p.Y != this.point1.Y) || (this.orientation == Orientation.Vertical && p.X != this.point1.X))
return false;
int k = (this.orientation == Orientation.Horizontal) ? p.X : p.Y;
int k1 = (this.orientation == Orientation.Horizontal) ? this.point1.X : this.point1.Y;
int k2 = (this.orientation == Orientation.Horizontal) ? this.point2.X : this.point2.Y;
return k >= Math.Min(k1, k2) && k <= Math.Max(k1, k2);
}
//does the given segment overlap us?
//public bool Overlaps(ConnectorSegment segment)
//{
// if (Orientation != segment.Orientation)
// return false;
// int k0 = (this.Orientation == Orientation.Horizontal) ? this.point1.Y : this.point1.X;
// int k1 = (this.Orientation == Orientation.Horizontal) ? this.point1.X : this.point1.Y;
// int k2 = (this.Orientation == Orientation.Horizontal) ? this.point2.X : this.point2.Y;
// int l0 = (this.Orientation == Orientation.Horizontal) ? segment.A.Y : segment.A.X;
// int l1 = (this.Orientation == Orientation.Horizontal) ? segment.A.X : segment.A.Y;
// int l2 = (this.Orientation == Orientation.Horizontal) ? segment.B.X : segment.B.Y;
// if (k0 != l0)
// return false;
// int min = Math.Min(k1, k2);
// k2 = Math.Max(k1, k2);
// k1 = min;
// min = Math.Min(l1, l2);
// l2 = Math.Max(l1, l2);
// l1 = min;
// if (l1 >= k2 || k1 >= l2)
// return false;
// return true;
//}
public ConnectorSegment PeprendecularThroughPoint(Point p)
{
Orientation newOrientation = (this.orientation == Orientation.Horizontal) ? Orientation.Vertical : Orientation.Horizontal;
Point newPoint = new Point(p.X, p.Y);
if (newOrientation == Orientation.Horizontal)
newPoint.X = int.MaxValue;
else
newPoint.Y = int.MaxValue;
return new ConnectorSegment(p, newPoint);
}
public Point ExtendPointOutwards(Point p)
{
Debug.Assert(!(p != this.point1 && p != this.point2), "wrong extension requested");
if (p != this.point1 && p != this.point2)
return p;
int k = (this.orientation == Orientation.Horizontal) ? p.X : p.Y;
int k1 = (this.orientation == Orientation.Horizontal) ? this.point1.X : this.point1.Y;
int k2 = (this.orientation == Orientation.Horizontal) ? this.point2.X : this.point2.Y;
if (k == Math.Min(k1, k2))
k--;
else
k++;
return new Point((this.orientation == Orientation.Horizontal) ? k : p.X, (this.orientation == Orientation.Horizontal) ? p.Y : k);
}
public static double DistanceBetweenPoints(Point p, Point q)
{
return Math.Sqrt((double)(p.X - q.X) * (p.X - q.X) + (double)(p.Y - q.Y) * (p.Y - q.Y));
}
//given two points construct a segment through them from lesser cover to higher
public static ConnectorSegment ConstructBoundSegment(CoverSet coverSet, Point a, Point b)
{
//
if (a.X != b.X && a.Y != b.Y)
return null;
return ConstructBoundSegment(coverSet, a, (a.X == b.X) ? Orientation.Vertical : Orientation.Horizontal);
}
public static ConnectorSegment SegmentFromLeftToRightCover(CoverSet coverSet, Point p)
{
ConnectorSegment leftCover = coverSet.GetCover(p, DesignerEdges.Left);
ConnectorSegment rightCover = coverSet.GetCover(p, DesignerEdges.Right);
//construct horizontal escape segment
Point left = new Point((leftCover != null) ? leftCover.A.X : int.MinValue, p.Y);
Point right = new Point((rightCover != null) ? rightCover.A.X : int.MaxValue, p.Y);
ConnectorSegment h = new ConnectorSegment(left, right);
return h;
}
public static ConnectorSegment SegmentFromBottomToTopCover(CoverSet coverSet, Point p)
{
ConnectorSegment bottomCover = coverSet.GetCover(p, DesignerEdges.Bottom);
ConnectorSegment topCover = coverSet.GetCover(p, DesignerEdges.Top);
//construct vertical escape segment
Point bottom = new Point(p.X, (bottomCover != null) ? bottomCover.A.Y : int.MinValue);
Point top = new Point(p.X, (topCover != null) ? topCover.A.Y : int.MaxValue);
ConnectorSegment v = new ConnectorSegment(bottom, top);
return v;
}
public static ConnectorSegment ConstructBoundSegment(CoverSet coverSet, Point a, Orientation orientation)
{
return (orientation == Orientation.Horizontal) ? SegmentFromLeftToRightCover(coverSet, a) : SegmentFromBottomToTopCover(coverSet, a);
}
public override bool Equals(object obj)
{
ConnectorSegment segment = obj as ConnectorSegment;
if (segment == null)
return false;
return (this.point1 == segment.A && this.point2 == segment.B && Orientation == segment.Orientation);
}
public override int GetHashCode()
{
return this.point1.GetHashCode() ^ this.point2.GetHashCode() ^ Orientation.GetHashCode();
}
}
#endregion
#region Class CoverSet (Set of vertical and horizontal covers)
private sealed class CoverSet
{
private List<ConnectorSegment> verticalCovers = new List<ConnectorSegment>();
private List<ConnectorSegment> horizontalCovers = new List<ConnectorSegment>();
private List<ConnectorSegment> usedEscapeLine = new List<ConnectorSegment>();
public CoverSet(Rectangle[] rectanglesToExclude, Point[] linesToExclude)
{
foreach (Rectangle rectangle in rectanglesToExclude)
{
AddCover(new ConnectorSegment(new Point(rectangle.Left, rectangle.Top), new Point(rectangle.Left, rectangle.Bottom)));
AddCover(new ConnectorSegment(new Point(rectangle.Right, rectangle.Top), new Point(rectangle.Right, rectangle.Bottom)));
AddCover(new ConnectorSegment(new Point(rectangle.Left, rectangle.Top), new Point(rectangle.Right, rectangle.Top)));
AddCover(new ConnectorSegment(new Point(rectangle.Left, rectangle.Bottom), new Point(rectangle.Right, rectangle.Bottom)));
}
//Add the linesegments to cover
for (int i = 0; i < linesToExclude.Length / 2; i++)
AddCover(new ConnectorSegment(linesToExclude[i * 2], linesToExclude[(i * 2) + 1]));
}
public void ClearUsedLines()
{
this.usedEscapeLine.Clear();
}
public void AddCover(ConnectorSegment cover)
{
List<ConnectorSegment> covers = (cover.Orientation == Orientation.Vertical) ? this.verticalCovers : this.horizontalCovers;
for (int i = 0; i < covers.Count; i++)
{
ConnectorSegment existingCover = covers[i];
if (cover.IsPointOnSegment(existingCover.A) && cover.IsPointOnSegment(existingCover.B))
{
//both points of vertical are on the new segment, delete the vertical and add the new one instead
covers.RemoveAt(i);
break;
}
else if (existingCover.IsPointOnSegment(cover.A) && existingCover.IsPointOnSegment(cover.B))
{
//both points of the new segment are on an existing segment already - skip this one
return;
}
}
covers.Add(cover);
}
//public ReadOnlyCollection<ConnectorSegment> VerticalCovers
//{
// get
// {
// return this.verticalCovers.AsReadOnly();
// }
//}
//public ReadOnlyCollection<ConnectorSegment> HorizontalCovers
//{
// get
// {
// return this.horizontalCovers.AsReadOnly();
// }
//}
public void AddUsedEscapeLine(ConnectorSegment segment)
{
this.usedEscapeLine.Add(segment);
}
//the new escape point should not lay on any of the already used escape segments
public bool EscapeLineHasBeenUsed(ConnectorSegment segment, Point escapePoint)
{
for (int i = 0; i < this.usedEscapeLine.Count; i++)
{
ConnectorSegment usedSegment = this.usedEscapeLine[i];
if (usedSegment.IsPointOnSegment(escapePoint))
return true;
}
return false;
}
//get just the vertical covers for the given point out of all stored segments
//public List<ConnectorSegment> GetVerticalCovers(Point p)
//{
// List<ConnectorSegment> covers = new List<ConnectorSegment>();
// foreach (ConnectorSegment segment in this.verticalCovers)
// {
// if (segment.Covers(p))
// covers.Add(segment);
// }
// return covers;
//}
//get just the horizontal covers for the given point out of all stored segments
//public List<ConnectorSegment> GetHorizontalCovers(Point p)
//{
// List<ConnectorSegment> covers = new List<ConnectorSegment>();
// foreach (ConnectorSegment segment in this.horizontalCovers)
// {
// if (segment.Covers(p))
// covers.Add(segment);
// }
// return covers;
//}
//get the cover on the given side (closest cover to the given side) for the point out of all stored segments
public ConnectorSegment GetCover(Point p, DesignerEdges side)
{
ConnectorSegment cover = null;
int distance = 0;
if (side == DesignerEdges.Left || side == DesignerEdges.Right)
{
for (int i = 0; i < this.verticalCovers.Count; i++)
{
ConnectorSegment segment = this.verticalCovers[i];
int currentDistance = (side == DesignerEdges.Left) ? p.X - segment.A.X : segment.A.X - p.X;
if (currentDistance > 0 && segment.Covers(p))
{
if (cover == null || distance > currentDistance)
{
cover = segment;
distance = currentDistance;
}
}
}
}
else
{
for (int i = 0; i < this.horizontalCovers.Count; i++)
{
ConnectorSegment segment = this.horizontalCovers[i];
int currentDistance = (side == DesignerEdges.Bottom) ? p.Y - segment.A.Y : segment.A.Y - p.Y;
if (currentDistance > 0 && segment.Covers(p))
{
if (cover == null || distance > currentDistance)
{
cover = segment;
distance = currentDistance;
}
}
}
}
return cover;
}
//get the cover on the given side (closest cover to the given side) for the point out of all stored segments
public List<ConnectorSegment> GetCovers(Point p, DesignerEdges side)
{
List<ConnectorSegment> covers = new List<ConnectorSegment>();
if (side == DesignerEdges.Left || side == DesignerEdges.Right)
{
for (int i = 0; i < this.verticalCovers.Count; i++)
{
ConnectorSegment segment = this.verticalCovers[i];
int currentDistance = (side == DesignerEdges.Left) ? p.X - segment.A.X : segment.A.X - p.X;
if (currentDistance > 0 && segment.Covers(p))
covers.Add(segment);
}
}
else
{
for (int i = 0; i < this.horizontalCovers.Count; i++)
{
ConnectorSegment segment = this.horizontalCovers[i];
int currentDistance = (side == DesignerEdges.Bottom) ? p.Y - segment.A.Y : segment.A.Y - p.Y;
if (currentDistance > 0 && segment.Covers(p))
covers.Add(segment);
}
}
return covers;
}
public bool IsEscapePoint(Point origin, Point escape, DesignerEdges side)
{
//get the original cover
ConnectorSegment originalCover = this.GetCover(origin, side);
int originalDistance;
if (side == DesignerEdges.Left || side == DesignerEdges.Right)
originalDistance = originalCover.A.X - escape.X;
else
originalDistance = originalCover.A.Y - escape.Y;
// the escape point should not be covered by the the original cover
if (originalCover.Covers(escape))
return false;
//it should not also be covered by any member of other segments between the original cover and the original point
List<ConnectorSegment> newCovers = this.GetCovers(escape, side);
for (int i = 0; i < newCovers.Count; i++)
{
ConnectorSegment newCover = newCovers[i];
//should never happen, just in case...
if (newCover == originalCover)
return false;
int newDistance;
if (side == DesignerEdges.Left || side == DesignerEdges.Right)
newDistance = Math.Abs(newCover.A.X - escape.X);
else
newDistance = Math.Abs(newCover.A.Y - escape.Y);
if (Math.Sign(newDistance) == Math.Sign(originalDistance) && Math.Abs(newDistance) < Math.Abs(originalDistance))
return false;
}
return true;
}
}
#endregion
}
#endregion
}
|