Commits

Anonymous committed 8932701

More work on vertex positioning. Added some graph-related controls for later use.

  • Participants
  • Parent commits b54de4d

Comments (0)

Files changed (17)

UserProjects/sprucely/CSharp/Composer/Composer/Composer.csproj

     <Compile Include="UI\Behaviors\DragAndDrop\DropTargetInsertionAdorner.cs" />
     <Compile Include="UI\Behaviors\DragAndDrop\IDragSource.cs" />
     <Compile Include="UI\Behaviors\DragAndDrop\IDropTarget.cs" />
+    <Compile Include="UI\Controls\Connection.cs" />
+    <Compile Include="UI\Controls\ConnectionAdorner.cs" />
+    <Compile Include="UI\Controls\Connector.cs" />
+    <Compile Include="UI\Controls\ConnectorAdorner.cs" />
+    <Compile Include="UI\Controls\GraphCanvas.cs" />
+    <Compile Include="UI\Controls\GraphItem.cs" />
+    <Compile Include="UI\Controls\DragThumb.cs" />
+    <Compile Include="UI\Controls\ISelectable.cs" />
+    <Compile Include="UI\Controls\PathFinder.cs" />
+    <Compile Include="UI\Controls\RelativePositionPanel.cs" />
+    <Compile Include="UI\Controls\RubberBandAdorner.cs" />
+    <Compile Include="UI\Converters\EqualityToBooleanConverter.cs" />
     <Compile Include="UI\DispatcherHelper.cs" />
     <Compile Include="UI\ItemsControlExtensions.cs" />
     <Compile Include="UI\Screens\GraphEditor\LayoutAlgorithmFactory.cs" />

