Hi Michal,
Following is the new source code of the NCreateConnectorTool, which will enter the next release it has two properties - SnapStartPlugToShape and SnapEndPlugToShape which control whether to additionally try to snap to the default shape port below the mouse pointer:
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Diagnostics;
using System.Windows.Forms;
using Nevron.GraphicsCore;
using Nevron.Dom;
using Nevron.Diagram.Filters;
using Nevron.Diagram.Batches;
namespace Nevron.Diagram.WinForm
{
///
/// The NCreateConnectorTool creates new connectors
/// [Serializable]
public class NCreateConnectorTool : NCreateElementTool
{
#region Constructors
///
/// Default constructor
/// public NCreateConnectorTool()
: base(NDWFR.ToolCreateConnector)
{
m_ConnectorType = ConnectorType.Line;
m_bAllowCreateDisconnected = true;
m_bAllowCreateReflexive = true;
}
#endregion
#region Interface implementations
#region INMouseEventProcessor
///
/// Processes the mouse move event
/// ///
mouse event arguments
public override bool ProcessMouseMove(MouseEventArgs e)
{
base.ProcessMouseMove(e);
// if not active - highlight the potential FROM port and request ready to activate cursor
if (m_bIsActive == false)
{
NSnapPointResult res = SnapStartPlug(m_Controller.m_MouseInfo);
NPort port = res.GetValidInwardPort(null);
HighlightPorts(port, null);
RequestReadyToActivateCursor(NDWFR.CursorCreateConnector);
return false;
}
try
{
// snap end point
m_EndSnapRes = SnapEndPlug(m_Controller.m_MouseInfo);
// try to leave the FROM shape
if (m_bHasLeftFromShape == false)
{
m_bHasLeftFromShape = IsMouseInFromShape();
}
// if connector can be created -> highlight the FROM and potential TO ports
// else -> remove highlights and request NO cursor
if (CanCreateConnector())
{
// redefine preview
NShape connector = (m_Preview as NShape);
RedefineConnector(connector, m_StartSnapRes.SnappedPoint, m_EndSnapRes.SnappedPoint);
connector.Visible = true;
// highligth ports, request cursor and valid drag bounds
HighlightPorts(m_StartSnapRes.GetValidInwardPort(null), m_EndSnapRes.GetValidInwardPort(null));
m_View.m_InteractivityManager.RequestCursor(NDWFR.CursorCreateConnector);
m_View.OnDragging(connector.Bounds);
}
else
{
// hide preview
(m_Preview as NShape).Visible = false;
// hide ports, request no cursor and no valid drag bounds
m_View.HighlightedPoints = null;
m_View.m_InteractivityManager.RequestCursor(Cursors.No);
m_View.OnDragging(NRectangleF.Empty);
}
}
catch (Exception ex)
{
Trace.WriteLine("Failed to update connector preview. Exception was: " + ex.Message);
}
return true;
}
#endregion
#endregion
#region Properties
///
/// Specifies the type of connector, which must be created
/// ///
/// By default set to Line
/// [Category("Behavior")]
[Description("Specifies the type of connector, which must be created")]
[DefaultValue(ConnectorType.Line)]
public ConnectorType ConnectorType
{
get
{
return m_ConnectorType;
}
set
{
m_ConnectorType = value;
}
}
///
/// Gets/sets whether partially disconnected connectors can be created
/// ///
/// By default set to true
/// [Category("Behavior")]
[Description("Gets/sets whether partially disconnected connectors can be created")]
[DefaultValue(true)]
public bool AllowCreateDisconnected
{
get
{
return m_bAllowCreateDisconnected;
}
set
{
m_bAllowCreateDisconnected = value;
}
}
///
/// Gets/sets whether reflexive connectors can be created
/// ///
/// By default set to true
/// [Category("Behavior")]
[Description("Gets/sets whether reflexive connectors can be created")]
[DefaultValue(true)]
public bool AllowCreateReflexive
{
get
{
return m_bAllowCreateReflexive;
}
set
{
m_bAllowCreateReflexive = value;
}
}
///
/// Specifies whether to attempt shape snapping if by default the start plug was not snapped to port
/// [Category("Behavior")]
[Description("Specifies whether to attempt shape snapping if by default the start plug was not snapped to port")]
[DefaultValue(false)]
public bool SnapStartPlugToShape
{
get
{
return m_bSnapStartPlugToShape;
}
set
{
m_bSnapStartPlugToShape = value;
}
}
///
/// Specifies whether to attempt shape snapping if by default the end plug was not snapped to port
/// [Category("Behavior")]
[Description("Specifies whether to attempt shape snapping if by default the end plug was not snapped to port")]
[DefaultValue(false)]
public bool SnapEndPlugToShape
{
get
{
return m_bSnapEndPlugToShape;
}
set
{
m_bSnapEndPlugToShape = value;
}
}
#endregion
#region Overrides
///
/// Activates the tool
/// ///
/// Overriden to store the FromPort and FromShape parameters as well as to create a connector preview
/// public override void Activate()
{
base.Activate();
// init start and end snap results
m_StartSnapRes = SnapStartPlug(m_StartMouseInfo);
m_EndSnapRes = new NSnapPointResult(m_StartSnapRes.SnappedPoint);
// init the has left from shape
m_bHasLeftFromShape = false;
// create the preview and add it to the preview layer
m_Preview = CreateElement(true);
m_View.m_PreviewLayer.AddChild(m_Preview);
(m_Preview as INVisible).Visible = false;
}
///
/// Deactivates the tool
/// ///
/// Overriden to create a new connector
/// public override void Deactivate()
{
// remove port highlights
m_View.HighlightedPoints = null;
// destroy preview
m_View.m_PreviewLayer.RemoveChild(m_Preview);
m_Preview = null;
// do nothing if connector must not be created
if (CanCreateConnector() == false)
{
base.Deactivate();
return;
}
// get the target layer
NLayer layer = TargetLayer;
Debug.Assert(layer != null, "Must not activate if there is no target layer");
if (layer == null)
{
base.Deactivate();
return;
}
m_Document.StartTransaction(NDR.TransactionCreateConnector);
try
{
// create connector
m_Element = CreateElement(false);
// redefine connector
NShape connector = (m_Element as NShape);
RedefineConnector(connector, m_StartSnapRes.SnappedPoint, m_EndSnapRes.SnappedPoint);
// append to layer
NBatchInsert batch = new NBatchInsert(m_Document, m_Element);
NTransactionResult res = batch.Append(layer, false);
if (res.Succeeded)
{
// connect
NPort fromPort = m_StartSnapRes.GetValidInwardPort(null);
if (fromPort != null)
{
connector.StartPlug.Connect(fromPort);
}
NPort toPort = m_EndSnapRes.GetValidInwardPort(null);
if (toPort != null)
{
connector.EndPlug.Connect(toPort);
}
// reflex or reroute
INReflexiveShape reflexive = (connector as INReflexiveShape);
if (reflexive != null && reflexive.CanReflex())
{
reflexive.Reflex();
}
else
{
INRoutableShape routable = (connector as INRoutableShape);
if (routable != null && routable.CanReroute())
{
routable.Reroute();
}
}
// select
if (m_bSelectNewElement)
{
m_View.m_Selection.SingleSelect(m_Element);
}
}
}
catch (Exception ex)
{
Trace.WriteLine("Failed to create connector. Exception was: " + ex.Message);
m_Document.Rollback();
base.Deactivate();
return;
}
m_Document.Commit();
base.Deactivate();
}
///
/// Aborts the tool if it is active
/// ///
/// Overriden to destroy the preview
/// public override void Abort()
{
// remove port highlights
m_View.HighlightedPoints = null;
// destroy preview
m_View.m_PreviewLayer.RemoveChild(m_Preview);
m_Preview = null;
base.Abort();
}
#endregion
#region Protected overrides
///
/// Overriden to create a connector from the specified type and with the specified style
/// ///
whether the node is for preview
///
new connectorprotected override INDiagramElement CreateElement(bool preview)
{
NDiagramElementFactory factory = m_View.ElementFactory;
switch (m_ConnectorType)
{
case ConnectorType.Line:
return factory.CreateLineConnector(preview);
case ConnectorType.Bezier:
return factory.CreateBezierConnector(preview);
case ConnectorType.SingleArrow:
return factory.CreateArrowConnector(preview, ArrowType.SingleArrow);
case ConnectorType.DoubleArrow:
return factory.CreateArrowConnector(preview, ArrowType.DoubleArrow);
case ConnectorType.SideToTopBottom:
return factory.CreateStep2Connector(preview, false);
case ConnectorType.TopBottomToSide:
return factory.CreateStep2Connector(preview, true);
case ConnectorType.SideToSide:
return factory.CreateStep3Connector(preview, false);
case ConnectorType.TopToBottom:
return factory.CreateStep3Connector(preview, true);
case ConnectorType.DynamicHV:
return factory.CreateRoutableConnector(preview, RoutableConnectorType.DynamicHV);
case ConnectorType.DynamicPolyline:
return factory.CreateRoutableConnector(preview, RoutableConnectorType.DynamicPolyline);
case ConnectorType.DynamicCurve:
return factory.CreateRoutableConnector(preview, RoutableConnectorType.DynamicCurve);
default:
Debug.Assert(false, "New connector type?");
break;
}
return null;
}
#endregion
#region Protected overridables
///
/// Redefines the specified connector geometry to connect the specified start and end points
/// ///
///
///
protected virtual void RedefineConnector(NShape connector, NPointF start, NPointF end)
{
switch (m_ConnectorType)
{
case ConnectorType.Bezier:
{
NBezierCurveShape bezier = (connector as NBezierCurveShape);
float dx = end.X - start.X;
float dy = end.Y - start.Y;
bezier.Points = new NPointF[]{
start,
new NPointF(start.X + (dx / 2), start.Y),
new NPointF(end.X - (dx / 2), end.Y),
end};
}
break;
case ConnectorType.SingleArrow:
case ConnectorType.DoubleArrow:
{
NArrowShape arrow = (connector as NArrowShape);
float mm = m_Document.MeasurementUnitConverter.ConvertX(NGraphicsUnit.Millimeter, m_Document.WorldMeasurementUnit, 1);
mm = mm / m_Document.SceneScaleToWorldX;
(arrow.Primitive as NArrowPath).DefineModel(arrow.ArrowType, start, end, 5 * mm, 45, 15 * mm);
}
break;
case ConnectorType.Line:
case ConnectorType.SideToTopBottom:
case ConnectorType.TopBottomToSide:
case ConnectorType.SideToSide:
case ConnectorType.TopToBottom:
case ConnectorType.DynamicHV:
case ConnectorType.DynamicPolyline:
case ConnectorType.DynamicCurve:
{
connector.StartPoint = start;
connector.EndPoint = end;
}
break;
default:
Debug.Assert(false, "New connector type?");
break;
}
}
///
/// Highlights the specified ports
/// ///
port 1
///
port 2
protected virtual void HighlightPorts(NPort port1, NPort port2)
{
ArrayList points = new ArrayList();
if (port1 != null)
points.Add(port1.Location);
if (port2 != null)
points.Add(port2.Location);
m_View.HighlightedPoints = points;
}
///
/// Determines whether a connector can currently be created
/// ///
protected virtual bool CanCreateConnector()
{
NPort fromPort = m_StartSnapRes.GetValidInwardPort(null);
NPort toPort = m_EndSnapRes.GetValidInwardPort(null);
// cannot create a connector with zero length unless at least
// one of its plugs is going to be connected
if (m_StartSnapRes.SnappedPoint == m_EndSnapRes.SnappedPoint && fromPort == null && toPort == null)
return false;
NShape fromShape = GetFromShape();
NShape toShape = GetToShape();
// self loop checks
if (fromShape != null && fromShape == toShape)
{
// cannot create a self loop, if the mouse has not left the from shape
if (m_bHasLeftFromShape == false)
return false;
// cannot create a self loop, if explicitly specified
if (m_bAllowCreateReflexive == false)
return false;
}
// cannot create if either port is not valid and disconnected connectors are not allowed
if (m_bAllowCreateDisconnected == false)
{
if (fromPort == null || toPort == null)
return false;
}
// return true if user allows both ports
return ValidateFromPort(fromPort) && ValidateToPort(toPort);
}
///
/// Gets the from shape
/// ///
protected virtual NShape GetFromShape()
{
NPort port = m_StartSnapRes.GetValidInwardPort(null);
if (port == null)
return null;
return port.Shape;
}
///
/// Gets the to shape
/// ///
protected virtual NShape GetToShape()
{
NPort port = m_EndSnapRes.GetValidInwardPort(null);
if (port == null)
return null;
return port.Shape;
}
///
/// Determines whether the mouse is in the from shape
/// ///
true if the mouse is in the from shape or the from shape does not exist, otherwise falseprotected virtual bool IsMouseInFromShape()
{
NShape shape = GetFromShape();
if (shape == null)
return true;
return (shape.HitTest(m_View.GetMousePositionInDevice(), m_View.ProvideDocumentHitTestContext()) == false);
}
///
/// Called to snap the start plug.
/// The default implementation simply calls SnapShapePlug method of the snap manager.
/// Override this method to support custom start plug snapping.
/// ///
///
protected virtual NSnapPointResult SnapStartPlug(NMouseInfo mouseInfo)
{
NSnapPointResult res = m_View.SnapManager.SnapShapePlug(mouseInfo.ScenePoint, null);
if (m_bSnapStartPlugToShape == false)
return res;
// successfully snapped to port
if (res.GetValidInwardPort(null) != null)
return res;
// attemp shape snapping
NShape shape = View.LastDocumentHit(mouseInfo.DevicePoint, NFilters.TypeNShape) as NShape;
if (shape == null || shape.Ports == null || shape.Ports.DefaultInwardPort == null)
return res;
NPort port = shape.Ports.DefaultInwardPort;
res.SnappedPoint = port.Location;
res.XSnappedTo.Object = port;
res.XSnappedTo.Type = SnapTargetsMask.Ports;
res.YSnappedTo.Object = port;
res.YSnappedTo.Type = SnapTargetsMask.Ports;
return res;
}
///
/// Called to snap the end plug.
/// The default implementation simply calls SnapShapePlug method of the snap manager.
/// Override this method to support custom end plug snapping.
/// ///
///
protected virtual NSnapPointResult SnapEndPlug(NMouseInfo mouseInfo)
{
NSnapPointResult res = m_View.SnapManager.SnapShapePlug(mouseInfo.ScenePoint, null);
if (m_bSnapEndPlugToShape == false)
return res;
// successfully snapped to port
if (res.GetValidInwardPort(null) != null)
return res;
// attemp shape snapping
NShape shape = View.LastDocumentHit(mouseInfo.DevicePoint, NFilters.TypeNShape) as NShape;
if (shape == null || shape.Ports == null || shape.Ports.DefaultInwardPort == null)
return res;
NPort port = shape.Ports.DefaultInwardPort;
res.SnappedPoint = port.Location;
res.XSnappedTo.Object = port;
res.XSnappedTo.Type = SnapTargetsMask.Ports;
res.YSnappedTo.Object = port;
res.YSnappedTo.Type = SnapTargetsMask.Ports;
return res;
}
///
/// Validates the port to which the connector start plug is going to be connected
/// ///
///
protected virtual bool ValidateFromPort(NPort port)
{
return true;
}
///
/// Validates the port to which the connector end plug is going to be connected
/// ///
///
protected virtual bool ValidateToPort(NPort port)
{
return true;
}
#endregion
#region Protected properties
///
/// Determines whether the mouse has left the from shape (if there was a from shape)
/// protected bool HasLeftFromShape
{
get
{
return m_bHasLeftFromShape;
}
}
///
/// Gets/sets the current snap result for the start connector plug
/// protected NSnapPointResult StartSnapRes
{
get
{
return m_StartSnapRes;
}
set
{
m_StartSnapRes = value;
}
}
///
/// Gets/sets the current snap result for the end connector plug
/// protected NSnapPointResult EndSnapRes
{
get
{
return m_EndSnapRes;
}
set
{
m_EndSnapRes = value;
}
}
#endregion
#region Fields
// properties
internal ConnectorType m_ConnectorType;
internal bool m_bAllowCreateDisconnected;
internal bool m_bAllowCreateReflexive;
internal bool m_bSnapStartPlugToShape;
internal bool m_bSnapEndPlugToShape;
// start and end snap
[NonSerialized] internal NSnapPointResult m_StartSnapRes;
[NonSerialized] internal NSnapPointResult m_EndSnapRes;
// has left start shape
[NonSerialized] internal bool m_bHasLeftFromShape;
#endregion
}
}