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