UserProjects/sprucely/CSharp/Composer/Composer/UI/Controls/Connection.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows.Controls;
+using System.ComponentModel;
+using System.Windows.Documents;
+using System.Windows.Media;
+using System.Windows;
+using System.Windows.Input;
+
+namespace Composer.UI.Controls
+{
+	public class Connection : Control, ISelectable, INotifyPropertyChanged
+	{
+		private Adorner connectionAdorner;
+
+		#region Properties
+
+		// source connector
+		private Connector source;
+		public Connector Source
+		{
+			get
+			{
+				return source;
+			}
+			set
+			{
+				if (source != value)
+				{
+					if (source != null)
+					{
+						source.PropertyChanged -= new PropertyChangedEventHandler(OnConnectorPositionChanged);
+						source.Connections.Remove(this);
+					}
+
+					source = value;
+
+					if (source != null)
+					{
+						source.Connections.Add(this);
+						source.PropertyChanged += new PropertyChangedEventHandler(OnConnectorPositionChanged);
+					}
+
+					UpdatePathGeometry();
+				}
+			}
+		}
+
+		// sink connector
+		private Connector sink;
+		public Connector Sink
+		{
+			get { return sink; }
+			set
+			{
+				if (sink != value)
+				{
+					if (sink != null)
+					{
+						sink.PropertyChanged -= new PropertyChangedEventHandler(OnConnectorPositionChanged);
+						sink.Connections.Remove(this);
+					}
+
+					sink = value;
+
+					if (sink != null)
+					{
+						sink.Connections.Add(this);
+						sink.PropertyChanged += new PropertyChangedEventHandler(OnConnectorPositionChanged);
+					}
+					UpdatePathGeometry();
+				}
+			}
+		}
+
+		// connection path geometry
+		private PathGeometry pathGeometry;
+		public PathGeometry PathGeometry
+		{
+			get { return pathGeometry; }
+			set
+			{
+				if (pathGeometry != value)
+				{
+					pathGeometry = value;
+					UpdateAnchorPosition();
+					OnPropertyChanged("PathGeometry");
+				}
+			}
+		}
+
+		// between source connector position and the beginning 
+		// of the path geometry we leave some space for visual reasons; 
+		// so the anchor position source really marks the beginning 
+		// of the path geometry on the source side
+		private Point anchorPositionSource;
+		public Point AnchorPositionSource
+		{
+			get { return anchorPositionSource; }
+			set
+			{
+				if (anchorPositionSource != value)
+				{
+					anchorPositionSource = value;
+					OnPropertyChanged("AnchorPositionSource");
+				}
+			}
+		}
+
+		// slope of the path at the anchor position
+		// needed for the rotation angle of the arrow
+		private double anchorAngleSource = 0;
+		public double AnchorAngleSource
+		{
+			get { return anchorAngleSource; }
+			set
+			{
+				if (anchorAngleSource != value)
+				{
+					anchorAngleSource = value;
+					OnPropertyChanged("AnchorAngleSource");
+				}
+			}
+		}
+
+		// analogue to source side
+		private Point anchorPositionSink;
+		public Point AnchorPositionSink
+		{
+			get { return anchorPositionSink; }
+			set
+			{
+				if (anchorPositionSink != value)
+				{
+					anchorPositionSink = value;
+					OnPropertyChanged("AnchorPositionSink");
+				}
+			}
+		}
+		// analogue to source side
+		private double anchorAngleSink = 0;
+		public double AnchorAngleSink
+		{
+			get { return anchorAngleSink; }
+			set
+			{
+				if (anchorAngleSink != value)
+				{
+					anchorAngleSink = value;
+					OnPropertyChanged("AnchorAngleSink");
+				}
+			}
+		}
+
+		private ArrowSymbol sourceArrowSymbol = ArrowSymbol.None;
+		public ArrowSymbol SourceArrowSymbol
+		{
+			get { return sourceArrowSymbol; }
+			set
+			{
+				if (sourceArrowSymbol != value)
+				{
+					sourceArrowSymbol = value;
+					OnPropertyChanged("SourceArrowSymbol");
+				}
+			}
+		}
+
+		public ArrowSymbol sinkArrowSymbol = ArrowSymbol.Arrow;
+		public ArrowSymbol SinkArrowSymbol
+		{
+			get { return sinkArrowSymbol; }
+			set
+			{
+				if (sinkArrowSymbol != value)
+				{
+					sinkArrowSymbol = value;
+					OnPropertyChanged("SinkArrowSymbol");
+				}
+			}
+		}
+
+		// specifies a point at half path length
+		private Point labelPosition;
+		public Point LabelPosition
+		{
+			get { return labelPosition; }
+			set
+			{
+				if (labelPosition != value)
+				{
+					labelPosition = value;
+					OnPropertyChanged("LabelPosition");
+				}
+			}
+		}
+
+		// pattern of dashes and gaps that is used to outline the connection path
+		private DoubleCollection strokeDashArray;
+		public DoubleCollection StrokeDashArray
+		{
+			get
+			{
+				return strokeDashArray;
+			}
+			set
+			{
+				if (strokeDashArray != value)
+				{
+					strokeDashArray = value;
+					OnPropertyChanged("StrokeDashArray");
+				}
+			}
+		}
+		// if connected, the ConnectionAdorner becomes visible
+		private bool isSelected;
+		public bool IsSelected
+		{
+			get { return isSelected; }
+			set
+			{
+				if (isSelected != value)
+				{
+					isSelected = value;
+					OnPropertyChanged("IsSelected");
+					if (isSelected)
+						ShowAdorner();
+					else
+						HideAdorner();
+				}
+			}
+		}
+
+		#endregion
+
+		public Connection(Connector source, Connector sink)
+		{
+			this.Source = source;
+			this.Sink = sink;
+			base.Unloaded += new RoutedEventHandler(Connection_Unloaded);
+		}
+
+		protected override void OnMouseDown(System.Windows.Input.MouseButtonEventArgs e)
+		{
+			base.OnMouseDown(e);
+
+			// usual selection business
+			GraphCanvas designer = VisualTreeHelper.GetParent(this) as GraphCanvas;
+			if (designer != null)
+				if ((Keyboard.Modifiers & (ModifierKeys.Shift | ModifierKeys.Control)) != ModifierKeys.None)
+					if (this.IsSelected)
+					{
+						this.IsSelected = false;
+						designer.SelectedItems.Remove(this);
+					}
+					else
+					{
+						this.IsSelected = true;
+						designer.SelectedItems.Add(this);
+					}
+				else if (!this.IsSelected)
+				{
+					foreach (ISelectable item in designer.SelectedItems)
+						item.IsSelected = false;
+
+					designer.SelectedItems.Clear();
+					this.IsSelected = true;
+					designer.SelectedItems.Add(this);
+				}
+			e.Handled = false;
+		}
+
+		void OnConnectorPositionChanged(object sender, PropertyChangedEventArgs e)
+		{
+			// whenever the 'Position' property of the source or sink Connector 
+			// changes we must update the connection path geometry
+			if (e.PropertyName.Equals("Position"))
+			{
+				UpdatePathGeometry();
+			}
+		}
+
+		private void UpdatePathGeometry()
+		{
+			if (Source != null && Sink != null)
+			{
+				PathGeometry geometry = new PathGeometry();
+				List<Point> linePoints = PathFinder.GetConnectionLine(Source.GetInfo(), Sink.GetInfo(), true);
+				if (linePoints.Count > 0)
+				{
+					PathFigure figure = new PathFigure();
+					figure.StartPoint = linePoints[0];
+					linePoints.Remove(linePoints[0]);
+					figure.Segments.Add(new PolyLineSegment(linePoints, true));
+					geometry.Figures.Add(figure);
+
+					this.PathGeometry = geometry;
+				}
+			}
+		}
+
+		private void UpdateAnchorPosition()
+		{
+			Point pathStartPoint, pathTangentAtStartPoint;
+			Point pathEndPoint, pathTangentAtEndPoint;
+			Point pathMidPoint, pathTangentAtMidPoint;
+
+			// the PathGeometry.GetPointAtFractionLength method gets the point and a tangent vector 
+			// on PathGeometry at the specified fraction of its length
+			this.PathGeometry.GetPointAtFractionLength(0, out pathStartPoint, out pathTangentAtStartPoint);
+			this.PathGeometry.GetPointAtFractionLength(1, out pathEndPoint, out pathTangentAtEndPoint);
+			this.PathGeometry.GetPointAtFractionLength(0.5, out pathMidPoint, out pathTangentAtMidPoint);
+
+			// get angle from tangent vector
+			this.AnchorAngleSource = Math.Atan2(-pathTangentAtStartPoint.Y, -pathTangentAtStartPoint.X) * (180 / Math.PI);
+			this.AnchorAngleSink = Math.Atan2(pathTangentAtEndPoint.Y, pathTangentAtEndPoint.X) * (180 / Math.PI);
+
+			// add some margin on source and sink side for visual reasons only
+			pathStartPoint.Offset(-pathTangentAtStartPoint.X * 5, -pathTangentAtStartPoint.Y * 5);
+			pathEndPoint.Offset(pathTangentAtEndPoint.X * 5, pathTangentAtEndPoint.Y * 5);
+
+			this.AnchorPositionSource = pathStartPoint;
+			this.AnchorPositionSink = pathEndPoint;
+			this.LabelPosition = pathMidPoint;
+		}
+
+		private void ShowAdorner()
+		{
+			// the ConnectionAdorner is created once for each Connection
+			if (this.connectionAdorner == null)
+			{
+				GraphCanvas designer = VisualTreeHelper.GetParent(this) as GraphCanvas;
+
+				AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(designer);
+				if (adornerLayer != null)
+				{
+					this.connectionAdorner = new ConnectionAdorner(designer, this);
+					adornerLayer.Add(this.connectionAdorner);
+				}
+			}
+			this.connectionAdorner.Visibility = Visibility.Visible;
+		}
+
+		internal void HideAdorner()
+		{
+			if (this.connectionAdorner != null)
+				this.connectionAdorner.Visibility = Visibility.Collapsed;
+		}
+
+		void Connection_Unloaded(object sender, RoutedEventArgs e)
+		{
+			// do some housekeeping when Connection is unloaded
+
+			// remove event handler
+			source.PropertyChanged -= new PropertyChangedEventHandler(OnConnectorPositionChanged);
+			sink.PropertyChanged -= new PropertyChangedEventHandler(OnConnectorPositionChanged);
+
+			// remove adorner
+			if (this.connectionAdorner != null)
+			{
+				GraphCanvas designer = VisualTreeHelper.GetParent(this) as GraphCanvas;
+
+				AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(designer);
+				if (adornerLayer != null)
+				{
+					adornerLayer.Remove(this.connectionAdorner);
+					this.connectionAdorner = null;
+				}
+			}
+		}
+
+		#region INotifyPropertyChanged Members
+
+		// we could use DependencyProperties as well to inform others of property changes
+		public event PropertyChangedEventHandler PropertyChanged;
+
+		protected void OnPropertyChanged(string name)
+		{
+			PropertyChangedEventHandler handler = PropertyChanged;
+			if (handler != null)
+			{
+				handler(this, new PropertyChangedEventArgs(name));
+			}
+		}
+
+		#endregion
+	}
+
+	public enum ArrowSymbol
+	{
+		None,
+		Arrow,
+		Diamond
+	}
+}

