From 297a40d72e8073d13caa2d42ae60d5ee2c20944b Mon Sep 17 00:00:00 2001 From: sbosse Date: Mon, 21 Jul 2025 23:40:53 +0200 Subject: [PATCH] Mon 21 Jul 22:43:21 CEST 2025 --- .../src/js/handler/mxEdgeSegmentHandler.js | 401 ++++++++++++++++++ 1 file changed, 401 insertions(+) create mode 100644 js/ui/mxgraph/src/js/handler/mxEdgeSegmentHandler.js diff --git a/js/ui/mxgraph/src/js/handler/mxEdgeSegmentHandler.js b/js/ui/mxgraph/src/js/handler/mxEdgeSegmentHandler.js new file mode 100644 index 0000000..513344e --- /dev/null +++ b/js/ui/mxgraph/src/js/handler/mxEdgeSegmentHandler.js @@ -0,0 +1,401 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +function mxEdgeSegmentHandler(state) +{ + mxEdgeHandler.call(this, state); +}; + +/** + * Extends mxEdgeHandler. + */ +mxUtils.extend(mxEdgeSegmentHandler, mxElbowEdgeHandler); + +/** + * Function: getCurrentPoints + * + * Returns the current absolute points. + */ +mxEdgeSegmentHandler.prototype.getCurrentPoints = function() +{ + var pts = this.state.absolutePoints; + + if (pts != null) + { + // Special case for straight edges where we add a virtual middle handle for moving the edge + if (pts.length == 2 || (pts.length == 3 && (pts[0].x == pts[1].x && pts[1].x == pts[2].x || + pts[0].y == pts[1].y && pts[1].y == pts[2].y))) + { + var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2; + var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2; + + pts = [pts[0], new mxPoint(cx, cy), new mxPoint(cx, cy), pts[pts.length - 1]]; + } + } + + return pts; +}; + +/** + * Function: getPreviewPoints + * + * Updates the given preview state taking into account the state of the constraint handler. + */ +mxEdgeSegmentHandler.prototype.getPreviewPoints = function(point) +{ + if (this.isSource || this.isTarget) + { + return mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this, arguments); + } + else + { + var pts = this.getCurrentPoints(); + var last = this.convertPoint(pts[0].clone(), false); + point = this.convertPoint(point.clone(), false); + var result = []; + + for (var i = 1; i < pts.length; i++) + { + var pt = this.convertPoint(pts[i].clone(), false); + + if (i == this.index) + { + if (Math.round(last.x - pt.x) == 0) + { + last.x = point.x; + pt.x = point.x; + } + + if (Math.round(last.y - pt.y) == 0) + { + last.y = point.y; + pt.y = point.y; + } + } + + if (i < pts.length - 1) + { + result.push(pt); + } + + last = pt; + } + + // Replaces single point that intersects with source or target + if (result.length == 1) + { + var source = this.state.getVisibleTerminalState(true); + var target = this.state.getVisibleTerminalState(false); + var scale = this.state.view.getScale(); + var tr = this.state.view.getTranslate(); + + var x = result[0].x * scale + tr.x; + var y = result[0].y * scale + tr.y; + + if ((source != null && mxUtils.contains(source, x, y)) || + (target != null && mxUtils.contains(target, x, y))) + { + result = [point, point]; + } + } + + return result; + } +}; + +/** + * Function: updatePreviewState + * + * Overridden to perform optimization of the edge style result. + */ +mxEdgeSegmentHandler.prototype.updatePreviewState = function(edge, point, terminalState, me) +{ + mxEdgeHandler.prototype.updatePreviewState.apply(this, arguments); + + // Checks and corrects preview by running edge style again + if (!this.isSource && !this.isTarget) + { + point = this.convertPoint(point.clone(), false); + var pts = edge.absolutePoints; + var pt0 = pts[0]; + var pt1 = pts[1]; + + var result = []; + + for (var i = 2; i < pts.length; i++) + { + var pt2 = pts[i]; + + // Merges adjacent segments only if more than 2 to allow for straight edges + if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) && + (Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0)) + { + result.push(this.convertPoint(pt1.clone(), false)); + } + + pt0 = pt1; + pt1 = pt2; + } + + var source = this.state.getVisibleTerminalState(true); + var target = this.state.getVisibleTerminalState(false); + var rpts = this.state.absolutePoints; + + // A straight line is represented by 3 handles + if (result.length == 0 && (Math.round(pts[0].x - pts[pts.length - 1].x) == 0 || + Math.round(pts[0].y - pts[pts.length - 1].y) == 0)) + { + result = [point, point]; + } + // Handles special case of transitions from straight vertical to routed + else if (pts.length == 5 && result.length == 2 && source != null && target != null && + rpts != null && Math.round(rpts[0].x - rpts[rpts.length - 1].x) == 0) + { + var view = this.graph.getView(); + var scale = view.getScale(); + var tr = view.getTranslate(); + + var y0 = view.getRoutingCenterY(source) / scale - tr.y; + + // Use fixed connection point y-coordinate if one exists + var sc = this.graph.getConnectionConstraint(edge, source, true); + + if (sc != null) + { + var pt = this.graph.getConnectionPoint(source, sc); + + if (pt != null) + { + this.convertPoint(pt, false); + y0 = pt.y; + } + } + + var ye = view.getRoutingCenterY(target) / scale - tr.y; + + // Use fixed connection point y-coordinate if one exists + var tc = this.graph.getConnectionConstraint(edge, target, false); + + if (tc) + { + var pt = this.graph.getConnectionPoint(target, tc); + + if (pt != null) + { + this.convertPoint(pt, false); + ye = pt.y; + } + } + + result = [new mxPoint(point.x, y0), new mxPoint(point.x, ye)]; + } + + this.points = result; + + // LATER: Check if points and result are different + edge.view.updateFixedTerminalPoints(edge, source, target); + edge.view.updatePoints(edge, this.points, source, target); + edge.view.updateFloatingTerminalPoints(edge, source, target); + } +}; + +/** + * Overriden to merge edge segments. + */ +mxEdgeSegmentHandler.prototype.connect = function(edge, terminal, isSource, isClone, me) +{ + // Merges adjacent edge segments + var pts = this.abspoints; + var pt0 = pts[0]; + var pt1 = pts[1]; + var result = []; + + for (var i = 2; i < pts.length; i++) + { + var pt2 = pts[i]; + + // Merges adjacent segments only if more than 2 to allow for straight edges + if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) && + (Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0)) + { + result.push(this.convertPoint(pt1.clone(), false)); + } + + pt0 = pt1; + pt1 = pt2; + } + + var model = this.graph.getModel(); + + model.beginUpdate(); + try + { + var geo = model.getGeometry(edge); + + if (geo != null) + { + geo = geo.clone(); + geo.points = result; + + model.setGeometry(edge, geo); + } + + edge = mxEdgeHandler.prototype.connect.apply(this, arguments); + } + finally + { + model.endUpdate(); + } + + return edge; +}; + +/** + * Function: getTooltipForNode + * + * Returns no tooltips. + */ +mxEdgeSegmentHandler.prototype.getTooltipForNode = function(node) +{ + return null; +}; + +/** + * Function: createBends + * + * Adds custom bends for the center of each segment. + */ +mxEdgeSegmentHandler.prototype.start = function(x, y, index) +{ + mxEdgeHandler.prototype.start.apply(this, arguments); + + if (this.bends[index] != null && !this.isSource && !this.isTarget) + { + mxUtils.setOpacity(this.bends[index].node, 100); + } +}; + +/** + * Function: createBends + * + * Adds custom bends for the center of each segment. + */ +mxEdgeSegmentHandler.prototype.createBends = function() +{ + var bends = []; + + // Source + var bend = this.createHandleShape(0); + this.initBend(bend); + bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); + bends.push(bend); + + var pts = this.getCurrentPoints(); + + // Waypoints (segment handles) + if (this.graph.isCellBendable(this.state.cell)) + { + if (this.points == null) + { + this.points = []; + } + + for (var i = 0; i < pts.length - 1; i++) + { + bend = this.createVirtualBend(); + bends.push(bend); + var horizontal = Math.round(pts[i].x - pts[i + 1].x) == 0; + + // Special case where dy is 0 as well + if (Math.round(pts[i].y - pts[i + 1].y) == 0 && i < pts.length - 2) + { + horizontal = Math.round(pts[i].x - pts[i + 2].x) == 0; + } + + bend.setCursor((horizontal) ? 'col-resize' : 'row-resize'); + this.points.push(new mxPoint(0,0)); + } + } + + // Target + var bend = this.createHandleShape(pts.length); + this.initBend(bend); + bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); + bends.push(bend); + + return bends; +}; + +/** + * Function: redraw + * + * Overridden to invoke before the redraw. + */ +mxEdgeSegmentHandler.prototype.redraw = function() +{ + this.refresh(); + mxEdgeHandler.prototype.redraw.apply(this, arguments); +}; + +/** + * Function: redrawInnerBends + * + * Updates the position of the custom bends. + */ +mxEdgeSegmentHandler.prototype.redrawInnerBends = function(p0, pe) +{ + if (this.graph.isCellBendable(this.state.cell)) + { + var pts = this.getCurrentPoints(); + + if (pts != null && pts.length > 1) + { + var straight = false; + + // Puts handle in the center of straight edges + if (pts.length == 4 && Math.round(pts[1].x - pts[2].x) == 0 && Math.round(pts[1].y - pts[2].y) == 0) + { + straight = true; + + if (Math.round(pts[0].y - pts[pts.length - 1].y) == 0) + { + var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2; + pts[1] = new mxPoint(cx, pts[1].y); + pts[2] = new mxPoint(cx, pts[2].y); + } + else + { + var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2; + pts[1] = new mxPoint(pts[1].x, cy); + pts[2] = new mxPoint(pts[2].x, cy); + } + } + + for (var i = 0; i < pts.length - 1; i++) + { + if (this.bends[i + 1] != null) + { + var p0 = pts[i]; + var pe = pts[i + 1]; + var pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2); + var b = this.bends[i + 1].bounds; + this.bends[i + 1].bounds = new mxRectangle(Math.floor(pt.x - b.width / 2), + Math.floor(pt.y - b.height / 2), b.width, b.height); + this.bends[i + 1].redraw(); + + if (this.manageLabelHandle) + { + this.checkLabelHandle(this.bends[i + 1].bounds); + } + } + } + + if (straight) + { + mxUtils.setOpacity(this.bends[1].node, this.virtualBendOpacity); + mxUtils.setOpacity(this.bends[3].node, this.virtualBendOpacity); + } + } + } +};