From 9f4c786a4012f7ec4aa6c58b38c72c68739d7069 Mon Sep 17 00:00:00 2001 From: sbosse Date: Mon, 21 Jul 2025 23:40:49 +0200 Subject: [PATCH] Mon 21 Jul 22:43:21 CEST 2025 --- js/ui/mxgraph/src/js/handler/mxHandle.js | 352 +++++++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 js/ui/mxgraph/src/js/handler/mxHandle.js diff --git a/js/ui/mxgraph/src/js/handler/mxHandle.js b/js/ui/mxgraph/src/js/handler/mxHandle.js new file mode 100644 index 0000000..cfaefd9 --- /dev/null +++ b/js/ui/mxgraph/src/js/handler/mxHandle.js @@ -0,0 +1,352 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxHandle + * + * Implements a single custom handle for vertices. + * + * Constructor: mxHandle + * + * Constructs a new handle for the given state. + * + * Parameters: + * + * state - of the cell to be handled. + */ +function mxHandle(state, cursor, image) +{ + this.graph = state.view.graph; + this.state = state; + this.cursor = (cursor != null) ? cursor : this.cursor; + this.image = (image != null) ? image : this.image; + this.init(); +}; + +/** + * Variable: cursor + * + * Specifies the cursor to be used for this handle. Default is 'default'. + */ +mxHandle.prototype.cursor = 'default'; + +/** + * Variable: image + * + * Specifies the to be used to render the handle. Default is null. + */ +mxHandle.prototype.image = null; + +/** + * Variable: image + * + * Specifies the to be used to render the handle. Default is null. + */ +mxHandle.prototype.ignoreGrid = false; + +/** + * Function: getPosition + * + * Hook for subclassers to return the current position of the handle. + */ +mxHandle.prototype.getPosition = function(bounds) { }; + +/** + * Function: setPosition + * + * Hooks for subclassers to update the style in the . + */ +mxHandle.prototype.setPosition = function(bounds, pt, me) { }; + +/** + * Function: execute + * + * Hook for subclassers to execute the handle. + */ +mxHandle.prototype.execute = function() { }; + +/** + * Function: copyStyle + * + * Sets the cell style with the given name to the corresponding value in . + */ +mxHandle.prototype.copyStyle = function(key) +{ + this.graph.setCellStyles(key, this.state.style[key], [this.state.cell]); +}; + +/** + * Function: processEvent + * + * Processes the given and invokes . + */ +mxHandle.prototype.processEvent = function(me) +{ + var scale = this.graph.view.scale; + var tr = this.graph.view.translate; + var pt = new mxPoint(me.getGraphX() / scale - tr.x, me.getGraphY() / scale - tr.y); + + // Center shape on mouse cursor + if (this.shape != null && this.shape.bounds != null) + { + pt.x -= this.shape.bounds.width / scale / 4; + pt.y -= this.shape.bounds.height / scale / 4; + } + + // Snaps to grid for the rotated position then applies the rotation for the direction after that + var alpha1 = -mxUtils.toRadians(this.getRotation()); + var alpha2 = -mxUtils.toRadians(this.getTotalRotation()) - alpha1; + pt = this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(pt, alpha1), + this.ignoreGrid || !this.graph.isGridEnabledEvent(me.getEvent())), alpha2)); + this.setPosition(this.state.getPaintBounds(), pt, me); + this.positionChanged(); + this.redraw(); +}; + +/** + * Function: positionChanged + * + * Called after has been called in . This repaints + * the state using . + */ +mxHandle.prototype.positionChanged = function() +{ + if (this.state.text != null) + { + this.state.text.apply(this.state); + } + + if (this.state.shape != null) + { + this.state.shape.apply(this.state); + } + + this.graph.cellRenderer.redraw(this.state, true); +}; + +/** + * Function: getRotation + * + * Returns the rotation defined in the style of the cell. + */ +mxHandle.prototype.getRotation = function() +{ + if (this.state.shape != null) + { + return this.state.shape.getRotation(); + } + + return 0; +}; + +/** + * Function: getTotalRotation + * + * Returns the rotation from the style and the rotation from the direction of + * the cell. + */ +mxHandle.prototype.getTotalRotation = function() +{ + if (this.state.shape != null) + { + return this.state.shape.getShapeRotation(); + } + + return 0; +}; + +/** + * Function: init + * + * Creates and initializes the shapes required for this handle. + */ +mxHandle.prototype.init = function() +{ + var html = this.isHtmlRequired(); + + if (this.image != null) + { + this.shape = new mxImageShape(new mxRectangle(0, 0, this.image.width, this.image.height), this.image.src); + this.shape.preserveImageAspect = false; + } + else + { + this.shape = this.createShape(html); + } + + this.initShape(html); +}; + +/** + * Function: createShape + * + * Creates and returns the shape for this handle. + */ +mxHandle.prototype.createShape = function(html) +{ + var bounds = new mxRectangle(0, 0, mxConstants.HANDLE_SIZE, mxConstants.HANDLE_SIZE); + + return new mxRectangleShape(bounds, mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); +}; + +/** + * Function: initShape + * + * Initializes and sets its cursor. + */ +mxHandle.prototype.initShape = function(html) +{ + if (html && this.shape.isHtmlAllowed()) + { + this.shape.dialect = mxConstants.DIALECT_STRICTHTML; + this.shape.init(this.graph.container); + } + else + { + this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; + + if (this.cursor != null) + { + this.shape.init(this.graph.getView().getOverlayPane()); + } + } + + mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state); + this.shape.node.style.cursor = this.cursor; +}; + +/** + * Function: redraw + * + * Renders the shape for this handle. + */ +mxHandle.prototype.redraw = function() +{ + if (this.shape != null && this.state.shape != null) + { + var pt = this.getPosition(this.state.getPaintBounds()); + + if (pt != null) + { + var alpha = mxUtils.toRadians(this.getTotalRotation()); + pt = this.rotatePoint(this.flipPoint(pt), alpha); + + var scale = this.graph.view.scale; + var tr = this.graph.view.translate; + this.shape.bounds.x = Math.floor((pt.x + tr.x) * scale - this.shape.bounds.width / 2); + this.shape.bounds.y = Math.floor((pt.y + tr.y) * scale - this.shape.bounds.height / 2); + + // Needed to force update of text bounds + this.state.unscaledWidth = null; + this.shape.redraw(); + } + } +}; + +/** + * Function: isHtmlRequired + * + * Returns true if this handle should be rendered in HTML. This returns true if + * the text node is in the graph container. + */ +mxHandle.prototype.isHtmlRequired = function() +{ + return this.state.text != null && this.state.text.node.parentNode == this.graph.container; +}; + +/** + * Function: rotatePoint + * + * Rotates the point by the given angle. + */ +mxHandle.prototype.rotatePoint = function(pt, alpha) +{ + var bounds = this.state.getCellBounds(); + var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY()); + var cos = Math.cos(alpha); + var sin = Math.sin(alpha); + + return mxUtils.getRotatedPoint(pt, cos, sin, cx); +}; + +/** + * Function: flipPoint + * + * Flips the given point vertically and/or horizontally. + */ +mxHandle.prototype.flipPoint = function(pt) +{ + if (this.state.shape != null) + { + var bounds = this.state.getCellBounds(); + + if (this.state.shape.flipH) + { + pt.x = 2 * bounds.x + bounds.width - pt.x; + } + + if (this.state.shape.flipV) + { + pt.y = 2 * bounds.y + bounds.height - pt.y; + } + } + + return pt; +}; + +/** + * Function: snapPoint + * + * Snaps the given point to the grid if ignore is false. This modifies + * the given point in-place and also returns it. + */ +mxHandle.prototype.snapPoint = function(pt, ignore) +{ + if (!ignore) + { + pt.x = this.graph.snap(pt.x); + pt.y = this.graph.snap(pt.y); + } + + return pt; +}; + +/** + * Function: setVisible + * + * Shows or hides this handle. + */ +mxHandle.prototype.setVisible = function(visible) +{ + if (this.shape != null && this.shape.node != null) + { + this.shape.node.style.display = (visible) ? '' : 'none'; + } +}; + +/** + * Function: reset + * + * Resets the state of this handle by setting its visibility to true. + */ +mxHandle.prototype.reset = function() +{ + this.setVisible(true); + this.state.style = this.graph.getCellStyle(this.state.cell); + this.positionChanged(); +}; + +/** + * Function: destroy + * + * Destroys this handle. + */ +mxHandle.prototype.destroy = function() +{ + if (this.shape != null) + { + this.shape.destroy(); + this.shape = null; + } +};