UserProjects/sprucely/CSharp/Composer/Composer/UI/Controls/ConnectionAdorner.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows.Media;
+using System.Windows.Documents;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.ComponentModel;
+using System.Windows.Input;
+using System.Windows;
+
+namespace Composer.UI.Controls
+{
+	public class ConnectionAdorner : Adorner
+	{
+		private GraphCanvas designerCanvas;
+		private Canvas adornerCanvas;
+		private Connection connection;
+		private PathGeometry pathGeometry;
+		private Connector fixConnector, dragConnector;
+		private Thumb sourceDragThumb, sinkDragThumb;
+		private Pen drawingPen;
+
+		private GraphItem hitDesignerItem;
+		private GraphItem HitDesignerItem
+		{
+			get { return hitDesignerItem; }
+			set
+			{
+				if (hitDesignerItem != value)
+				{
+					if (hitDesignerItem != null)
+						hitDesignerItem.IsDragConnectionOver = false;
+
+					hitDesignerItem = value;
+
+					if (hitDesignerItem != null)
+						hitDesignerItem.IsDragConnectionOver = true;
+				}
+			}
+		}
+
+		private Connector hitConnector;
+		private Connector HitConnector
+		{
+			get { return hitConnector; }
+			set
+			{
+				if (hitConnector != value)
+				{
+					hitConnector = value;
+				}
+			}
+		}
+
+		private VisualCollection visualChildren;
+		protected override int VisualChildrenCount
+		{
+			get
+			{
+				return this.visualChildren.Count;
+			}
+		}
+
+		protected override Visual GetVisualChild(int index)
+		{
+			return this.visualChildren[index];
+		}
+
+		public ConnectionAdorner(GraphCanvas designer, Connection connection)
+			: base(designer)
+		{
+			this.designerCanvas = designer;
+			adornerCanvas = new Canvas();
+			this.visualChildren = new VisualCollection(this);
+			this.visualChildren.Add(adornerCanvas);
+
+			this.connection = connection;
+			this.connection.PropertyChanged += new PropertyChangedEventHandler(AnchorPositionChanged);
+
+			InitializeDragThumbs();
+
+			drawingPen = new Pen(Brushes.LightSlateGray, 1);
+			drawingPen.LineJoin = PenLineJoin.Round;
+		}
+
+		private void InitializeDragThumbs()
+		{
+			Style dragThumbStyle = connection.FindResource("ConnectionAdornerThumbStyle") as Style;
+
+			//source drag thumb
+			sourceDragThumb = new Thumb();
+			Canvas.SetLeft(sourceDragThumb, connection.AnchorPositionSource.X);
+			Canvas.SetTop(sourceDragThumb, connection.AnchorPositionSource.Y);
+			this.adornerCanvas.Children.Add(sourceDragThumb);
+			if (dragThumbStyle != null)
+				sourceDragThumb.Style = dragThumbStyle;
+
+			sourceDragThumb.DragDelta += new DragDeltaEventHandler(thumbDragThumb_DragDelta);
+			sourceDragThumb.DragStarted += new DragStartedEventHandler(thumbDragThumb_DragStarted);
+			sourceDragThumb.DragCompleted += new DragCompletedEventHandler(thumbDragThumb_DragCompleted);
+
+			// sink drag thumb
+			sinkDragThumb = new Thumb();
+			Canvas.SetLeft(sinkDragThumb, connection.AnchorPositionSink.X);
+			Canvas.SetTop(sinkDragThumb, connection.AnchorPositionSink.Y);
+			this.adornerCanvas.Children.Add(sinkDragThumb);
+			if (dragThumbStyle != null)
+				sinkDragThumb.Style = dragThumbStyle;
+
+			sinkDragThumb.DragDelta += new DragDeltaEventHandler(thumbDragThumb_DragDelta);
+			sinkDragThumb.DragStarted += new DragStartedEventHandler(thumbDragThumb_DragStarted);
+			sinkDragThumb.DragCompleted += new DragCompletedEventHandler(thumbDragThumb_DragCompleted);
+		}
+		void AnchorPositionChanged(object sender, PropertyChangedEventArgs e)
+		{
+			if (e.PropertyName.Equals("AnchorPositionSource"))
+			{
+				Canvas.SetLeft(sourceDragThumb, connection.AnchorPositionSource.X);
+				Canvas.SetTop(sourceDragThumb, connection.AnchorPositionSource.Y);
+			}
+
+			if (e.PropertyName.Equals("AnchorPositionSink"))
+			{
+				Canvas.SetLeft(sinkDragThumb, connection.AnchorPositionSink.X);
+				Canvas.SetTop(sinkDragThumb, connection.AnchorPositionSink.Y);
+			}
+		}
+
+		void thumbDragThumb_DragCompleted(object sender, DragCompletedEventArgs e)
+		{
+			if (HitConnector != null)
+			{
+				if (connection != null)
+				{
+					if (connection.Source == fixConnector)
+						connection.Sink = this.HitConnector;
+					else
+						connection.Source = this.HitConnector;
+				}
+			}
+
+			this.HitDesignerItem = null;
+			this.HitConnector = null;
+			this.pathGeometry = null;
+			this.connection.StrokeDashArray = null;
+			this.InvalidateVisual();
+		}
+
+		void thumbDragThumb_DragStarted(object sender, DragStartedEventArgs e)
+		{
+			this.HitDesignerItem = null;
+			this.HitConnector = null;
+			this.pathGeometry = null;
+			this.Cursor = Cursors.Cross;
+			this.connection.StrokeDashArray = new DoubleCollection(new double[] { 1, 2 });
+
+			if (sender == sourceDragThumb)
+			{
+				fixConnector = connection.Sink;
+				dragConnector = connection.Source;
+			}
+			else if (sender == sinkDragThumb)
+			{
+				dragConnector = connection.Sink;
+				fixConnector = connection.Source;
+			}
+		}
+
+		void thumbDragThumb_DragDelta(object sender, DragDeltaEventArgs e)
+		{
+			Point currentPosition = Mouse.GetPosition(this);
+			this.HitTesting(currentPosition);
+			this.pathGeometry = UpdatePathGeometry(currentPosition);
+			this.InvalidateVisual();
+		}
+
+		protected override void OnRender(DrawingContext dc)
+		{
+			base.OnRender(dc);
+			dc.DrawGeometry(null, drawingPen, this.pathGeometry);
+		}
+
+		protected override Size ArrangeOverride(Size finalSize)
+		{
+			adornerCanvas.Arrange(new Rect(0, 0, this.designerCanvas.ActualWidth, this.designerCanvas.ActualHeight));
+			return finalSize;
+		}
+
+		private PathGeometry UpdatePathGeometry(Point position)
+		{
+			PathGeometry geometry = new PathGeometry();
+
+			ConnectorOrientation targetOrientation;
+			if (HitConnector != null)
+				targetOrientation = HitConnector.Orientation;
+			else
+				targetOrientation = dragConnector.Orientation;
+
+			List<Point> linePoints = PathFinder.GetConnectionLine(fixConnector.GetInfo(), position, targetOrientation);
+
+			if (linePoints.Count > 0)
+			{
+				PathFigure figure = new PathFigure();
+				figure.StartPoint = linePoints[0];
+				linePoints.Remove(linePoints[0]);
+				figure.Segments.Add(new PolyLineSegment(linePoints, true));
+				geometry.Figures.Add(figure);
+			}
+
+			return geometry;
+		}
+
+		private void HitTesting(Point hitPoint)
+		{
+			bool hitConnectorFlag = false;
+
+			DependencyObject hitObject = designerCanvas.InputHitTest(hitPoint) as DependencyObject;
+			while (hitObject != null &&
+				   hitObject != fixConnector.ParentDesignerItem &&
+				   hitObject.GetType() != typeof(GraphCanvas))
+			{
+				if (hitObject is Connector)
+				{
+					HitConnector = hitObject as Connector;
+					hitConnectorFlag = true;
+				}
+
+				if (hitObject is GraphItem)
+				{
+					HitDesignerItem = hitObject as GraphItem;
+					if (!hitConnectorFlag)
+						HitConnector = null;
+					return;
+				}
+				hitObject = VisualTreeHelper.GetParent(hitObject);
+			}
+
+			HitConnector = null;
+			HitDesignerItem = null;
+		}
+	}
+}

