UNPKG

9.42 kBPlain TextView Raw
1/*
2* Copyright (C) 1998-2021 by Northwoods Software Corporation. All Rights Reserved.
3*/
4
5/*
6* This is an extension and not part of the main GoJS library.
7* Note that the API for this class may change with any version, even point releases.
8* If you intend to use an extension in production, you should copy the code to your own source directory.
9* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
10* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
11*/
12
13import * as go from '../release/go-module.js';
14
15/**
16 * The PolylineLinkingTool class the user to draw a new {@link Link} by clicking where the route should go,
17 * until clicking on a valid target port.
18 *
19 * This tool supports routing both orthogonal and straight links.
20 * You can customize the {@link LinkingBaseTool#temporaryLink} as needed to affect the
21 * appearance and behavior of the temporary link that is shown during the linking operation.
22 * You can customize the {@link LinkingTool#archetypeLinkData} to specify property values
23 * that can be data-bound by your link template for the Links that are actually created.
24 *
25 * If you want to experiment with this extension, try the <a href="../../extensionsJSM/PolylineLinking.html">Polyline Linking</a> sample.
26 * @category Tool Extension
27 */
28export class PolylineLinkingTool extends go.LinkingTool {
29 private _firstMouseDown: boolean = false;
30 private _horizontal: boolean = false;
31
32 /**
33 * Constructs an PolylineLinkingTool, sets {@link #portGravity} to 0, and sets the name for the tool.
34 */
35 constructor() {
36 super();
37 this.portGravity = 0; // must click on a target port in order to complete the link
38 this.name = 'PolylineLinking';
39 }
40
41 /**
42 * @hidden @internal
43 * This internal method adds a point to the route.
44 * During the operation of this tool, the very last point changes to follow the mouse point.
45 * This method is called by {@link #doMouseDown} in order to add a new "last" point.
46 * @param {Point} p
47 */
48 private addPoint(p: go.Point): void {
49 if (this._firstMouseDown) return;
50 const pts = this.temporaryLink.points.copy();
51 this._horizontal = !this._horizontal;
52 pts.add(p.copy());
53 this.temporaryLink.points = pts;
54 }
55
56 /**
57 * @hidden @internal
58 * This internal method moves the last point of the temporary Link's route.
59 * This is called by {@link #doMouseMove} and other methods that want to adjust the end of the route.
60 * @param {Point} p
61 */
62 private moveLastPoint(p: go.Point): void {
63 if (this._firstMouseDown) return;
64 const pts = this.temporaryLink.points.copy();
65 if (this.temporaryLink.isOrthogonal) {
66 const q = pts.elt(pts.length - 3).copy();
67 if (this._horizontal) {
68 q.y = p.y;
69 } else {
70 q.x = p.x;
71 }
72 pts.setElt(pts.length - 2, q);
73 }
74 pts.setElt(pts.length - 1, p.copy());
75 this.temporaryLink.points = pts;
76 }
77
78 /**
79 * @hidden @internal
80 * This internal method removes the last point of the temporary Link's route.
81 * This is called by the "Z" command in {@link #doKeyDown}
82 * and by {@link #doMouseUp} when a valid target port is found and we want to
83 * discard the current mouse point from the route.
84 */
85 private removeLastPoint(): void {
86 if (this._firstMouseDown) return;
87 const pts = this.temporaryLink.points.copy();
88 if (pts.length === 0) return;
89 pts.removeAt(pts.length - 1);
90 this.temporaryLink.points = pts;
91 this._horizontal = !this._horizontal;
92 }
93
94 /**
95 * Use a "crosshair" cursor.
96 */
97 public doActivate(): void {
98 super.doActivate();
99 this.diagram.currentCursor = 'crosshair';
100 // until a mouse down occurs, allow the temporary link to be routed to the temporary node/port
101 this._firstMouseDown = true;
102 }
103
104 /**
105 * Add a point to the route that the temporary Link is accumulating.
106 */
107 public doMouseDown(): void {
108 if (!this.isActive) {
109 this.doActivate();
110 }
111 if (this.diagram.lastInput.left) {
112 if (this._firstMouseDown) {
113 this._firstMouseDown = false;
114 // disconnect the temporary node/port from the temporary link
115 // so that it doesn't lose the points that are accumulating
116 if (this.isForwards) {
117 this.temporaryLink.toNode = null;
118 } else {
119 this.temporaryLink.fromNode = null;
120 }
121 const pts = this.temporaryLink.points;
122 const ult = pts.elt(pts.length - 1);
123 const penult = pts.elt(pts.length - 2);
124 this._horizontal = (ult.x === penult.x);
125 }
126 // a new temporary end point, the previous one is now "accepted"
127 this.addPoint(this.diagram.lastInput.documentPoint);
128 } else { // e.g. right mouse down
129 this.doCancel();
130 }
131 }
132
133 /**
134 * Have the temporary link reach to the last mouse point.
135 */
136 public doMouseMove(): void {
137 if (this.isActive) {
138 this.moveLastPoint(this.diagram.lastInput.documentPoint);
139 super.doMouseMove();
140 }
141 }
142
143 /**
144 * If this event happens on a valid target port (as determined by {@link LinkingBaseTool#findTargetPort}),
145 * we complete the link drawing operation. {@link #insertLink} is overridden to transfer the accumulated
146 * route drawn by user clicks to the new {@link Link} that was created.
147 *
148 * If this event happens elsewhere in the diagram, this tool is not stopped: the drawing of the route continues.
149 */
150 public doMouseUp(): void {
151 if (!this.isActive) return;
152 const target = this.findTargetPort(this.isForwards);
153 if (target !== null) {
154 if (this._firstMouseDown) {
155 super.doMouseUp();
156 } else {
157 let pts;
158 this.removeLastPoint(); // remove temporary point
159 const spot = this.isForwards ? target.toSpot : target.fromSpot;
160 if (spot.equals(go.Spot.None)) {
161 const pt = this.temporaryLink.getLinkPointFromPoint(target.part as go.Node, target,
162 target.getDocumentPoint(go.Spot.Center),
163 this.temporaryLink.points.elt(this.temporaryLink.points.length - 2),
164 !this.isForwards);
165 this.moveLastPoint(pt);
166 pts = this.temporaryLink.points.copy();
167 if (this.temporaryLink.isOrthogonal) {
168 pts.insertAt(pts.length - 2, pts.elt(pts.length - 2));
169 }
170 } else {
171 // copy the route of saved points, because we're about to recompute it
172 pts = this.temporaryLink.points.copy();
173 // terminate the link in the expected manner by letting the
174 // temporary link connect with the temporary node/port and letting the
175 // normal route computation take place
176 if (this.isForwards) {
177 this.copyPortProperties(target.part as go.Node, target, this.temporaryToNode, this.temporaryToPort, true);
178 this.temporaryLink.toNode = target.part as go.Node;
179 } else {
180 this.copyPortProperties(target.part as go.Node, target, this.temporaryFromNode, this.temporaryFromPort, false);
181 this.temporaryLink.fromNode = target.part as go.Node;
182 }
183 this.temporaryLink.updateRoute();
184 // now copy the final one or two points of the temporary link's route
185 // into the route built up in the PTS List.
186 const natpts = this.temporaryLink.points;
187 const numnatpts = natpts.length;
188 if (numnatpts >= 2) {
189 if (numnatpts >= 3) {
190 const penult = natpts.elt(numnatpts - 2);
191 pts.insertAt(pts.length - 1, penult);
192 if (this.temporaryLink.isOrthogonal) {
193 pts.insertAt(pts.length - 1, penult);
194 }
195 }
196 const ult = natpts.elt(numnatpts - 1);
197 pts.setElt(pts.length - 1, ult);
198 }
199 }
200 // save desired route in temporary link;
201 // insertLink will copy the route into the new real Link
202 this.temporaryLink.points = pts;
203 super.doMouseUp();
204 }
205 }
206 }
207
208 /**
209 * This method overrides the standard link creation method by additionally
210 * replacing the default link route with the custom one laid out by the user.
211 */
212 public insertLink(fromnode: go.Node, fromport: go.GraphObject, tonode: go.Node, toport: go.GraphObject): go.Link | null {
213 const link = super.insertLink(fromnode, fromport, tonode, toport);
214 if (link !== null && !this._firstMouseDown) {
215 // ignore natural route by replacing with route accumulated by this tool
216 link.points = this.temporaryLink.points;
217 }
218 return link;
219 }
220
221 /**
222 * This supports the "Z" command during this tool's operation to remove the last added point of the route.
223 * Type ESCAPE to completely cancel the operation of the tool.
224 */
225 public doKeyDown(): void {
226 if (!this.isActive) return;
227 const e = this.diagram.lastInput;
228 if (e.key === 'Z' && this.temporaryLink.points.length > (this.temporaryLink.isOrthogonal ? 4 : 3)) { // undo
229 // remove a point, and then treat the last one as a temporary one
230 this.removeLastPoint();
231 this.moveLastPoint(e.documentPoint);
232 } else {
233 super.doKeyDown();
234 }
235 }
236}