/** * 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, shape) { this.graph = state.view.graph; this.state = state; this.cursor = (cursor != null) ? cursor : this.cursor; this.image = (image != null) ? image : this.image; this.shape = (shape != null) ? shape : null; 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: ignoreGrid * * Default is false. */ 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(me) { }; /** * 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.redraw(); }; /** * Function: positionChanged * * Should be called after 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 if (this.shape == null) { 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.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; } };