UserProjects/sprucely/CSharp/Composer/Composer/UI/Controls/Connector.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.ComponentModel;
+using System.Windows.Input;
+using System.Windows.Documents;
+using System.Windows.Media;
+
+namespace Composer.UI.Controls
+{
+	public class Connector : Control, INotifyPropertyChanged
+	{
+		// drag start point, relative to the DesignerCanvas
+		private Point? dragStartPoint = null;
+
+		public ConnectorOrientation Orientation { get; set; }
+
+		// center position of this Connector relative to the DesignerCanvas
+		private Point position;
+		public Point Position
+		{
+			get { return position; }
+			set
+			{
+				if (position != value)
+				{
+					position = value;
+					OnPropertyChanged("Position");
+				}
+			}
+		}
+
+		// the DesignerItem this Connector belongs to;
+		// retrieved from DataContext, which is set in the
+		// DesignerItem template
+		private GraphItem parentDesignerItem;
+		public GraphItem ParentDesignerItem
+		{
+			get
+			{
+				if (parentDesignerItem == null)
+					parentDesignerItem = this.DataContext as GraphItem;
+
+				return parentDesignerItem;
+			}
+		}
+
+		// keep track of connections that link to this connector
+		private List<Connection> connections;
+		public List<Connection> Connections
+		{
+			get
+			{
+				if (connections == null)
+					connections = new List<Connection>();
+				return connections;
+			}
+		}
+
+		public Connector()
+		{
+			// fired when layout changes
+			base.LayoutUpdated += new EventHandler(Connector_LayoutUpdated);
+		}
+
+		// when the layout changes we update the position property
+		void Connector_LayoutUpdated(object sender, EventArgs e)
+		{
+			GraphCanvas designer = GetDesignerCanvas(this);
+			if (designer != null)
+			{
+				//get centre position of this Connector relative to the DesignerCanvas
+				this.Position = this.TransformToAncestor(designer).Transform(new Point(this.Width / 2, this.Height / 2));
+			}
+		}
+
+		protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
+		{
+			base.OnMouseLeftButtonDown(e);
+			GraphCanvas canvas = GetDesignerCanvas(this);
+			if (canvas != null)
+			{
+				// position relative to DesignerCanvas
+				this.dragStartPoint = new Point?(e.GetPosition(canvas));
+				e.Handled = true;
+			}
+		}
+
+		protected override void OnMouseMove(MouseEventArgs e)
+		{
+			base.OnMouseMove(e);
+
+			// if mouse button is not pressed we have no drag operation, ...
+			if (e.LeftButton != MouseButtonState.Pressed)
+				this.dragStartPoint = null;
+
+			// but if mouse button is pressed and start point value is set we do have one
+			if (this.dragStartPoint.HasValue)
+			{
+				// create connection adorner 
+				GraphCanvas canvas = GetDesignerCanvas(this);
+				if (canvas != null)
+				{
+					AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(canvas);
+					if (adornerLayer != null)
+					{
+						ConnectorAdorner adorner = new ConnectorAdorner(canvas, this);
+						if (adorner != null)
+						{
+							adornerLayer.Add(adorner);
+							e.Handled = true;
+						}
+					}
+				}
+			}
+		}
+
+		internal ConnectorInfo GetInfo()
+		{
+			ConnectorInfo info = new ConnectorInfo();
+			info.DesignerItemLeft = GraphCanvas.GetLeft(this.ParentDesignerItem);
+			info.DesignerItemTop = GraphCanvas.GetTop(this.ParentDesignerItem);
+			info.DesignerItemSize = new Size(this.ParentDesignerItem.ActualWidth, this.ParentDesignerItem.ActualHeight);
+			info.Orientation = this.Orientation;
+			info.Position = this.Position;
+			return info;
+		}
+
+		// iterate through visual tree to get parent DesignerCanvas
+		private GraphCanvas GetDesignerCanvas(DependencyObject element)
+		{
+			while (element != null && !(element is GraphCanvas))
+				element = VisualTreeHelper.GetParent(element);
+
+			return element as GraphCanvas;
+		}
+
+		#region INotifyPropertyChanged Members
+
+		// we could use DependencyProperties as well to inform others of property changes
+		public event PropertyChangedEventHandler PropertyChanged;
+		protected void OnPropertyChanged(string name)
+		{
+			PropertyChangedEventHandler handler = PropertyChanged;
+			if (handler != null)
+			{
+				handler(this, new PropertyChangedEventArgs(name));
+			}
+		}
+
+		#endregion
+	}
+
+	// provides compact info about a connector; used for the 
+	// routing algorithm, instead of hand over a full fledged Connector
+	internal struct ConnectorInfo
+	{
+		public double DesignerItemLeft { get; set; }
+		public double DesignerItemTop { get; set; }
+		public Size DesignerItemSize { get; set; }
+		public Point Position { get; set; }
+		public ConnectorOrientation Orientation { get; set; }
+	}
+
+	public enum ConnectorOrientation
+	{
+		None,
+		Left,
+		Top,
+		Right,
+		Bottom,
+		Center
+	}
+}

