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 |
|
13 | import * 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 | */
|
28 | export 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 | }
|