Starting a link at the nearest port to the mouse

Hi All

I have fairly small ports defined on a GoNode, and my users are complaining it is difficult to start a link because they have to click directly on the port.

Now I know I could probably define a larger bounded port, mostly invisible, with just a small visible appearance, but I was wondering if there is an easy way to modify the LinkingTool to search for the nearest port when the mouse is pressed? The method PickNearestPort is not suitable as far as I can tell because it uses information based on a link that is already connected at one end.

Thanks!

You’d have to change the CanStart() method in the LinkingTool. But there wouldn’t be any cursor feedback that the mouse was over a port, so the users might get confused. The best option (I think) is to make the ports bigger.

The Flowcharter sample makes ports visible on a mouse-over of the node for a short time. You could modify this logic to make the ports bigger on the mouse-over.
(edited after my first cup of coffee)

This code, taken from MovableLinkApp and extended, allows Alt-mouse-down over white space to snap a “Start Link” from the nearest port.

New code highlighted in blue.
[Serializable]
public class LinkingNewTool : GoToolLinkingNew {
public LinkingNewTool(GoView v) : base(v) {}
public override IGoPort PickNearestPort(PointF dc) {
IGoPort iport = base.PickNearestPort(dc);
if (this.EndPort != null && this.EndPort.GoObject is GoPort) {
if (iport != null) {
myLastNearestPort = (GoPort)this.EndPort.GoObject;
myLastNearestPort.Style = GoPortStyle.Rectangle;
myLastNearestPort.Pen = LinkingNewTool.HighlightPen;
myLastNearestPort.Brush = LinkingNewTool.HighlightBrush;
} else if (myLastNearestPort != null) {
myLastNearestPort.Style = GoPortStyle.None;
myLastNearestPort = null;
}
}
return iport;
}
public override IGoPort PickPort(PointF dc) {
IGoPort port = base.PickPort(dc);
if (port != null) return port;
// find a close port to start the link if Alt key is down
if (!this.FirstInput.Alt) return null;
return PickNearbyStartPort(dc);
}
private IGoPort PickNearbyStartPort(PointF dc) {
IGoPort bestPort = null;
float dist = this.View.PortGravity;
// get the region for all ports that we might care about
RectangleF scope = new RectangleF(dc.X - dist, dc.Y - dist, dist * 2, dist * 2);
float bestDist = dist * dist; // square here so don't need to sqrt later
foreach (GoLayer layer in this.View.Layers.Backwards) {
if (!layer.IsInDocument) continue;
if (!layer.CanViewObjects()) continue;
foreach (GoObject obj in layer.Backwards) {
bestPort = pickNearbyPort1(obj, dc, bestPort, ref bestDist);
}
}
return bestPort;
}
private IGoPort pickNearbyPort1(GoObject obj, PointF dc, IGoPort bestPort, ref float bestDist) {
// remember that it's possible some object will implement both IGoPort and GoGroup
IGoPort port = obj as IGoPort;
if (port != null && port.CanLinkFrom()) {
PointF toPoint = PortPoint(port, dc);
float dx = dc.X - toPoint.X;
float dy = dc.Y - toPoint.Y;
float dist = dx * dx + dy * dy; // don't bother taking sqrt
if (dist < bestDist) { // closest so far
bestPort = port;
bestDist = dist;
}
}
GoGroup group = obj as GoGroup;
if (group != null) {
foreach (GoObject child in group.GetEnumerator()) {
bestPort = pickNearbyPort1(child, dc, bestPort, ref bestDist);
}
}
return bestPort;
}
public override void Stop() {
base.Stop();
if (myLastNearestPort != null) {
myLastNearestPort.Style = GoPortStyle.None;
myLastNearestPort = null;
}
}
public static readonly Pen HighlightPen = new Pen(Color.Red, 2);
public static readonly Brush HighlightBrush = null;
private GoPort myLastNearestPort = null;
}