UserProjects/sprucely/CSharp/Composer/Composer/UI/Controls/ConnectorAdorner.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows.Media;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows;
+
+namespace Composer.UI.Controls
+{
+	public class ConnectorAdorner : Adorner
+	{
+		private PathGeometry pathGeometry;
+		private GraphCanvas designerCanvas;
+		private Connector sourceConnector;
+		private Pen drawingPen;
+
+		private GraphItem hitDesignerItem;
+		private GraphItem HitDesignerItem
+		{
+			get { return hitDesignerItem; }
+			set
+			{
+				if (hitDesignerItem != value)
+				{
+					if (hitDesignerItem != null)
+						hitDesignerItem.IsDragConnectionOver = false;
+
+					hitDesignerItem = value;
+
+					if (hitDesignerItem != null)
+						hitDesignerItem.IsDragConnectionOver = true;
+				}
+			}
+		}
+
+		private Connector hitConnector;
+		private Connector HitConnector
+		{
+			get { return hitConnector; }
+			set
+			{
+				if (hitConnector != value)
+				{
+					hitConnector = value;
+				}
+			}
+		}
+
+		public ConnectorAdorner(GraphCanvas designer, Connector sourceConnector)
+			: base(designer)
+		{
+			this.designerCanvas = designer;
+			this.sourceConnector = sourceConnector;
+			drawingPen = new Pen(Brushes.LightSlateGray, 1);
+			drawingPen.LineJoin = PenLineJoin.Round;
+			this.Cursor = Cursors.Cross;
+		}
+
+		protected override void OnMouseUp(MouseButtonEventArgs e)
+		{
+			if (HitConnector != null)
+			{
+				Connector sourceConnector = this.sourceConnector;
+				Connector sinkConnector = this.HitConnector;
+				Connection newConnection = new Connection(sourceConnector, sinkConnector);
+
+				// connections are added with z-index of zero
+				this.designerCanvas.Children.Insert(0, newConnection);
+			}
+			if (HitDesignerItem != null)
+			{
+				this.HitDesignerItem.IsDragConnectionOver = false;
+			}
+
+			if (this.IsMouseCaptured) this.ReleaseMouseCapture();
+
+			AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.designerCanvas);
+			if (adornerLayer != null)
+			{
+				adornerLayer.Remove(this);
+			}
+		}
+
+		protected override void OnMouseMove(MouseEventArgs e)
+		{
+			if (e.LeftButton == MouseButtonState.Pressed)
+			{
+				if (!this.IsMouseCaptured) this.CaptureMouse();
+				HitTesting(e.GetPosition(this));
+				this.pathGeometry = GetPathGeometry(e.GetPosition(this));
+				this.InvalidateVisual();
+			}
+			else
+			{
+				if (this.IsMouseCaptured) this.ReleaseMouseCapture();
+			}
+		}
+
+		protected override void OnRender(DrawingContext dc)
+		{
+			base.OnRender(dc);
+			dc.DrawGeometry(null, drawingPen, this.pathGeometry);
+
+			// without a background the OnMouseMove event would not be fired
+			// Alternative: implement a Canvas as a child of this adorner, like
+			// the ConnectionAdorner does.
+			dc.DrawRectangle(Brushes.Transparent, null, new Rect(RenderSize));
+		}
+
+		private PathGeometry GetPathGeometry(Point position)
+		{
+			PathGeometry geometry = new PathGeometry();
+
+			ConnectorOrientation targetOrientation;
+			if (HitConnector != null)
+				targetOrientation = HitConnector.Orientation;
+			else
+				targetOrientation = ConnectorOrientation.None;
+
+			List<Point> pathPoints = PathFinder.GetConnectionLine(sourceConnector.GetInfo(), position, targetOrientation);
+
+			if (pathPoints.Count > 0)
+			{
+				PathFigure figure = new PathFigure();
+				figure.StartPoint = pathPoints[0];
+				pathPoints.Remove(pathPoints[0]);
+				figure.Segments.Add(new PolyLineSegment(pathPoints, true));
+				geometry.Figures.Add(figure);
+			}
+
+			return geometry;
+		}
+
+		private void HitTesting(Point hitPoint)
+		{
+			bool hitConnectorFlag = false;
+
+			DependencyObject hitObject = designerCanvas.InputHitTest(hitPoint) as DependencyObject;
+			while (hitObject != null &&
+				   hitObject != sourceConnector.ParentDesignerItem &&
+				   hitObject.GetType() != typeof(GraphCanvas))
+			{
+				if (hitObject is Connector)
+				{
+					HitConnector = hitObject as Connector;
+					hitConnectorFlag = true;
+				}
+
+				if (hitObject is GraphItem)
+				{
+					HitDesignerItem = hitObject as GraphItem;
+					if (!hitConnectorFlag)
+						HitConnector = null;
+					return;
+				}
+				hitObject = VisualTreeHelper.GetParent(hitObject);
+			}
+
+			HitConnector = null;
+			HitDesignerItem = null;
+		}
+	}
+}

UserProjects/sprucely/CSharp/Composer/Composer/UI/Controls/DragThumb.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows.Controls.Primitives;
+using System.Windows.Media;
+using System.Windows.Controls;
+
+namespace Composer.UI.Controls
+{
+	public class DragThumb : Thumb
+	{
+		public DragThumb()
+		{
+			base.DragDelta += new DragDeltaEventHandler(DragThumb_DragDelta);
+		}
+
+		void DragThumb_DragDelta(object sender, DragDeltaEventArgs e)
+		{
+			GraphItem designerItem = this.DataContext as GraphItem;
+			GraphCanvas designer = VisualTreeHelper.GetParent(designerItem) as GraphCanvas;
+			if (designerItem != null && designer != null && designerItem.IsSelected)
+			{
+				double minLeft = double.MaxValue;
+				double minTop = double.MaxValue;
+
+				// we only move DesignerItems
+				var designerItems = from item in designer.SelectedItems
+									where item is GraphItem
+									select item;
+
+				foreach (GraphItem item in designerItems)
+				{
+					double left = Canvas.GetLeft(item);
+					double top = Canvas.GetTop(item);
+
+					minLeft = double.IsNaN(left) ? 0 : Math.Min(left, minLeft);
+					minTop = double.IsNaN(top) ? 0 : Math.Min(top, minTop);
+				}
+
+				double deltaHorizontal = Math.Max(-minLeft, e.HorizontalChange);
+				double deltaVertical = Math.Max(-minTop, e.VerticalChange);
+
+				foreach (GraphItem item in designerItems)
+				{
+					double left = Canvas.GetLeft(item);
+					double top = Canvas.GetTop(item);
+
+					if (double.IsNaN(left)) left = 0;
+					if (double.IsNaN(top)) top = 0;
+
+					Canvas.SetLeft(item, left + deltaHorizontal);
+					Canvas.SetTop(item, top + deltaVertical);
+				}
+
+				designer.InvalidateMeasure();
+				e.Handled = true;
+			}
+		}
+	}
+}

