| /** |
| * Copyright (c) 2006-2015, JGraph Ltd |
| * Copyright (c) 2006-2015, Gaudenz Alder |
| */ |
| /** |
| * Class: mxVertexHandler |
| * |
| * Event handler for resizing cells. This handler is automatically created in |
| * <mxGraph.createHandler>. |
| * |
| * Constructor: mxVertexHandler |
| * |
| * Constructs an event handler that allows to resize vertices |
| * and groups. |
| * |
| * Parameters: |
| * |
| * state - <mxCellState> of the cell to be resized. |
| */ |
| function mxVertexHandler(state) |
| { |
| if (state != null) |
| { |
| this.state = state; |
| this.init(); |
| |
| // Handles escape keystrokes |
| this.escapeHandler = mxUtils.bind(this, function(sender, evt) |
| { |
| if (this.livePreview && this.index != null) |
| { |
| // Redraws the live preview |
| this.state.view.graph.cellRenderer.redraw(this.state, true); |
| |
| // Redraws connected edges |
| this.state.view.invalidate(this.state.cell); |
| this.state.invalid = false; |
| this.state.view.validate(); |
| } |
| |
| this.reset(); |
| }); |
| |
| this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler); |
| } |
| }; |
| |
| /** |
| * Variable: graph |
| * |
| * Reference to the enclosing <mxGraph>. |
| */ |
| mxVertexHandler.prototype.graph = null; |
| |
| /** |
| * Variable: state |
| * |
| * Reference to the <mxCellState> being modified. |
| */ |
| mxVertexHandler.prototype.state = null; |
| |
| /** |
| * Variable: singleSizer |
| * |
| * Specifies if only one sizer handle at the bottom, right corner should be |
| * used. Default is false. |
| */ |
| mxVertexHandler.prototype.singleSizer = false; |
| |
| /** |
| * Variable: index |
| * |
| * Holds the index of the current handle. |
| */ |
| mxVertexHandler.prototype.index = null; |
| |
| /** |
| * Variable: allowHandleBoundsCheck |
| * |
| * Specifies if the bounds of handles should be used for hit-detection in IE or |
| * if <tolerance> > 0. Default is true. |
| */ |
| mxVertexHandler.prototype.allowHandleBoundsCheck = true; |
| |
| /** |
| * Variable: handleImage |
| * |
| * Optional <mxImage> to be used as handles. Default is null. |
| */ |
| mxVertexHandler.prototype.handleImage = null; |
| |
| /** |
| * Variable: tolerance |
| * |
| * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0. |
| */ |
| mxVertexHandler.prototype.tolerance = 0; |
| |
| /** |
| * Variable: rotationEnabled |
| * |
| * Specifies if a rotation handle should be visible. Default is false. |
| */ |
| mxVertexHandler.prototype.rotationEnabled = false; |
| |
| /** |
| * Variable: parentHighlightEnabled |
| * |
| * Specifies if the parent should be highlighted if a child cell is selected. |
| * Default is false. |
| */ |
| mxVertexHandler.prototype.parentHighlightEnabled = false; |
| |
| /** |
| * Variable: rotationRaster |
| * |
| * Specifies if rotation steps should be "rasterized" depening on the distance |
| * to the handle. Default is true. |
| */ |
| mxVertexHandler.prototype.rotationRaster = true; |
| |
| /** |
| * Variable: rotationCursor |
| * |
| * Specifies the cursor for the rotation handle. Default is 'crosshair'. |
| */ |
| mxVertexHandler.prototype.rotationCursor = 'crosshair'; |
| |
| /** |
| * Variable: livePreview |
| * |
| * Specifies if resize should change the cell in-place. This is an experimental |
| * feature for non-touch devices. Default is false. |
| */ |
| mxVertexHandler.prototype.livePreview = false; |
| |
| /** |
| * Variable: manageSizers |
| * |
| * Specifies if sizers should be hidden and spaced if the vertex is small. |
| * Default is false. |
| */ |
| mxVertexHandler.prototype.manageSizers = false; |
| |
| /** |
| * Variable: constrainGroupByChildren |
| * |
| * Specifies if the size of groups should be constrained by the children. |
| * Default is false. |
| */ |
| mxVertexHandler.prototype.constrainGroupByChildren = false; |
| |
| /** |
| * Variable: rotationHandleVSpacing |
| * |
| * Vertical spacing for rotation icon. Default is -16. |
| */ |
| mxVertexHandler.prototype.rotationHandleVSpacing = -16; |
| |
| /** |
| * Variable: horizontalOffset |
| * |
| * The horizontal offset for the handles. This is updated in <redrawHandles> |
| * if <manageSizers> is true and the sizers are offset horizontally. |
| */ |
| mxVertexHandler.prototype.horizontalOffset = 0; |
| |
| /** |
| * Variable: verticalOffset |
| * |
| * The horizontal offset for the handles. This is updated in <redrawHandles> |
| * if <manageSizers> is true and the sizers are offset vertically. |
| */ |
| mxVertexHandler.prototype.verticalOffset = 0; |
| |
| /** |
| * Function: init |
| * |
| * Initializes the shapes required for this vertex handler. |
| */ |
| mxVertexHandler.prototype.init = function() |
| { |
| this.graph = this.state.view.graph; |
| this.selectionBounds = this.getSelectionBounds(this.state); |
| this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height); |
| this.selectionBorder = this.createSelectionShape(this.bounds); |
| // VML dialect required here for event transparency in IE |
| this.selectionBorder.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; |
| this.selectionBorder.pointerEvents = false; |
| this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0'); |
| this.selectionBorder.init(this.graph.getView().getOverlayPane()); |
| mxEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state); |
| |
| if (this.graph.isCellMovable(this.state.cell)) |
| { |
| this.selectionBorder.setCursor(mxConstants.CURSOR_MOVABLE_VERTEX); |
| } |
| |
| // Adds the sizer handles |
| if (mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells) |
| { |
| var resizable = this.graph.isCellResizable(this.state.cell); |
| this.sizers = []; |
| |
| if (resizable || (this.graph.isLabelMovable(this.state.cell) && |
| this.state.width >= 2 && this.state.height >= 2)) |
| { |
| var i = 0; |
| |
| if (resizable) |
| { |
| if (!this.singleSizer) |
| { |
| this.sizers.push(this.createSizer('nw-resize', i++)); |
| this.sizers.push(this.createSizer('n-resize', i++)); |
| this.sizers.push(this.createSizer('ne-resize', i++)); |
| this.sizers.push(this.createSizer('w-resize', i++)); |
| this.sizers.push(this.createSizer('e-resize', i++)); |
| this.sizers.push(this.createSizer('sw-resize', i++)); |
| this.sizers.push(this.createSizer('s-resize', i++)); |
| } |
| |
| this.sizers.push(this.createSizer('se-resize', i++)); |
| } |
| |
| var geo = this.graph.model.getGeometry(this.state.cell); |
| |
| if (geo != null && !geo.relative && !this.graph.isSwimlane(this.state.cell) && |
| this.graph.isLabelMovable(this.state.cell)) |
| { |
| // Marks this as the label handle for getHandleForEvent |
| this.labelShape = this.createSizer(mxConstants.CURSOR_LABEL_HANDLE, mxEvent.LABEL_HANDLE, mxConstants.LABEL_HANDLE_SIZE, mxConstants.LABEL_HANDLE_FILLCOLOR); |
| this.sizers.push(this.labelShape); |
| } |
| } |
| else if (this.graph.isCellMovable(this.state.cell) && !this.graph.isCellResizable(this.state.cell) && |
| this.state.width < 2 && this.state.height < 2) |
| { |
| this.labelShape = this.createSizer(mxConstants.CURSOR_MOVABLE_VERTEX, |
| mxEvent.LABEL_HANDLE, null, mxConstants.LABEL_HANDLE_FILLCOLOR); |
| this.sizers.push(this.labelShape); |
| } |
| } |
| |
| // Adds the rotation handler |
| if (this.isRotationHandleVisible()) |
| { |
| this.rotationShape = this.createSizer(this.rotationCursor, mxEvent.ROTATION_HANDLE, |
| mxConstants.HANDLE_SIZE + 3, mxConstants.HANDLE_FILLCOLOR); |
| this.sizers.push(this.rotationShape); |
| } |
| |
| this.customHandles = this.createCustomHandles(); |
| this.redraw(); |
| |
| if (this.constrainGroupByChildren) |
| { |
| this.updateMinBounds(); |
| } |
| }; |
| |
| /** |
| * Function: isRotationHandleVisible |
| * |
| * Returns true if the rotation handle should be showing. |
| */ |
| mxVertexHandler.prototype.isRotationHandleVisible = function() |
| { |
| return this.graph.isEnabled() && this.rotationEnabled && this.graph.isCellRotatable(this.state.cell) && |
| (mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells) && |
| this.state.width >= 2 && this.state.height >= 2; |
| }; |
| |
| /** |
| * Function: isConstrainedEvent |
| * |
| * Returns true if the aspect ratio if the cell should be maintained. |
| */ |
| mxVertexHandler.prototype.isConstrainedEvent = function(me) |
| { |
| return mxEvent.isShiftDown(me.getEvent()) || this.state.style[mxConstants.STYLE_ASPECT] == 'fixed'; |
| }; |
| |
| /** |
| * Function: isCenteredEvent |
| * |
| * Returns true if the center of the vertex should be maintained during the resize. |
| */ |
| mxVertexHandler.prototype.isCenteredEvent = function(state, me) |
| { |
| return false; |
| }; |
| |
| /** |
| * Function: createCustomHandles |
| * |
| * Returns an array of custom handles. This implementation returns null. |
| */ |
| mxVertexHandler.prototype.createCustomHandles = function() |
| { |
| return null; |
| }; |
| |
| /** |
| * Function: updateMinBounds |
| * |
| * Initializes the shapes required for this vertex handler. |
| */ |
| mxVertexHandler.prototype.updateMinBounds = function() |
| { |
| var children = this.graph.getChildCells(this.state.cell); |
| |
| if (children.length > 0) |
| { |
| this.minBounds = this.graph.view.getBounds(children); |
| |
| if (this.minBounds != null) |
| { |
| var s = this.state.view.scale; |
| var t = this.state.view.translate; |
| |
| this.minBounds.x -= this.state.x; |
| this.minBounds.y -= this.state.y; |
| this.minBounds.x /= s; |
| this.minBounds.y /= s; |
| this.minBounds.width /= s; |
| this.minBounds.height /= s; |
| this.x0 = this.state.x / s - t.x; |
| this.y0 = this.state.y / s - t.y; |
| } |
| } |
| }; |
| |
| /** |
| * Function: getSelectionBounds |
| * |
| * Returns the mxRectangle that defines the bounds of the selection |
| * border. |
| */ |
| mxVertexHandler.prototype.getSelectionBounds = function(state) |
| { |
| return new mxRectangle(Math.round(state.x), Math.round(state.y), Math.round(state.width), Math.round(state.height)); |
| }; |
| |
| /** |
| * Function: createParentHighlightShape |
| * |
| * Creates the shape used to draw the selection border. |
| */ |
| mxVertexHandler.prototype.createParentHighlightShape = function(bounds) |
| { |
| return this.createSelectionShape(bounds); |
| }; |
| |
| /** |
| * Function: createSelectionShape |
| * |
| * Creates the shape used to draw the selection border. |
| */ |
| mxVertexHandler.prototype.createSelectionShape = function(bounds) |
| { |
| var shape = new mxRectangleShape(bounds, null, this.getSelectionColor()); |
| shape.strokewidth = this.getSelectionStrokeWidth(); |
| shape.isDashed = this.isSelectionDashed(); |
| |
| return shape; |
| }; |
| |
| /** |
| * Function: getSelectionColor |
| * |
| * Returns <mxConstants.VERTEX_SELECTION_COLOR>. |
| */ |
| mxVertexHandler.prototype.getSelectionColor = function() |
| { |
| return mxConstants.VERTEX_SELECTION_COLOR; |
| }; |
| |
| /** |
| * Function: getSelectionStrokeWidth |
| * |
| * Returns <mxConstants.VERTEX_SELECTION_STROKEWIDTH>. |
| */ |
| mxVertexHandler.prototype.getSelectionStrokeWidth = function() |
| { |
| return mxConstants.VERTEX_SELECTION_STROKEWIDTH; |
| }; |
| |
| /** |
| * Function: isSelectionDashed |
| * |
| * Returns <mxConstants.VERTEX_SELECTION_DASHED>. |
| */ |
| mxVertexHandler.prototype.isSelectionDashed = function() |
| { |
| return mxConstants.VERTEX_SELECTION_DASHED; |
| }; |
| |
| /** |
| * Function: createSizer |
| * |
| * Creates a sizer handle for the specified cursor and index and returns |
| * the new <mxRectangleShape> that represents the handle. |
| */ |
| mxVertexHandler.prototype.createSizer = function(cursor, index, size, fillColor) |
| { |
| size = size || mxConstants.HANDLE_SIZE; |
| |
| var bounds = new mxRectangle(0, 0, size, size); |
| var sizer = this.createSizerShape(bounds, index, fillColor); |
| |
| if (sizer.isHtmlAllowed() && this.state.text != null && this.state.text.node.parentNode == this.graph.container) |
| { |
| sizer.bounds.height -= 1; |
| sizer.bounds.width -= 1; |
| sizer.dialect = mxConstants.DIALECT_STRICTHTML; |
| sizer.init(this.graph.container); |
| } |
| else |
| { |
| sizer.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? |
| mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; |
| sizer.init(this.graph.getView().getOverlayPane()); |
| } |
| |
| mxEvent.redirectMouseEvents(sizer.node, this.graph, this.state); |
| |
| if (this.graph.isEnabled()) |
| { |
| sizer.setCursor(cursor); |
| } |
| |
| if (!this.isSizerVisible(index)) |
| { |
| sizer.visible = false; |
| } |
| |
| return sizer; |
| }; |
| |
| /** |
| * Function: isSizerVisible |
| * |
| * Returns true if the sizer for the given index is visible. |
| * This returns true for all given indices. |
| */ |
| mxVertexHandler.prototype.isSizerVisible = function(index) |
| { |
| return true; |
| }; |
| |
| /** |
| * Function: createSizerShape |
| * |
| * Creates the shape used for the sizer handle for the specified bounds an |
| * index. Only images and rectangles should be returned if support for HTML |
| * labels with not foreign objects is required. |
| */ |
| mxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor) |
| { |
| if (this.handleImage != null) |
| { |
| bounds = new mxRectangle(bounds.x, bounds.y, this.handleImage.width, this.handleImage.height); |
| var shape = new mxImageShape(bounds, this.handleImage.src); |
| |
| // Allows HTML rendering of the images |
| shape.preserveImageAspect = false; |
| |
| return shape; |
| } |
| else if (index == mxEvent.ROTATION_HANDLE) |
| { |
| return new mxEllipse(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); |
| } |
| else |
| { |
| return new mxRectangleShape(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); |
| } |
| }; |
| |
| /** |
| * Function: createBounds |
| * |
| * Helper method to create an <mxRectangle> around the given centerpoint |
| * with a width and height of 2*s or 6, if no s is given. |
| */ |
| mxVertexHandler.prototype.moveSizerTo = function(shape, x, y) |
| { |
| if (shape != null) |
| { |
| shape.bounds.x = Math.floor(x - shape.bounds.width / 2); |
| shape.bounds.y = Math.floor(y - shape.bounds.height / 2); |
| |
| // Fixes visible inactive handles in VML |
| if (shape.node != null && shape.node.style.display != 'none') |
| { |
| shape.redraw(); |
| } |
| } |
| }; |
| |
| /** |
| * Function: getHandleForEvent |
| * |
| * Returns the index of the handle for the given event. This returns the index |
| * of the sizer from where the event originated or <mxEvent.LABEL_INDEX>. |
| */ |
| mxVertexHandler.prototype.getHandleForEvent = function(me) |
| { |
| // Connection highlight may consume events before they reach sizer handle |
| var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1; |
| var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ? |
| new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null; |
| |
| function checkShape(shape) |
| { |
| return shape != null && (me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit) && |
| shape.node.style.display != 'none' && shape.node.style.visibility != 'hidden')); |
| } |
| |
| if (this.customHandles != null && this.isCustomHandleEvent(me)) |
| { |
| // Inverse loop order to match display order |
| for (var i = this.customHandles.length - 1; i >= 0; i--) |
| { |
| if (checkShape(this.customHandles[i].shape)) |
| { |
| // LATER: Return reference to active shape |
| return mxEvent.CUSTOM_HANDLE - i; |
| } |
| } |
| } |
| |
| if (checkShape(this.rotationShape)) |
| { |
| return mxEvent.ROTATION_HANDLE; |
| } |
| else if (checkShape(this.labelShape)) |
| { |
| return mxEvent.LABEL_HANDLE; |
| } |
| |
| if (this.sizers != null) |
| { |
| for (var i = 0; i < this.sizers.length; i++) |
| { |
| if (checkShape(this.sizers[i])) |
| { |
| return i; |
| } |
| } |
| } |
| |
| return null; |
| }; |
| |
| /** |
| * Function: isCustomHandleEvent |
| * |
| * Returns true if the given event allows custom handles to be changed. This |
| * implementation returns true. |
| */ |
| mxVertexHandler.prototype.isCustomHandleEvent = function(me) |
| { |
| return true; |
| }; |
| |
| /** |
| * Function: mouseDown |
| * |
| * Handles the event if a handle has been clicked. By consuming the |
| * event all subsequent events of the gesture are redirected to this |
| * handler. |
| */ |
| mxVertexHandler.prototype.mouseDown = function(sender, me) |
| { |
| var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 0; |
| |
| if (!me.isConsumed() && this.graph.isEnabled() && (tol > 0 || me.getState() == this.state)) |
| { |
| var handle = this.getHandleForEvent(me); |
| |
| if (handle != null) |
| { |
| this.start(me.getGraphX(), me.getGraphY(), handle); |
| me.consume(); |
| } |
| } |
| }; |
| |
| /** |
| * Function: isLivePreviewBorder |
| * |
| * Called if <livePreview> is enabled to check if a border should be painted. |
| * This implementation returns true if the shape is transparent. |
| */ |
| mxVertexHandler.prototype.isLivePreviewBorder = function() |
| { |
| return this.state.shape != null && this.state.shape.fill == null && this.state.shape.stroke == null; |
| }; |
| |
| /** |
| * Function: start |
| * |
| * Starts the handling of the mouse gesture. |
| */ |
| mxVertexHandler.prototype.start = function(x, y, index) |
| { |
| this.inTolerance = true; |
| this.childOffsetX = 0; |
| this.childOffsetY = 0; |
| this.index = index; |
| this.startX = x; |
| this.startY = y; |
| |
| // Saves reference to parent state |
| var model = this.state.view.graph.model; |
| var parent = model.getParent(this.state.cell); |
| |
| if (this.state.view.currentRoot != parent && (model.isVertex(parent) || model.isEdge(parent))) |
| { |
| this.parentState = this.state.view.graph.view.getState(parent); |
| } |
| |
| // Creates a preview that can be on top of any HTML label |
| this.selectionBorder.node.style.display = (index == mxEvent.ROTATION_HANDLE) ? 'inline' : 'none'; |
| |
| // Creates the border that represents the new bounds |
| if (!this.livePreview || this.isLivePreviewBorder()) |
| { |
| this.preview = this.createSelectionShape(this.bounds); |
| |
| if (!(mxClient.IS_SVG && Number(this.state.style[mxConstants.STYLE_ROTATION] || '0') != 0) && |
| this.state.text != null && this.state.text.node.parentNode == this.graph.container) |
| { |
| this.preview.dialect = mxConstants.DIALECT_STRICTHTML; |
| this.preview.init(this.graph.container); |
| } |
| else |
| { |
| this.preview.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? |
| mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; |
| this.preview.init(this.graph.view.getOverlayPane()); |
| } |
| } |
| |
| // Prepares the handles for live preview |
| if (this.livePreview) |
| { |
| this.hideSizers(); |
| |
| if (index == mxEvent.ROTATION_HANDLE) |
| { |
| this.rotationShape.node.style.display = ''; |
| } |
| else if (index == mxEvent.LABEL_HANDLE) |
| { |
| this.labelShape.node.style.display = ''; |
| } |
| else if (this.sizers != null && this.sizers[index] != null) |
| { |
| this.sizers[index].node.style.display = ''; |
| } |
| else if (index <= mxEvent.CUSTOM_HANDLE && this.customHandles != null) |
| { |
| this.customHandles[mxEvent.CUSTOM_HANDLE - index].setVisible(true); |
| } |
| |
| // Gets the array of connected edge handlers for redrawing |
| var edges = this.graph.getEdges(this.state.cell); |
| this.edgeHandlers = []; |
| |
| for (var i = 0; i < edges.length; i++) |
| { |
| var handler = this.graph.selectionCellsHandler.getHandler(edges[i]); |
| |
| if (handler != null) |
| { |
| this.edgeHandlers.push(handler); |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Function: hideHandles |
| * |
| * Shortcut to <hideSizers>. |
| */ |
| mxVertexHandler.prototype.setHandlesVisible = function(visible) |
| { |
| if (this.sizers != null) |
| { |
| for (var i = 0; i < this.sizers.length; i++) |
| { |
| this.sizers[i].node.style.display = (visible) ? '' : 'none'; |
| } |
| } |
| |
| if (this.customHandles != null) |
| { |
| for (var i = 0; i < this.customHandles.length; i++) |
| { |
| this.customHandles[i].setVisible(visible); |
| } |
| } |
| }; |
| |
| /** |
| * Function: hideSizers |
| * |
| * Hides all sizers except. |
| * |
| * Starts the handling of the mouse gesture. |
| */ |
| mxVertexHandler.prototype.hideSizers = function() |
| { |
| this.setHandlesVisible(false); |
| }; |
| |
| /** |
| * Function: checkTolerance |
| * |
| * Checks if the coordinates for the given event are within the |
| * <mxGraph.tolerance>. If the event is a mouse event then the tolerance is |
| * ignored. |
| */ |
| mxVertexHandler.prototype.checkTolerance = function(me) |
| { |
| if (this.inTolerance && this.startX != null && this.startY != null) |
| { |
| if (mxEvent.isMouseEvent(me.getEvent()) || |
| Math.abs(me.getGraphX() - this.startX) > this.graph.tolerance || |
| Math.abs(me.getGraphY() - this.startY) > this.graph.tolerance) |
| { |
| this.inTolerance = false; |
| } |
| } |
| }; |
| |
| /** |
| * Function: updateHint |
| * |
| * Hook for subclassers do show details while the handler is active. |
| */ |
| mxVertexHandler.prototype.updateHint = function(me) { }; |
| |
| /** |
| * Function: removeHint |
| * |
| * Hooks for subclassers to hide details when the handler gets inactive. |
| */ |
| mxVertexHandler.prototype.removeHint = function() { }; |
| |
| /** |
| * Function: roundAngle |
| * |
| * Hook for rounding the angle. This uses Math.round. |
| */ |
| mxVertexHandler.prototype.roundAngle = function(angle) |
| { |
| return Math.round(angle * 10) / 10; |
| }; |
| |
| /** |
| * Function: roundLength |
| * |
| * Hook for rounding the unscaled width or height. This uses Math.round. |
| */ |
| mxVertexHandler.prototype.roundLength = function(length) |
| { |
| return Math.round(length); |
| }; |
| |
| /** |
| * Function: mouseMove |
| * |
| * Handles the event by updating the preview. |
| */ |
| mxVertexHandler.prototype.mouseMove = function(sender, me) |
| { |
| if (!me.isConsumed() && this.index != null) |
| { |
| // Checks tolerance for ignoring single clicks |
| this.checkTolerance(me); |
| |
| if (!this.inTolerance) |
| { |
| if (this.index <= mxEvent.CUSTOM_HANDLE) |
| { |
| if (this.customHandles != null) |
| { |
| this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me); |
| this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = true; |
| } |
| } |
| else if (this.index == mxEvent.LABEL_HANDLE) |
| { |
| this.moveLabel(me); |
| } |
| else if (this.index == mxEvent.ROTATION_HANDLE) |
| { |
| this.rotateVertex(me); |
| } |
| else |
| { |
| this.resizeVertex(me); |
| } |
| |
| this.updateHint(me); |
| } |
| |
| me.consume(); |
| } |
| // Workaround for disabling the connect highlight when over handle |
| else if (!this.graph.isMouseDown && this.getHandleForEvent(me) != null) |
| { |
| me.consume(false); |
| } |
| }; |
| |
| /** |
| * Function: rotateVertex |
| * |
| * Rotates the vertex. |
| */ |
| mxVertexHandler.prototype.moveLabel = function(me) |
| { |
| var point = new mxPoint(me.getGraphX(), me.getGraphY()); |
| var tr = this.graph.view.translate; |
| var scale = this.graph.view.scale; |
| |
| if (this.graph.isGridEnabledEvent(me.getEvent())) |
| { |
| point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale; |
| point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale; |
| } |
| |
| var index = (this.rotationShape != null) ? this.sizers.length - 2 : this.sizers.length - 1; |
| this.moveSizerTo(this.sizers[index], point.x, point.y); |
| }; |
| |
| /** |
| * Function: rotateVertex |
| * |
| * Rotates the vertex. |
| */ |
| mxVertexHandler.prototype.rotateVertex = function(me) |
| { |
| var point = new mxPoint(me.getGraphX(), me.getGraphY()); |
| var dx = this.state.x + this.state.width / 2 - point.x; |
| var dy = this.state.y + this.state.height / 2 - point.y; |
| this.currentAlpha = (dx != 0) ? Math.atan(dy / dx) * 180 / Math.PI + 90 : ((dy < 0) ? 180 : 0); |
| |
| if (dx > 0) |
| { |
| this.currentAlpha -= 180; |
| } |
| |
| // Rotation raster |
| if (this.rotationRaster && this.graph.isGridEnabledEvent(me.getEvent())) |
| { |
| var dx = point.x - this.state.getCenterX(); |
| var dy = point.y - this.state.getCenterY(); |
| var dist = Math.abs(Math.sqrt(dx * dx + dy * dy) - 20) * 3; |
| var raster = Math.max(1, 5 * Math.min(3, Math.max(0, Math.round(80 / Math.abs(dist))))); |
| |
| this.currentAlpha = Math.round(this.currentAlpha / raster) * raster; |
| } |
| else |
| { |
| this.currentAlpha = this.roundAngle(this.currentAlpha); |
| } |
| |
| this.selectionBorder.rotation = this.currentAlpha; |
| this.selectionBorder.redraw(); |
| |
| if (this.livePreview) |
| { |
| this.redrawHandles(); |
| } |
| }; |
| |
| /** |
| * Function: rotateVertex |
| * |
| * Rotates the vertex. |
| */ |
| mxVertexHandler.prototype.resizeVertex = function(me) |
| { |
| var ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY()); |
| var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0'); |
| var point = new mxPoint(me.getGraphX(), me.getGraphY()); |
| var tr = this.graph.view.translate; |
| var scale = this.graph.view.scale; |
| var cos = Math.cos(-alpha); |
| var sin = Math.sin(-alpha); |
| |
| var dx = point.x - this.startX; |
| var dy = point.y - this.startY; |
| |
| // Rotates vector for mouse gesture |
| var tx = cos * dx - sin * dy; |
| var ty = sin * dx + cos * dy; |
| |
| dx = tx; |
| dy = ty; |
| |
| var geo = this.graph.getCellGeometry(this.state.cell); |
| this.unscaledBounds = this.union(geo, dx / scale, dy / scale, this.index, |
| this.graph.isGridEnabledEvent(me.getEvent()), 1, |
| new mxPoint(0, 0), this.isConstrainedEvent(me), |
| this.isCenteredEvent(this.state, me)); |
| |
| // Keeps vertex within maximum graph or parent bounds |
| if (!geo.relative) |
| { |
| var max = this.graph.getMaximumGraphBounds(); |
| |
| // Handles child cells |
| if (max != null && this.parentState != null) |
| { |
| max = mxRectangle.fromRectangle(max); |
| |
| max.x -= (this.parentState.x - tr.x * scale) / scale; |
| max.y -= (this.parentState.y - tr.y * scale) / scale; |
| } |
| |
| if (this.graph.isConstrainChild(this.state.cell)) |
| { |
| var tmp = this.graph.getCellContainmentArea(this.state.cell); |
| |
| if (tmp != null) |
| { |
| var overlap = this.graph.getOverlap(this.state.cell); |
| |
| if (overlap > 0) |
| { |
| tmp = mxRectangle.fromRectangle(tmp); |
| |
| tmp.x -= tmp.width * overlap; |
| tmp.y -= tmp.height * overlap; |
| tmp.width += 2 * tmp.width * overlap; |
| tmp.height += 2 * tmp.height * overlap; |
| } |
| |
| if (max == null) |
| { |
| max = tmp; |
| } |
| else |
| { |
| max = mxRectangle.fromRectangle(max); |
| max.intersect(tmp); |
| } |
| } |
| } |
| |
| if (max != null) |
| { |
| if (this.unscaledBounds.x < max.x) |
| { |
| this.unscaledBounds.width -= max.x - this.unscaledBounds.x; |
| this.unscaledBounds.x = max.x; |
| } |
| |
| if (this.unscaledBounds.y < max.y) |
| { |
| this.unscaledBounds.height -= max.y - this.unscaledBounds.y; |
| this.unscaledBounds.y = max.y; |
| } |
| |
| if (this.unscaledBounds.x + this.unscaledBounds.width > max.x + max.width) |
| { |
| this.unscaledBounds.width -= this.unscaledBounds.x + |
| this.unscaledBounds.width - max.x - max.width; |
| } |
| |
| if (this.unscaledBounds.y + this.unscaledBounds.height > max.y + max.height) |
| { |
| this.unscaledBounds.height -= this.unscaledBounds.y + |
| this.unscaledBounds.height - max.y - max.height; |
| } |
| } |
| } |
| |
| this.bounds = new mxRectangle(((this.parentState != null) ? this.parentState.x : tr.x * scale) + |
| (this.unscaledBounds.x) * scale, ((this.parentState != null) ? this.parentState.y : tr.y * scale) + |
| (this.unscaledBounds.y) * scale, this.unscaledBounds.width * scale, this.unscaledBounds.height * scale); |
| |
| if (geo.relative && this.parentState != null) |
| { |
| this.bounds.x += this.state.x - this.parentState.x; |
| this.bounds.y += this.state.y - this.parentState.y; |
| } |
| |
| cos = Math.cos(alpha); |
| sin = Math.sin(alpha); |
| |
| var c2 = new mxPoint(this.bounds.getCenterX(), this.bounds.getCenterY()); |
| |
| var dx = c2.x - ct.x; |
| var dy = c2.y - ct.y; |
| |
| var dx2 = cos * dx - sin * dy; |
| var dy2 = sin * dx + cos * dy; |
| |
| var dx3 = dx2 - dx; |
| var dy3 = dy2 - dy; |
| |
| var dx4 = this.bounds.x - this.state.x; |
| var dy4 = this.bounds.y - this.state.y; |
| |
| var dx5 = cos * dx4 - sin * dy4; |
| var dy5 = sin * dx4 + cos * dy4; |
| |
| this.bounds.x += dx3; |
| this.bounds.y += dy3; |
| |
| // Rounds unscaled bounds to int |
| this.unscaledBounds.x = this.roundLength(this.unscaledBounds.x + dx3 / scale); |
| this.unscaledBounds.y = this.roundLength(this.unscaledBounds.y + dy3 / scale); |
| this.unscaledBounds.width = this.roundLength(this.unscaledBounds.width); |
| this.unscaledBounds.height = this.roundLength(this.unscaledBounds.height); |
| |
| // Shifts the children according to parent offset |
| if (!this.graph.isCellCollapsed(this.state.cell) && (dx3 != 0 || dy3 != 0)) |
| { |
| this.childOffsetX = this.state.x - this.bounds.x + dx5; |
| this.childOffsetY = this.state.y - this.bounds.y + dy5; |
| } |
| else |
| { |
| this.childOffsetX = 0; |
| this.childOffsetY = 0; |
| } |
| |
| if (this.livePreview) |
| { |
| this.updateLivePreview(me); |
| } |
| |
| if (this.preview != null) |
| { |
| this.drawPreview(); |
| } |
| }; |
| |
| /** |
| * Function: updateLivePreview |
| * |
| * Repaints the live preview. |
| */ |
| mxVertexHandler.prototype.updateLivePreview = function(me) |
| { |
| // TODO: Apply child offset to children in live preview |
| var scale = this.graph.view.scale; |
| var tr = this.graph.view.translate; |
| |
| // Saves current state |
| var tempState = this.state.clone(); |
| |
| // Temporarily changes size and origin |
| this.state.x = this.bounds.x; |
| this.state.y = this.bounds.y; |
| this.state.origin = new mxPoint(this.state.x / scale - tr.x, this.state.y / scale - tr.y); |
| this.state.width = this.bounds.width; |
| this.state.height = this.bounds.height; |
| |
| // Needed to force update of text bounds |
| this.state.unscaledWidth = null; |
| |
| // Redraws cell and handles |
| var off = this.state.absoluteOffset; |
| off = new mxPoint(off.x, off.y); |
| |
| // Required to store and reset absolute offset for updating label position |
| this.state.absoluteOffset.x = 0; |
| this.state.absoluteOffset.y = 0; |
| var geo = this.graph.getCellGeometry(this.state.cell); |
| |
| if (geo != null) |
| { |
| var offset = geo.offset || this.EMPTY_POINT; |
| |
| if (offset != null && !geo.relative) |
| { |
| this.state.absoluteOffset.x = this.state.view.scale * offset.x; |
| this.state.absoluteOffset.y = this.state.view.scale * offset.y; |
| } |
| |
| this.state.view.updateVertexLabelOffset(this.state); |
| } |
| |
| // Draws the live preview |
| this.state.view.graph.cellRenderer.redraw(this.state, true); |
| |
| // Redraws connected edges TODO: Include child edges |
| this.state.view.invalidate(this.state.cell); |
| this.state.invalid = false; |
| this.state.view.validate(); |
| this.redrawHandles(); |
| |
| // Restores current state |
| this.state.setState(tempState); |
| }; |
| |
| /** |
| * Function: mouseUp |
| * |
| * Handles the event by applying the changes to the geometry. |
| */ |
| mxVertexHandler.prototype.mouseUp = function(sender, me) |
| { |
| if (this.index != null && this.state != null) |
| { |
| var point = new mxPoint(me.getGraphX(), me.getGraphY()); |
| |
| this.graph.getModel().beginUpdate(); |
| try |
| { |
| if (this.index <= mxEvent.CUSTOM_HANDLE) |
| { |
| if (this.customHandles != null) |
| { |
| this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = false; |
| this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].execute(); |
| } |
| } |
| else if (this.index == mxEvent.ROTATION_HANDLE) |
| { |
| if (this.currentAlpha != null) |
| { |
| var delta = this.currentAlpha - (this.state.style[mxConstants.STYLE_ROTATION] || 0); |
| |
| if (delta != 0) |
| { |
| this.rotateCell(this.state.cell, delta); |
| } |
| } |
| else |
| { |
| this.rotateClick(); |
| } |
| } |
| else |
| { |
| var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent()); |
| var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0'); |
| var cos = Math.cos(-alpha); |
| var sin = Math.sin(-alpha); |
| |
| var dx = point.x - this.startX; |
| var dy = point.y - this.startY; |
| |
| // Rotates vector for mouse gesture |
| var tx = cos * dx - sin * dy; |
| var ty = sin * dx + cos * dy; |
| |
| dx = tx; |
| dy = ty; |
| |
| var s = this.graph.view.scale; |
| var recurse = this.isRecursiveResize(this.state, me); |
| this.resizeCell(this.state.cell, this.roundLength(dx / s), this.roundLength(dy / s), |
| this.index, gridEnabled, this.isConstrainedEvent(me), recurse); |
| } |
| } |
| finally |
| { |
| this.graph.getModel().endUpdate(); |
| } |
| |
| me.consume(); |
| this.reset(); |
| } |
| }; |
| |
| /** |
| * Function: rotateCell |
| * |
| * Rotates the given cell to the given rotation. |
| */ |
| mxVertexHandler.prototype.isRecursiveResize = function(state, me) |
| { |
| return this.graph.isRecursiveResize(this.state); |
| }; |
| |
| /** |
| * Function: rotateClick |
| * |
| * Hook for subclassers to implement a single click on the rotation handle. |
| * This code is executed as part of the model transaction. This implementation |
| * is empty. |
| */ |
| mxVertexHandler.prototype.rotateClick = function() { }; |
| |
| /** |
| * Function: rotateCell |
| * |
| * Rotates the given cell and its children by the given angle in degrees. |
| * |
| * Parameters: |
| * |
| * cell - <mxCell> to be rotated. |
| * angle - Angle in degrees. |
| */ |
| mxVertexHandler.prototype.rotateCell = function(cell, angle, parent) |
| { |
| if (angle != 0) |
| { |
| var model = this.graph.getModel(); |
| |
| if (model.isVertex(cell) || model.isEdge(cell)) |
| { |
| if (!model.isEdge(cell)) |
| { |
| var state = this.graph.view.getState(cell); |
| var style = (state != null) ? state.style : this.graph.getCellStyle(cell); |
| |
| if (style != null) |
| { |
| var total = (style[mxConstants.STYLE_ROTATION] || 0) + angle; |
| this.graph.setCellStyles(mxConstants.STYLE_ROTATION, total, [cell]); |
| } |
| } |
| |
| var geo = this.graph.getCellGeometry(cell); |
| |
| if (geo != null) |
| { |
| var pgeo = this.graph.getCellGeometry(parent); |
| |
| if (pgeo != null && !model.isEdge(parent)) |
| { |
| geo = geo.clone(); |
| geo.rotate(angle, new mxPoint(pgeo.width / 2, pgeo.height / 2)); |
| model.setGeometry(cell, geo); |
| } |
| |
| if ((model.isVertex(cell) && !geo.relative) || model.isEdge(cell)) |
| { |
| // Recursive rotation |
| var childCount = model.getChildCount(cell); |
| |
| for (var i = 0; i < childCount; i++) |
| { |
| this.rotateCell(model.getChildAt(cell, i), angle, cell); |
| } |
| } |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Function: reset |
| * |
| * Resets the state of this handler. |
| */ |
| mxVertexHandler.prototype.reset = function() |
| { |
| if (this.sizers != null && this.index != null && this.sizers[this.index] != null && |
| this.sizers[this.index].node.style.display == 'none') |
| { |
| this.sizers[this.index].node.style.display = ''; |
| } |
| |
| this.currentAlpha = null; |
| this.inTolerance = null; |
| this.index = null; |
| |
| // TODO: Reset and redraw cell states for live preview |
| if (this.preview != null) |
| { |
| this.preview.destroy(); |
| this.preview = null; |
| } |
| |
| if (this.livePreview && this.sizers != null) |
| { |
| for (var i = 0; i < this.sizers.length; i++) |
| { |
| if (this.sizers[i] != null) |
| { |
| this.sizers[i].node.style.display = ''; |
| } |
| } |
| } |
| |
| if (this.customHandles != null) |
| { |
| for (var i = 0; i < this.customHandles.length; i++) |
| { |
| if (this.customHandles[i].active) |
| { |
| this.customHandles[i].active = false; |
| this.customHandles[i].reset(); |
| } |
| else |
| { |
| this.customHandles[i].setVisible(true); |
| } |
| } |
| } |
| |
| // Checks if handler has been destroyed |
| if (this.selectionBorder != null) |
| { |
| this.selectionBorder.node.style.display = 'inline'; |
| this.selectionBounds = this.getSelectionBounds(this.state); |
| this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, |
| this.selectionBounds.width, this.selectionBounds.height); |
| this.drawPreview(); |
| } |
| |
| this.removeHint(); |
| this.redrawHandles(); |
| this.edgeHandlers = null; |
| this.unscaledBounds = null; |
| }; |
| |
| /** |
| * Function: resizeCell |
| * |
| * Uses the given vector to change the bounds of the given cell |
| * in the graph using <mxGraph.resizeCell>. |
| */ |
| mxVertexHandler.prototype.resizeCell = function(cell, dx, dy, index, gridEnabled, constrained, recurse) |
| { |
| var geo = this.graph.model.getGeometry(cell); |
| |
| if (geo != null) |
| { |
| if (index == mxEvent.LABEL_HANDLE) |
| { |
| var scale = this.graph.view.scale; |
| dx = Math.round((this.labelShape.bounds.getCenterX() - this.startX) / scale); |
| dy = Math.round((this.labelShape.bounds.getCenterY() - this.startY) / scale); |
| |
| geo = geo.clone(); |
| |
| if (geo.offset == null) |
| { |
| geo.offset = new mxPoint(dx, dy); |
| } |
| else |
| { |
| geo.offset.x += dx; |
| geo.offset.y += dy; |
| } |
| |
| this.graph.model.setGeometry(cell, geo); |
| } |
| else if (this.unscaledBounds != null) |
| { |
| var scale = this.graph.view.scale; |
| |
| if (this.childOffsetX != 0 || this.childOffsetY != 0) |
| { |
| this.moveChildren(cell, Math.round(this.childOffsetX / scale), Math.round(this.childOffsetY / scale)); |
| } |
| |
| this.graph.resizeCell(cell, this.unscaledBounds, recurse); |
| } |
| } |
| }; |
| |
| /** |
| * Function: moveChildren |
| * |
| * Moves the children of the given cell by the given vector. |
| */ |
| mxVertexHandler.prototype.moveChildren = function(cell, dx, dy) |
| { |
| var model = this.graph.getModel(); |
| var childCount = model.getChildCount(cell); |
| |
| for (var i = 0; i < childCount; i++) |
| { |
| var child = model.getChildAt(cell, i); |
| var geo = this.graph.getCellGeometry(child); |
| |
| if (geo != null) |
| { |
| geo = geo.clone(); |
| geo.translate(dx, dy); |
| model.setGeometry(child, geo); |
| } |
| } |
| }; |
| /** |
| * Function: union |
| * |
| * Returns the union of the given bounds and location for the specified |
| * handle index. |
| * |
| * To override this to limit the size of vertex via a minWidth/-Height style, |
| * the following code can be used. |
| * |
| * (code) |
| * var vertexHandlerUnion = mxVertexHandler.prototype.union; |
| * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained) |
| * { |
| * var result = vertexHandlerUnion.apply(this, arguments); |
| * |
| * result.width = Math.max(result.width, mxUtils.getNumber(this.state.style, 'minWidth', 0)); |
| * result.height = Math.max(result.height, mxUtils.getNumber(this.state.style, 'minHeight', 0)); |
| * |
| * return result; |
| * }; |
| * (end) |
| * |
| * The minWidth/-Height style can then be used as follows: |
| * |
| * (code) |
| * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'minWidth=100;minHeight=100;'); |
| * (end) |
| * |
| * To override this to update the height for a wrapped text if the width of a vertex is |
| * changed, the following can be used. |
| * |
| * (code) |
| * var mxVertexHandlerUnion = mxVertexHandler.prototype.union; |
| * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained) |
| * { |
| * var result = mxVertexHandlerUnion.apply(this, arguments); |
| * var s = this.state; |
| * |
| * if (this.graph.isHtmlLabel(s.cell) && (index == 3 || index == 4) && |
| * s.text != null && s.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap') |
| * { |
| * var label = this.graph.getLabel(s.cell); |
| * var fontSize = mxUtils.getNumber(s.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE); |
| * var ww = result.width / s.view.scale - s.text.spacingRight - s.text.spacingLeft |
| * |
| * result.height = mxUtils.getSizeForString(label, fontSize, s.style[mxConstants.STYLE_FONTFAMILY], ww).height; |
| * } |
| * |
| * return result; |
| * }; |
| * (end) |
| */ |
| mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained, centered) |
| { |
| if (this.singleSizer) |
| { |
| var x = bounds.x + bounds.width + dx; |
| var y = bounds.y + bounds.height + dy; |
| |
| if (gridEnabled) |
| { |
| x = this.graph.snap(x / scale) * scale; |
| y = this.graph.snap(y / scale) * scale; |
| } |
| |
| var rect = new mxRectangle(bounds.x, bounds.y, 0, 0); |
| rect.add(new mxRectangle(x, y, 0, 0)); |
| |
| return rect; |
| } |
| else |
| { |
| var w0 = bounds.width; |
| var h0 = bounds.height; |
| var left = bounds.x - tr.x * scale; |
| var right = left + w0; |
| var top = bounds.y - tr.y * scale; |
| var bottom = top + h0; |
| |
| var cx = left + w0 / 2; |
| var cy = top + h0 / 2; |
| |
| if (index > 4 /* Bottom Row */) |
| { |
| bottom = bottom + dy; |
| |
| if (gridEnabled) |
| { |
| bottom = this.graph.snap(bottom / scale) * scale; |
| } |
| } |
| else if (index < 3 /* Top Row */) |
| { |
| top = top + dy; |
| |
| if (gridEnabled) |
| { |
| top = this.graph.snap(top / scale) * scale; |
| } |
| } |
| |
| if (index == 0 || index == 3 || index == 5 /* Left */) |
| { |
| left += dx; |
| |
| if (gridEnabled) |
| { |
| left = this.graph.snap(left / scale) * scale; |
| } |
| } |
| else if (index == 2 || index == 4 || index == 7 /* Right */) |
| { |
| right += dx; |
| |
| if (gridEnabled) |
| { |
| right = this.graph.snap(right / scale) * scale; |
| } |
| } |
| |
| var width = right - left; |
| var height = bottom - top; |
| |
| if (constrained) |
| { |
| var geo = this.graph.getCellGeometry(this.state.cell); |
| |
| if (geo != null) |
| { |
| var aspect = geo.width / geo.height; |
| |
| if (index== 1 || index== 2 || index == 7 || index == 6) |
| { |
| width = height * aspect; |
| } |
| else |
| { |
| height = width / aspect; |
| } |
| |
| if (index == 0) |
| { |
| left = right - width; |
| top = bottom - height; |
| } |
| } |
| } |
| |
| if (centered) |
| { |
| width += (width - w0); |
| height += (height - h0); |
| |
| var cdx = cx - (left + width / 2); |
| var cdy = cy - (top + height / 2); |
| |
| left += cdx; |
| top += cdy; |
| right += cdx; |
| bottom += cdy; |
| } |
| |
| // Flips over left side |
| if (width < 0) |
| { |
| left += width; |
| width = Math.abs(width); |
| } |
| |
| // Flips over top side |
| if (height < 0) |
| { |
| top += height; |
| height = Math.abs(height); |
| } |
| |
| var result = new mxRectangle(left + tr.x * scale, top + tr.y * scale, width, height); |
| |
| if (this.minBounds != null) |
| { |
| result.width = Math.max(result.width, this.minBounds.x * scale + this.minBounds.width * scale + |
| Math.max(0, this.x0 * scale - result.x)); |
| result.height = Math.max(result.height, this.minBounds.y * scale + this.minBounds.height * scale + |
| Math.max(0, this.y0 * scale - result.y)); |
| } |
| |
| return result; |
| } |
| }; |
| |
| /** |
| * Function: redraw |
| * |
| * Redraws the handles and the preview. |
| */ |
| mxVertexHandler.prototype.redraw = function() |
| { |
| this.selectionBounds = this.getSelectionBounds(this.state); |
| this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height); |
| |
| this.redrawHandles(); |
| this.drawPreview(); |
| }; |
| |
| /** |
| * Returns the padding to be used for drawing handles for the current <bounds>. |
| */ |
| mxVertexHandler.prototype.getHandlePadding = function() |
| { |
| // KNOWN: Tolerance depends on event type (eg. 0 for mouse events) |
| var result = new mxPoint(0, 0); |
| var tol = this.tolerance; |
| |
| if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null && |
| (this.bounds.width < 2 * this.sizers[0].bounds.width + 2 * tol || |
| this.bounds.height < 2 * this.sizers[0].bounds.height + 2 * tol)) |
| { |
| tol /= 2; |
| |
| result.x = this.sizers[0].bounds.width + tol; |
| result.y = this.sizers[0].bounds.height + tol; |
| } |
| |
| return result; |
| }; |
| |
| /** |
| * Function: redrawHandles |
| * |
| * Redraws the handles. To hide certain handles the following code can be used. |
| * |
| * (code) |
| * mxVertexHandler.prototype.redrawHandles = function() |
| * { |
| * mxVertexHandlerRedrawHandles.apply(this, arguments); |
| * |
| * if (this.sizers != null && this.sizers.length > 7) |
| * { |
| * this.sizers[1].node.style.display = 'none'; |
| * this.sizers[6].node.style.display = 'none'; |
| * } |
| * }; |
| * (end) |
| */ |
| mxVertexHandler.prototype.redrawHandles = function() |
| { |
| var tol = this.tolerance; |
| this.horizontalOffset = 0; |
| this.verticalOffset = 0; |
| var s = this.bounds; |
| |
| if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null) |
| { |
| if (this.index == null && this.manageSizers && this.sizers.length >= 8) |
| { |
| // KNOWN: Tolerance depends on event type (eg. 0 for mouse events) |
| var padding = this.getHandlePadding(); |
| this.horizontalOffset = padding.x; |
| this.verticalOffset = padding.y; |
| |
| if (this.horizontalOffset != 0 || this.verticalOffset != 0) |
| { |
| s = new mxRectangle(s.x, s.y, s.width, s.height); |
| |
| s.x -= this.horizontalOffset / 2; |
| s.width += this.horizontalOffset; |
| s.y -= this.verticalOffset / 2; |
| s.height += this.verticalOffset; |
| } |
| |
| if (this.sizers.length >= 8) |
| { |
| if ((s.width < 2 * this.sizers[0].bounds.width + 2 * tol) || |
| (s.height < 2 * this.sizers[0].bounds.height + 2 * tol)) |
| { |
| this.sizers[0].node.style.display = 'none'; |
| this.sizers[2].node.style.display = 'none'; |
| this.sizers[5].node.style.display = 'none'; |
| this.sizers[7].node.style.display = 'none'; |
| } |
| else |
| { |
| this.sizers[0].node.style.display = ''; |
| this.sizers[2].node.style.display = ''; |
| this.sizers[5].node.style.display = ''; |
| this.sizers[7].node.style.display = ''; |
| } |
| } |
| } |
| |
| var r = s.x + s.width; |
| var b = s.y + s.height; |
| |
| if (this.singleSizer) |
| { |
| this.moveSizerTo(this.sizers[0], r, b); |
| } |
| else |
| { |
| var cx = s.x + s.width / 2; |
| var cy = s.y + s.height / 2; |
| |
| if (this.sizers.length >= 8) |
| { |
| var crs = ['nw-resize', 'n-resize', 'ne-resize', 'e-resize', 'se-resize', 's-resize', 'sw-resize', 'w-resize']; |
| |
| var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0'); |
| var cos = Math.cos(alpha); |
| var sin = Math.sin(alpha); |
| |
| var da = Math.round(alpha * 4 / Math.PI); |
| |
| var ct = new mxPoint(s.getCenterX(), s.getCenterY()); |
| var pt = mxUtils.getRotatedPoint(new mxPoint(s.x, s.y), cos, sin, ct); |
| |
| this.moveSizerTo(this.sizers[0], pt.x, pt.y); |
| this.sizers[0].setCursor(crs[mxUtils.mod(0 + da, crs.length)]); |
| |
| pt.x = cx; |
| pt.y = s.y; |
| pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); |
| |
| this.moveSizerTo(this.sizers[1], pt.x, pt.y); |
| this.sizers[1].setCursor(crs[mxUtils.mod(1 + da, crs.length)]); |
| |
| pt.x = r; |
| pt.y = s.y; |
| pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); |
| |
| this.moveSizerTo(this.sizers[2], pt.x, pt.y); |
| this.sizers[2].setCursor(crs[mxUtils.mod(2 + da, crs.length)]); |
| |
| pt.x = s.x; |
| pt.y = cy; |
| pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); |
| |
| this.moveSizerTo(this.sizers[3], pt.x, pt.y); |
| this.sizers[3].setCursor(crs[mxUtils.mod(7 + da, crs.length)]); |
| |
| pt.x = r; |
| pt.y = cy; |
| pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); |
| |
| this.moveSizerTo(this.sizers[4], pt.x, pt.y); |
| this.sizers[4].setCursor(crs[mxUtils.mod(3 + da, crs.length)]); |
| |
| pt.x = s.x; |
| pt.y = b; |
| pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); |
| |
| this.moveSizerTo(this.sizers[5], pt.x, pt.y); |
| this.sizers[5].setCursor(crs[mxUtils.mod(6 + da, crs.length)]); |
| |
| pt.x = cx; |
| pt.y = b; |
| pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); |
| |
| this.moveSizerTo(this.sizers[6], pt.x, pt.y); |
| this.sizers[6].setCursor(crs[mxUtils.mod(5 + da, crs.length)]); |
| |
| pt.x = r; |
| pt.y = b; |
| pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); |
| |
| this.moveSizerTo(this.sizers[7], pt.x, pt.y); |
| this.sizers[7].setCursor(crs[mxUtils.mod(4 + da, crs.length)]); |
| |
| this.moveSizerTo(this.sizers[8], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y); |
| } |
| else if (this.state.width >= 2 && this.state.height >= 2) |
| { |
| this.moveSizerTo(this.sizers[0], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y); |
| } |
| else |
| { |
| this.moveSizerTo(this.sizers[0], this.state.x, this.state.y); |
| } |
| } |
| } |
| |
| if (this.rotationShape != null) |
| { |
| var alpha = mxUtils.toRadians((this.currentAlpha != null) ? this.currentAlpha : this.state.style[mxConstants.STYLE_ROTATION] || '0'); |
| var cos = Math.cos(alpha); |
| var sin = Math.sin(alpha); |
| |
| var ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY()); |
| var pt = mxUtils.getRotatedPoint(new mxPoint(s.x + s.width / 2, s.y + this.rotationHandleVSpacing), cos, sin, ct); |
| |
| if (this.rotationShape.node != null) |
| { |
| this.moveSizerTo(this.rotationShape, pt.x, pt.y); |
| } |
| } |
| |
| if (this.selectionBorder != null) |
| { |
| this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0'); |
| } |
| |
| if (this.edgeHandlers != null) |
| { |
| for (var i = 0; i < this.edgeHandlers.length; i++) |
| { |
| this.edgeHandlers[i].redraw(); |
| } |
| } |
| |
| if (this.customHandles != null) |
| { |
| for (var i = 0; i < this.customHandles.length; i++) |
| { |
| var temp = this.customHandles[i].shape.node.style.display; |
| this.customHandles[i].redraw(); |
| this.customHandles[i].shape.node.style.display = temp; |
| } |
| } |
| |
| this.updateParentHighlight(); |
| }; |
| |
| /** |
| * Function: updateParentHighlight |
| * |
| * Updates the highlight of the parent if <parentHighlightEnabled> is true. |
| */ |
| mxVertexHandler.prototype.updateParentHighlight = function() |
| { |
| // If not destroyed |
| if (this.selectionBorder != null) |
| { |
| if (this.parentHighlight != null) |
| { |
| var parent = this.graph.model.getParent(this.state.cell); |
| |
| if (this.graph.model.isVertex(parent)) |
| { |
| var pstate = this.graph.view.getState(parent); |
| var b = this.parentHighlight.bounds; |
| |
| if (pstate != null && (b.x != pstate.x || b.y != pstate.y || |
| b.width != pstate.width || b.height != pstate.height)) |
| { |
| this.parentHighlight.bounds = pstate; |
| this.parentHighlight.redraw(); |
| } |
| } |
| else |
| { |
| this.parentHighlight.destroy(); |
| this.parentHighlight = null; |
| } |
| } |
| else if (this.parentHighlightEnabled) |
| { |
| var parent = this.graph.model.getParent(this.state.cell); |
| |
| if (this.graph.model.isVertex(parent)) |
| { |
| var pstate = this.graph.view.getState(parent); |
| |
| if (pstate != null) |
| { |
| this.parentHighlight = this.createParentHighlightShape(pstate); |
| // VML dialect required here for event transparency in IE |
| this.parentHighlight.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; |
| this.parentHighlight.pointerEvents = false; |
| this.parentHighlight.rotation = Number(pstate.style[mxConstants.STYLE_ROTATION] || '0'); |
| this.parentHighlight.init(this.graph.getView().getOverlayPane()); |
| } |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Function: drawPreview |
| * |
| * Redraws the preview. |
| */ |
| mxVertexHandler.prototype.drawPreview = function() |
| { |
| if (this.preview != null) |
| { |
| this.preview.bounds = this.bounds; |
| |
| if (this.preview.node.parentNode == this.graph.container) |
| { |
| this.preview.bounds.width = Math.max(0, this.preview.bounds.width - 1); |
| this.preview.bounds.height = Math.max(0, this.preview.bounds.height - 1); |
| } |
| |
| this.preview.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0'); |
| this.preview.redraw(); |
| } |
| |
| this.selectionBorder.bounds = this.bounds; |
| this.selectionBorder.redraw(); |
| |
| if (this.parentHighlight != null) |
| { |
| this.parentHighlight.redraw(); |
| } |
| }; |
| |
| /** |
| * Function: destroy |
| * |
| * Destroys the handler and all its resources and DOM nodes. |
| */ |
| mxVertexHandler.prototype.destroy = function() |
| { |
| if (this.escapeHandler != null) |
| { |
| this.state.view.graph.removeListener(this.escapeHandler); |
| this.escapeHandler = null; |
| } |
| |
| if (this.preview != null) |
| { |
| this.preview.destroy(); |
| this.preview = null; |
| } |
| |
| if (this.parentHighlight != null) |
| { |
| this.parentHighlight.destroy(); |
| this.parentHighlight = null; |
| } |
| |
| if (this.selectionBorder != null) |
| { |
| this.selectionBorder.destroy(); |
| this.selectionBorder = null; |
| } |
| |
| this.labelShape = null; |
| this.removeHint(); |
| |
| if (this.sizers != null) |
| { |
| for (var i = 0; i < this.sizers.length; i++) |
| { |
| this.sizers[i].destroy(); |
| } |
| |
| this.sizers = null; |
| } |
| |
| if (this.customHandles != null) |
| { |
| for (var i = 0; i < this.customHandles.length; i++) |
| { |
| this.customHandles[i].destroy(); |
| } |
| |
| this.customHandles = null; |
| } |
| }; |