UNPKG

10.3 kBJavaScriptView Raw
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*/
11import * 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 */
25export 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}