UserProjects/sprucely/CSharp/Composer/Composer/UI/Controls/GraphCanvas.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows.Controls;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Documents;
+
+namespace Composer.UI.Controls
+{
+	public class GraphCanvas : Canvas
+	{
+		// start point of the rubberband drag operation
+		private Point? rubberbandSelectionStartPoint = null;
+
+		// keep track of selected items 
+		private List<ISelectable> selectedItems;
+		public List<ISelectable> SelectedItems
+		{
+			get
+			{
+				if (selectedItems == null)
+					selectedItems = new List<ISelectable>();
+				return selectedItems;
+			}
+			set
+			{
+				selectedItems = value;
+			}
+		}
+
+		public GraphCanvas()
+		{
+			this.AllowDrop = true;
+		}
+
+		protected override void OnMouseDown(MouseButtonEventArgs e)
+		{
+			base.OnMouseDown(e);
+			if (e.Source == this)
+			{
+				// in case that this click is the start for a 
+				// drag operation we cache the start point
+				this.rubberbandSelectionStartPoint = new Point?(e.GetPosition(this));
+
+				// if you click directly on the canvas all 
+				// selected items are 'de-selected'
+				foreach (ISelectable item in SelectedItems)
+					item.IsSelected = false;
+				selectedItems.Clear();
+
+				e.Handled = true;
+			}
+		}
+
+		protected override void OnMouseMove(MouseEventArgs e)
+		{
+			base.OnMouseMove(e);
+
+			// if mouse button is not pressed we have no drag operation, ...
+			if (e.LeftButton != MouseButtonState.Pressed)
+				this.rubberbandSelectionStartPoint = null;
+
+			// ... but if mouse button is pressed and start
+			// point value is set we do have one
+			if (this.rubberbandSelectionStartPoint.HasValue)
+			{
+				// create rubberband adorner
+				AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
+				if (adornerLayer != null)
+				{
+					RubberbandAdorner adorner = new RubberbandAdorner(this, rubberbandSelectionStartPoint);
+					if (adorner != null)
+					{
+						adornerLayer.Add(adorner);
+					}
+				}
+			}
+			e.Handled = true;
+		}
+
+		protected override void OnDrop(DragEventArgs e)
+		{
+			base.OnDrop(e);
+			//DragObject dragObject = e.Data.GetData(typeof(DragObject)) as DragObject;
+			//if (dragObject != null && !String.IsNullOrEmpty(dragObject.Xaml))
+			//{
+			//    DesignerItem newItem = null;
+			//    Object content = XamlReader.Load(XmlReader.Create(new StringReader(dragObject.Xaml)));
+
+			//    if (content != null)
+			//    {
+			//        newItem = new DesignerItem();
+			//        newItem.Content = content;
+
+			//        Point position = e.GetPosition(this);
+
+			//        if (dragObject.DesiredSize.HasValue)
+			//        {
+			//            Size desiredSize = dragObject.DesiredSize.Value;
+			//            newItem.Width = desiredSize.Width;
+			//            newItem.Height = desiredSize.Height;
+
+			//            DesignerCanvas.SetLeft(newItem, Math.Max(0, position.X - newItem.Width / 2));
+			//            DesignerCanvas.SetTop(newItem, Math.Max(0, position.Y - newItem.Height / 2));
+			//        }
+			//        else
+			//        {
+			//            DesignerCanvas.SetLeft(newItem, Math.Max(0, position.X));
+			//            DesignerCanvas.SetTop(newItem, Math.Max(0, position.Y));
+			//        }
+
+			//        this.Children.Add(newItem);
+
+			//        //update selection
+			//        foreach (ISelectable item in this.SelectedItems)
+			//            item.IsSelected = false;
+			//        SelectedItems.Clear();
+			//        newItem.IsSelected = true;
+			//        this.SelectedItems.Add(newItem);
+			//    }
+
+			//    e.Handled = true;
+			//}
+		}
+
+		protected override Size MeasureOverride(Size constraint)
+		{
+			Size size = new Size();
+			foreach (UIElement element in base.Children)
+			{
+				double left = Canvas.GetLeft(element);
+				double top = Canvas.GetTop(element);
+				left = double.IsNaN(left) ? 0 : left;
+				top = double.IsNaN(top) ? 0 : top;
+
+				//measure desired size for each child
+				element.Measure(constraint);
+
+				Size desiredSize = element.DesiredSize;
+				if (!double.IsNaN(desiredSize.Width) && !double.IsNaN(desiredSize.Height))
+				{
+					size.Width = Math.Max(size.Width, left + desiredSize.Width);
+					size.Height = Math.Max(size.Height, top + desiredSize.Height);
+				}
+			}
+			// add margin 
+			size.Width += 10;
+			size.Height += 10;
+			return size;
+		}
+	}
+}

UserProjects/sprucely/CSharp/Composer/Composer/UI/Controls/GraphItem.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace Composer.UI.Controls
+{
+	//These attributes identify the types of the named parts that are used for templating
+	[TemplatePart(Name = "PART_DragThumb", Type = typeof(DragThumb))]
+	[TemplatePart(Name = "PART_ResizeDecorator", Type = typeof(Control))]
+	[TemplatePart(Name = "PART_ConnectorDecorator", Type = typeof(Control))]
+	[TemplatePart(Name = "PART_ContentPresenter", Type = typeof(ContentPresenter))]
+	public class GraphItem : ContentControl, ISelectable
+	{
+		#region IsSelected Property
+
+		public bool IsSelected
+		{
+			get { return (bool)GetValue(IsSelectedProperty); }
+			set { SetValue(IsSelectedProperty, value); }
+		}
+		public static readonly DependencyProperty IsSelectedProperty =
+		  DependencyProperty.Register("IsSelected",
+									   typeof(bool),
+									   typeof(GraphItem),
+									   new FrameworkPropertyMetadata(false));
+
+		#endregion
+
+		#region DragThumbTemplate Property
+
+		// can be used to replace the default template for the DragThumb
+		public static readonly DependencyProperty DragThumbTemplateProperty =
+			DependencyProperty.RegisterAttached("DragThumbTemplate", typeof(ControlTemplate), typeof(GraphItem));
+
+		public static ControlTemplate GetDragThumbTemplate(UIElement element)
+		{
+			return (ControlTemplate)element.GetValue(DragThumbTemplateProperty);
+		}
+
+		public static void SetDragThumbTemplate(UIElement element, ControlTemplate value)
+		{
+			element.SetValue(DragThumbTemplateProperty, value);
+		}
+
+		#endregion
+
+		#region ConnectorDecoratorTemplate Property
+
+		// can be used to replace the default template for the ConnectorDecorator
+		public static readonly DependencyProperty ConnectorDecoratorTemplateProperty =
+			DependencyProperty.RegisterAttached("ConnectorDecoratorTemplate", typeof(ControlTemplate), typeof(GraphItem));
+
+		public static ControlTemplate GetConnectorDecoratorTemplate(UIElement element)
+		{
+			return (ControlTemplate)element.GetValue(ConnectorDecoratorTemplateProperty);
+		}
+
+		public static void SetConnectorDecoratorTemplate(UIElement element, ControlTemplate value)
+		{
+			element.SetValue(ConnectorDecoratorTemplateProperty, value);
+		}
+
+		#endregion
+
+		#region IsDragConnectionOver
+
+		// while drag connection procedure is ongoing and the mouse moves over 
+		// this item this value is true; if true the ConnectorDecorator is triggered
+		// to be visible, see template
+		public bool IsDragConnectionOver
+		{
+			get { return (bool)GetValue(IsDragConnectionOverProperty); }
+			set { SetValue(IsDragConnectionOverProperty, value); }
+		}
+		public static readonly DependencyProperty IsDragConnectionOverProperty =
+			DependencyProperty.Register("IsDragConnectionOver",
+										 typeof(bool),
+										 typeof(GraphItem),
+										 new FrameworkPropertyMetadata(false));
+
+		#endregion
+
+		static GraphItem()
+		{
+			// set the key to reference the style for this control
+			FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(
+				typeof(GraphItem), new FrameworkPropertyMetadata(typeof(GraphItem)));
+		}
+
+		public GraphItem()
+		{
+			this.Loaded += new RoutedEventHandler(DesignerItem_Loaded);
+		}
+
+		protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
+		{
+			base.OnPreviewMouseDown(e);
+			GraphCanvas designer = VisualTreeHelper.GetParent(this) as GraphCanvas;
+
+			// update selection
+			if (designer != null)
+				if ((Keyboard.Modifiers & (ModifierKeys.Shift | ModifierKeys.Control)) != ModifierKeys.None)
+					if (this.IsSelected)
+					{
+						this.IsSelected = false;
+						designer.SelectedItems.Remove(this);
+					}
+					else
+					{
+						this.IsSelected = true;
+						designer.SelectedItems.Add(this);
+					}
+				else if (!this.IsSelected)
+				{
+					foreach (ISelectable item in designer.SelectedItems)
+						item.IsSelected = false;
+
+					designer.SelectedItems.Clear();
+					this.IsSelected = true;
+					designer.SelectedItems.Add(this);
+				}
+			e.Handled = false;
+		}
+
+		void DesignerItem_Loaded(object sender, RoutedEventArgs e)
+		{
+			// if DragThumbTemplate and ConnectorDecoratorTemplate properties of this class
+			// are set these templates are applied; 
+			// Note: this method is only executed when the Loaded event is fired, so
+			// setting DragThumbTemplate or ConnectorDecoratorTemplate properties after
+			// will have no effect.
+			if (base.Template != null)
+			{
+				ContentPresenter contentPresenter =
+					this.Template.FindName("PART_ContentPresenter", this) as ContentPresenter;
+				if (contentPresenter != null)
+				{
+					UIElement contentVisual = VisualTreeHelper.GetChild(contentPresenter, 0) as UIElement;
+					if (contentVisual != null)
+					{
+						DragThumb thumb = this.Template.FindName("PART_DragThumb", this) as DragThumb;
+						Control connectorDecorator = this.Template.FindName("PART_ConnectorDecorator", this) as Control;
+
+						if (thumb != null)
+						{
+							ControlTemplate template =
+								GraphItem.GetDragThumbTemplate(contentVisual) as ControlTemplate;
+							if (template != null)
+								thumb.Template = template;
+						}
+
+
+						if (connectorDecorator != null)
+						{
+							ControlTemplate template =
+								GraphItem.GetConnectorDecoratorTemplate(contentVisual) as ControlTemplate;
+							if (template != null)
+								connectorDecorator.Template = template;
+						}
+					}
+				}
+			}
+		}
+	}
+}

UserProjects/sprucely/CSharp/Composer/Composer/UI/Controls/ISelectable.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Composer.UI.Controls
+{
+	// Common interface for items that can be selected
+	// on the DesignerCanvas; used by DesignerItem and Connection
+	public interface ISelectable
+	{
+		bool IsSelected { get; set; }
+	}
+}

UserProjects/sprucely/CSharp/Composer/Composer/UI/Controls/PathFinder.cs

+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Composer.UI.Controls
+{
+	// Helper class to provide an orthogonal connection path
+	internal class PathFinder
+	{
+		private const int margin = 20;
+
+		internal static List<Point> GetConnectionLine(ConnectorInfo source, ConnectorInfo sink, bool showLastLine)
+		{
+			List<Point> linePoints = new List<Point>();
+
+			Rect rectSource = GetRectWithMargin(source, margin);
+			Rect rectSink = GetRectWithMargin(sink, margin);
+
+			Point startPoint = GetOffsetPoint(source, rectSource);
+			Point endPoint = GetOffsetPoint(sink, rectSink);
+
+			linePoints.Add(startPoint);
+			Point currentPoint = startPoint;
+
+			if (!rectSink.Contains(currentPoint) && !rectSource.Contains(endPoint))
+			{
+				while (true)
+				{
+					#region source node
+
+					if (IsPointVisible(currentPoint, endPoint, new Rect[] { rectSource, rectSink }))
+					{
+						linePoints.Add(endPoint);
+						currentPoint = endPoint;
+						break;
+					}
+
+					Point neighbour = GetNearestVisibleNeighborSink(currentPoint, endPoint, sink, rectSource, rectSink);
+					if (!double.IsNaN(neighbour.X))
+					{
+						linePoints.Add(neighbour);
+						linePoints.Add(endPoint);
+						currentPoint = endPoint;
+						break;
+					}
+
+					if (currentPoint == startPoint)
+					{
+						bool flag;
+						Point n = GetNearestNeighborSource(source, endPoint, rectSource, rectSink, out flag);
+						linePoints.Add(n);
+						currentPoint = n;
+
+						if (!IsRectVisible(currentPoint, rectSink, new Rect[] { rectSource }))
+						{
+							Point n1, n2;
+							GetOppositeCorners(source.Orientation, rectSource, out n1, out n2);
+							if (flag)
+							{
+								linePoints.Add(n1);
+								currentPoint = n1;
+							}
+							else
+							{
+								linePoints.Add(n2);
+								currentPoint = n2;
+							}
+							if (!IsRectVisible(currentPoint, rectSink, new Rect[] { rectSource }))
+							{
+								if (flag)
+								{
+									linePoints.Add(n2);
+									currentPoint = n2;
+								}
+								else
+								{
+									linePoints.Add(n1);
+									currentPoint = n1;
+								}
+							}
+						}
+					}
+					#endregion
+
+					#region sink node
+
+					else // from here on we jump to the sink node
+					{
+						Point n1, n2; // neighbour corner
+						Point s1, s2; // opposite corner
+						GetNeighborCorners(sink.Orientation, rectSink, out s1, out s2);
+						GetOppositeCorners(sink.Orientation, rectSink, out n1, out n2);
+
+						bool n1Visible = IsPointVisible(currentPoint, n1, new Rect[] { rectSource, rectSink });
+						bool n2Visible = IsPointVisible(currentPoint, n2, new Rect[] { rectSource, rectSink });
+
+						if (n1Visible && n2Visible)
+						{
+							if (rectSource.Contains(n1))
+							{
+								linePoints.Add(n2);
+								if (rectSource.Contains(s2))
+								{
+									linePoints.Add(n1);
+									linePoints.Add(s1);
+								}
+								else
+									linePoints.Add(s2);
+
+								linePoints.Add(endPoint);
+								currentPoint = endPoint;
+								break;
+							}
+
+							if (rectSource.Contains(n2))
+							{
+								linePoints.Add(n1);
+								if (rectSource.Contains(s1))
+								{
+									linePoints.Add(n2);
+									linePoints.Add(s2);
+								}
+								else
+									linePoints.Add(s1);
+
+								linePoints.Add(endPoint);
+								currentPoint = endPoint;
+								break;
+							}
+
+							if ((Distance(n1, endPoint) <= Distance(n2, endPoint)))
+							{
+								linePoints.Add(n1);
+								if (rectSource.Contains(s1))
+								{
+									linePoints.Add(n2);
+									linePoints.Add(s2);
+								}
+								else
+									linePoints.Add(s1);
+								linePoints.Add(endPoint);
+								currentPoint = endPoint;
+								break;
+							}
+							else
+							{
+								linePoints.Add(n2);
+								if (rectSource.Contains(s2))
+								{
+									linePoints.Add(n1);
+									linePoints.Add(s1);
+								}
+								else
+									linePoints.Add(s2);
+								linePoints.Add(endPoint);
+								currentPoint = endPoint;
+								break;
+							}
+						}
+						else if (n1Visible)
+						{
+							linePoints.Add(n1);
+							if (rectSource.Contains(s1))
+							{
+								linePoints.Add(n2);
+								linePoints.Add(s2);
+							}
+							else
+								linePoints.Add(s1);
+							linePoints.Add(endPoint);
+							currentPoint = endPoint;
+							break;
+						}
+						else
+						{
+							linePoints.Add(n2);
+							if (rectSource.Contains(s2))
+							{
+								linePoints.Add(n1);
+								linePoints.Add(s1);
+							}
+							else
+								linePoints.Add(s2);
+							linePoints.Add(endPoint);
+							currentPoint = endPoint;
+							break;
+						}
+					}
+					#endregion
+				}
+			}
+			else
+			{
+				linePoints.Add(endPoint);
+			}
+
+			linePoints = OptimizeLinePoints(linePoints, new Rect[] { rectSource, rectSink }, source.Orientation, sink.Orientation);
+
+			CheckPathEnd(source, sink, showLastLine, linePoints);
+			return linePoints;
+		}
+
+		internal static List<Point> GetConnectionLine(ConnectorInfo source, Point sinkPoint, ConnectorOrientation preferredOrientation)
+		{
+			List<Point> linePoints = new List<Point>();
+			Rect rectSource = GetRectWithMargin(source, 10);
+			Point startPoint = GetOffsetPoint(source, rectSource);
+			Point endPoint = sinkPoint;
+
+			linePoints.Add(startPoint);
+			Point currentPoint = startPoint;
+
+			if (!rectSource.Contains(endPoint))
+			{
+				while (true)
+				{
+					if (IsPointVisible(currentPoint, endPoint, new Rect[] { rectSource }))
+					{
+						linePoints.Add(endPoint);
+						break;
+					}
+
+					bool sideFlag;
+					Point n = GetNearestNeighborSource(source, endPoint, rectSource, out sideFlag);
+					linePoints.Add(n);
+					currentPoint = n;
+
+					if (IsPointVisible(currentPoint, endPoint, new Rect[] { rectSource }))
+					{
+						linePoints.Add(endPoint);
+						break;
+					}
+					else
+					{
+						Point n1, n2;
+						GetOppositeCorners(source.Orientation, rectSource, out n1, out n2);
+						if (sideFlag)
+							linePoints.Add(n1);
+						else
+							linePoints.Add(n2);
+
+						linePoints.Add(endPoint);
+						break;
+					}
+				}
+			}
+			else
+			{
+				linePoints.Add(endPoint);
+			}
+
+			if (preferredOrientation != ConnectorOrientation.None)
+				linePoints = OptimizeLinePoints(linePoints, new Rect[] { rectSource }, source.Orientation, preferredOrientation);
+			else
+				linePoints = OptimizeLinePoints(linePoints, new Rect[] { rectSource }, source.Orientation, GetOpositeOrientation(source.Orientation));
+
+			return linePoints;
+		}
+
+		private static List<Point> OptimizeLinePoints(List<Point> linePoints, Rect[] rectangles, ConnectorOrientation sourceOrientation, ConnectorOrientation sinkOrientation)
+		{
+			List<Point> points = new List<Point>();
+			int cut = 0;
+
+			for (int i = 0; i < linePoints.Count; i++)
+			{
+				if (i >= cut)
+				{
+					for (int k = linePoints.Count - 1; k > i; k--)
+					{
+						if (IsPointVisible(linePoints[i], linePoints[k], rectangles))
+						{
+							cut = k;
+							break;
+						}
+					}
+					points.Add(linePoints[i]);
+				}
+			}
+
+			#region Line
+			for (int j = 0; j < points.Count - 1; j++)
+			{
+				if (points[j].X != points[j + 1].X && points[j].Y != points[j + 1].Y)
+				{
+					ConnectorOrientation orientationFrom;
+					ConnectorOrientation orientationTo;
+
+					// orientation from point
+					if (j == 0)
+						orientationFrom = sourceOrientation;
+					else
+						orientationFrom = GetOrientation(points[j], points[j - 1]);
+
+					// orientation to pint 
+					if (j == points.Count - 2)
+						orientationTo = sinkOrientation;
+					else
+						orientationTo = GetOrientation(points[j + 1], points[j + 2]);
+
+
+					if ((orientationFrom == ConnectorOrientation.Left || orientationFrom == ConnectorOrientation.Right) &&
+						(orientationTo == ConnectorOrientation.Left || orientationTo == ConnectorOrientation.Right))
+					{
+						double centerX = Math.Min(points[j].X, points[j + 1].X) + Math.Abs(points[j].X - points[j + 1].X) / 2;
+						points.Insert(j + 1, new Point(centerX, points[j].Y));
+						points.Insert(j + 2, new Point(centerX, points[j + 2].Y));
+						if (points.Count - 1 > j + 3)
+							points.RemoveAt(j + 3);
+						return points;
+					}
+
+					if ((orientationFrom == ConnectorOrientation.Top || orientationFrom == ConnectorOrientation.Bottom) &&
+						(orientationTo == ConnectorOrientation.Top || orientationTo == ConnectorOrientation.Bottom))
+					{
+						double centerY = Math.Min(points[j].Y, points[j + 1].Y) + Math.Abs(points[j].Y - points[j + 1].Y) / 2;
+						points.Insert(j + 1, new Point(points[j].X, centerY));
+						points.Insert(j + 2, new Point(points[j + 2].X, centerY));
+						if (points.Count - 1 > j + 3)
+							points.RemoveAt(j + 3);
+						return points;
+					}
+
+					if ((orientationFrom == ConnectorOrientation.Left || orientationFrom == ConnectorOrientation.Right) &&
+						(orientationTo == ConnectorOrientation.Top || orientationTo == ConnectorOrientation.Bottom))
+					{
+						points.Insert(j + 1, new Point(points[j + 1].X, points[j].Y));
+						return points;