| /** |
| * Copyright (c) 2006-2015, JGraph Ltd |
| * Copyright (c) 2006-2015, Gaudenz Alder |
| */ |
| /** |
| * Class: mxEdgeHandler |
| * |
| * Graph event handler that reconnects edges and modifies control points and |
| * the edge label location. Uses <mxTerminalMarker> for finding and |
| * highlighting new source and target vertices. This handler is automatically |
| * created in <mxGraph.createHandler> for each selected edge. |
| * |
| * To enable adding/removing control points, the following code can be used: |
| * |
| * (code) |
| * mxEdgeHandler.prototype.addEnabled = true; |
| * mxEdgeHandler.prototype.removeEnabled = true; |
| * (end) |
| * |
| * Note: This experimental feature is not recommended for production use. |
| * |
| * Constructor: mxEdgeHandler |
| * |
| * Constructs an edge handler for the specified <mxCellState>. |
| * |
| * Parameters: |
| * |
| * state - <mxCellState> of the cell to be handled. |
| */ |
| function mxEdgeHandler(state) |
| { |
| if (state != null) |
| { |
| this.state = state; |
| this.init(); |
| |
| // Handles escape keystrokes |
| this.escapeHandler = mxUtils.bind(this, function(sender, evt) |
| { |
| this.reset(); |
| }); |
| |
| this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler); |
| } |
| }; |
| |
| /** |
| * Variable: graph |
| * |
| * Reference to the enclosing <mxGraph>. |
| */ |
| mxEdgeHandler.prototype.graph = null; |
| |
| /** |
| * Variable: state |
| * |
| * Reference to the <mxCellState> being modified. |
| */ |
| mxEdgeHandler.prototype.state = null; |
| |
| /** |
| * Variable: marker |
| * |
| * Holds the <mxTerminalMarker> which is used for highlighting terminals. |
| */ |
| mxEdgeHandler.prototype.marker = null; |
| |
| /** |
| * Variable: constraintHandler |
| * |
| * Holds the <mxConstraintHandler> used for drawing and highlighting |
| * constraints. |
| */ |
| mxEdgeHandler.prototype.constraintHandler = null; |
| |
| /** |
| * Variable: error |
| * |
| * Holds the current validation error while a connection is being changed. |
| */ |
| mxEdgeHandler.prototype.error = null; |
| |
| /** |
| * Variable: shape |
| * |
| * Holds the <mxShape> that represents the preview edge. |
| */ |
| mxEdgeHandler.prototype.shape = null; |
| |
| /** |
| * Variable: bends |
| * |
| * Holds the <mxShapes> that represent the points. |
| */ |
| mxEdgeHandler.prototype.bends = null; |
| |
| /** |
| * Variable: labelShape |
| * |
| * Holds the <mxShape> that represents the label position. |
| */ |
| mxEdgeHandler.prototype.labelShape = null; |
| |
| /** |
| * Variable: cloneEnabled |
| * |
| * Specifies if cloning by control-drag is enabled. Default is true. |
| */ |
| mxEdgeHandler.prototype.cloneEnabled = true; |
| |
| /** |
| * Variable: addEnabled |
| * |
| * Specifies if adding bends by shift-click is enabled. Default is false. |
| * Note: This experimental feature is not recommended for production use. |
| */ |
| mxEdgeHandler.prototype.addEnabled = false; |
| |
| /** |
| * Variable: removeEnabled |
| * |
| * Specifies if removing bends by shift-click is enabled. Default is false. |
| * Note: This experimental feature is not recommended for production use. |
| */ |
| mxEdgeHandler.prototype.removeEnabled = false; |
| |
| /** |
| * Variable: dblClickRemoveEnabled |
| * |
| * Specifies if removing bends by double click is enabled. Default is false. |
| */ |
| mxEdgeHandler.prototype.dblClickRemoveEnabled = false; |
| |
| /** |
| * Variable: mergeRemoveEnabled |
| * |
| * Specifies if removing bends by dropping them on other bends is enabled. |
| * Default is false. |
| */ |
| mxEdgeHandler.prototype.mergeRemoveEnabled = false; |
| |
| /** |
| * Variable: straightRemoveEnabled |
| * |
| * Specifies if removing bends by creating straight segments should be enabled. |
| * If enabled, this can be overridden by holding down the alt key while moving. |
| * Default is false. |
| */ |
| mxEdgeHandler.prototype.straightRemoveEnabled = false; |
| |
| /** |
| * Variable: virtualBendsEnabled |
| * |
| * Specifies if virtual bends should be added in the center of each |
| * segments. These bends can then be used to add new waypoints. |
| * Default is false. |
| */ |
| mxEdgeHandler.prototype.virtualBendsEnabled = false; |
| |
| /** |
| * Variable: virtualBendOpacity |
| * |
| * Opacity to be used for virtual bends (see <virtualBendsEnabled>). |
| * Default is 20. |
| */ |
| mxEdgeHandler.prototype.virtualBendOpacity = 20; |
| |
| /** |
| * Variable: parentHighlightEnabled |
| * |
| * Specifies if the parent should be highlighted if a child cell is selected. |
| * Default is false. |
| */ |
| mxEdgeHandler.prototype.parentHighlightEnabled = false; |
| |
| /** |
| * Variable: preferHtml |
| * |
| * Specifies if bends should be added to the graph container. This is updated |
| * in <init> based on whether the edge or one of its terminals has an HTML |
| * label in the container. |
| */ |
| mxEdgeHandler.prototype.preferHtml = false; |
| |
| /** |
| * Variable: allowHandleBoundsCheck |
| * |
| * Specifies if the bounds of handles should be used for hit-detection in IE |
| * Default is true. |
| */ |
| mxEdgeHandler.prototype.allowHandleBoundsCheck = true; |
| |
| /** |
| * Variable: snapToTerminals |
| * |
| * Specifies if waypoints should snap to the routing centers of terminals. |
| * Default is false. |
| */ |
| mxEdgeHandler.prototype.snapToTerminals = false; |
| |
| /** |
| * Variable: handleImage |
| * |
| * Optional <mxImage> to be used as handles. Default is null. |
| */ |
| mxEdgeHandler.prototype.handleImage = null; |
| |
| /** |
| * Variable: tolerance |
| * |
| * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0. |
| */ |
| mxEdgeHandler.prototype.tolerance = 0; |
| |
| /** |
| * Variable: outlineConnect |
| * |
| * Specifies if connections to the outline of a highlighted target should be |
| * enabled. This will allow to place the connection point along the outline of |
| * the highlighted target. Default is false. |
| */ |
| mxEdgeHandler.prototype.outlineConnect = false; |
| |
| /** |
| * Variable: manageLabelHandle |
| * |
| * Specifies if the label handle should be moved if it intersects with another |
| * handle. Uses <checkLabelHandle> for checking and moving. Default is false. |
| */ |
| mxEdgeHandler.prototype.manageLabelHandle = false; |
| |
| /** |
| * Function: init |
| * |
| * Initializes the shapes required for this edge handler. |
| */ |
| mxEdgeHandler.prototype.init = function() |
| { |
| this.graph = this.state.view.graph; |
| this.marker = this.createMarker(); |
| this.constraintHandler = new mxConstraintHandler(this.graph); |
| |
| // Clones the original points from the cell |
| // and makes sure at least one point exists |
| this.points = []; |
| |
| // Uses the absolute points of the state |
| // for the initial configuration and preview |
| this.abspoints = this.getSelectionPoints(this.state); |
| this.shape = this.createSelectionShape(this.abspoints); |
| this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? |
| mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; |
| this.shape.init(this.graph.getView().getOverlayPane()); |
| this.shape.pointerEvents = false; |
| this.shape.setCursor(mxConstants.CURSOR_MOVABLE_EDGE); |
| mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state); |
| |
| // Updates preferHtml |
| this.preferHtml = this.state.text != null && |
| this.state.text.node.parentNode == this.graph.container; |
| |
| if (!this.preferHtml) |
| { |
| // Checks source terminal |
| var sourceState = this.state.getVisibleTerminalState(true); |
| |
| if (sourceState != null) |
| { |
| this.preferHtml = sourceState.text != null && |
| sourceState.text.node.parentNode == this.graph.container; |
| } |
| |
| if (!this.preferHtml) |
| { |
| // Checks target terminal |
| var targetState = this.state.getVisibleTerminalState(false); |
| |
| if (targetState != null) |
| { |
| this.preferHtml = targetState.text != null && |
| targetState.text.node.parentNode == this.graph.container; |
| } |
| } |
| } |
| |
| // Adds highlight for parent group |
| 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()); |
| } |
| } |
| } |
| |
| // Creates bends for the non-routed absolute points |
| // or bends that don't correspond to points |
| if (this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells || |
| mxGraphHandler.prototype.maxCells <= 0) |
| { |
| this.bends = this.createBends(); |
| |
| if (this.isVirtualBendsEnabled()) |
| { |
| this.virtualBends = this.createVirtualBends(); |
| } |
| } |
| |
| // Adds a rectangular handle for the label position |
| this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y); |
| this.labelShape = this.createLabelHandleShape(); |
| this.initBend(this.labelShape); |
| this.labelShape.setCursor(mxConstants.CURSOR_LABEL_HANDLE); |
| |
| this.customHandles = this.createCustomHandles(); |
| |
| this.redraw(); |
| }; |
| |
| /** |
| * Function: createCustomHandles |
| * |
| * Returns an array of custom handles. This implementation returns null. |
| */ |
| mxEdgeHandler.prototype.createCustomHandles = function() |
| { |
| return null; |
| }; |
| |
| /** |
| * Function: isVirtualBendsEnabled |
| * |
| * Returns true if virtual bends should be added. This returns true if |
| * <virtualBendsEnabled> is true and the current style allows and |
| * renders custom waypoints. |
| */ |
| mxEdgeHandler.prototype.isVirtualBendsEnabled = function(evt) |
| { |
| return this.virtualBendsEnabled && (this.state.style[mxConstants.STYLE_EDGE] == null || |
| this.state.style[mxConstants.STYLE_EDGE] == mxConstants.NONE || |
| this.state.style[mxConstants.STYLE_NOEDGESTYLE] == 1) && |
| mxUtils.getValue(this.state.style, mxConstants.STYLE_SHAPE, null) != 'arrow'; |
| }; |
| |
| /** |
| * Function: isAddPointEvent |
| * |
| * Returns true if the given event is a trigger to add a new point. This |
| * implementation returns true if shift is pressed. |
| */ |
| mxEdgeHandler.prototype.isAddPointEvent = function(evt) |
| { |
| return mxEvent.isShiftDown(evt); |
| }; |
| |
| /** |
| * Function: isRemovePointEvent |
| * |
| * Returns true if the given event is a trigger to remove a point. This |
| * implementation returns true if shift is pressed. |
| */ |
| mxEdgeHandler.prototype.isRemovePointEvent = function(evt) |
| { |
| return mxEvent.isShiftDown(evt); |
| }; |
| |
| /** |
| * Function: getSelectionPoints |
| * |
| * Returns the list of points that defines the selection stroke. |
| */ |
| mxEdgeHandler.prototype.getSelectionPoints = function(state) |
| { |
| return state.absolutePoints; |
| }; |
| |
| /** |
| * Function: createSelectionShape |
| * |
| * Creates the shape used to draw the selection border. |
| */ |
| mxEdgeHandler.prototype.createParentHighlightShape = function(bounds) |
| { |
| var shape = new mxRectangleShape(bounds, null, this.getSelectionColor()); |
| shape.strokewidth = this.getSelectionStrokeWidth(); |
| shape.isDashed = this.isSelectionDashed(); |
| |
| return shape; |
| }; |
| |
| /** |
| * Function: createSelectionShape |
| * |
| * Creates the shape used to draw the selection border. |
| */ |
| mxEdgeHandler.prototype.createSelectionShape = function(points) |
| { |
| var shape = new this.state.shape.constructor(); |
| shape.outline = true; |
| shape.apply(this.state); |
| |
| shape.isDashed = this.isSelectionDashed(); |
| shape.stroke = this.getSelectionColor(); |
| shape.isShadow = false; |
| |
| return shape; |
| }; |
| |
| /** |
| * Function: getSelectionColor |
| * |
| * Returns <mxConstants.EDGE_SELECTION_COLOR>. |
| */ |
| mxEdgeHandler.prototype.getSelectionColor = function() |
| { |
| return mxConstants.EDGE_SELECTION_COLOR; |
| }; |
| |
| /** |
| * Function: getSelectionStrokeWidth |
| * |
| * Returns <mxConstants.EDGE_SELECTION_STROKEWIDTH>. |
| */ |
| mxEdgeHandler.prototype.getSelectionStrokeWidth = function() |
| { |
| return mxConstants.EDGE_SELECTION_STROKEWIDTH; |
| }; |
| |
| /** |
| * Function: isSelectionDashed |
| * |
| * Returns <mxConstants.EDGE_SELECTION_DASHED>. |
| */ |
| mxEdgeHandler.prototype.isSelectionDashed = function() |
| { |
| return mxConstants.EDGE_SELECTION_DASHED; |
| }; |
| |
| /** |
| * Function: isConnectableCell |
| * |
| * Returns true if the given cell is connectable. This is a hook to |
| * disable floating connections. This implementation returns true. |
| */ |
| mxEdgeHandler.prototype.isConnectableCell = function(cell) |
| { |
| return true; |
| }; |
| |
| /** |
| * Function: getCellAt |
| * |
| * Creates and returns the <mxCellMarker> used in <marker>. |
| */ |
| mxEdgeHandler.prototype.getCellAt = function(x, y) |
| { |
| return (!this.outlineConnect) ? this.graph.getCellAt(x, y) : null; |
| }; |
| |
| /** |
| * Function: createMarker |
| * |
| * Creates and returns the <mxCellMarker> used in <marker>. |
| */ |
| mxEdgeHandler.prototype.createMarker = function() |
| { |
| var marker = new mxCellMarker(this.graph); |
| var self = this; // closure |
| |
| // Only returns edges if they are connectable and never returns |
| // the edge that is currently being modified |
| marker.getCell = function(me) |
| { |
| var cell = mxCellMarker.prototype.getCell.apply(this, arguments); |
| |
| // Checks for cell at preview point (with grid) |
| if ((cell == self.state.cell || cell == null) && self.currentPoint != null) |
| { |
| cell = self.graph.getCellAt(self.currentPoint.x, self.currentPoint.y); |
| } |
| |
| // Uses connectable parent vertex if one exists |
| if (cell != null && !this.graph.isCellConnectable(cell)) |
| { |
| var parent = this.graph.getModel().getParent(cell); |
| |
| if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) |
| { |
| cell = parent; |
| } |
| } |
| |
| var model = self.graph.getModel(); |
| |
| if ((this.graph.isSwimlane(cell) && self.currentPoint != null && |
| this.graph.hitsSwimlaneContent(cell, self.currentPoint.x, self.currentPoint.y)) || |
| (!self.isConnectableCell(cell)) || (cell == self.state.cell || |
| (cell != null && !self.graph.connectableEdges && model.isEdge(cell))) || |
| model.isAncestor(self.state.cell, cell)) |
| { |
| cell = null; |
| } |
| |
| if (!this.graph.isCellConnectable(cell)) |
| { |
| cell = null; |
| } |
| |
| return cell; |
| }; |
| |
| // Sets the highlight color according to validateConnection |
| marker.isValidState = function(state) |
| { |
| var model = self.graph.getModel(); |
| var other = self.graph.view.getTerminalPort(state, |
| self.graph.view.getState(model.getTerminal(self.state.cell, |
| !self.isSource)), !self.isSource); |
| var otherCell = (other != null) ? other.cell : null; |
| var source = (self.isSource) ? state.cell : otherCell; |
| var target = (self.isSource) ? otherCell : state.cell; |
| |
| // Updates the error message of the handler |
| self.error = self.validateConnection(source, target); |
| |
| return self.error == null; |
| }; |
| |
| return marker; |
| }; |
| |
| /** |
| * Function: validateConnection |
| * |
| * Returns the error message or an empty string if the connection for the |
| * given source, target pair is not valid. Otherwise it returns null. This |
| * implementation uses <mxGraph.getEdgeValidationError>. |
| * |
| * Parameters: |
| * |
| * source - <mxCell> that represents the source terminal. |
| * target - <mxCell> that represents the target terminal. |
| */ |
| mxEdgeHandler.prototype.validateConnection = function(source, target) |
| { |
| return this.graph.getEdgeValidationError(this.state.cell, source, target); |
| }; |
| |
| /** |
| * Function: createBends |
| * |
| * Creates and returns the bends used for modifying the edge. This is |
| * typically an array of <mxRectangleShapes>. |
| */ |
| mxEdgeHandler.prototype.createBends = function() |
| { |
| var cell = this.state.cell; |
| var bends = []; |
| |
| for (var i = 0; i < this.abspoints.length; i++) |
| { |
| if (this.isHandleVisible(i)) |
| { |
| var source = i == 0; |
| var target = i == this.abspoints.length - 1; |
| var terminal = source || target; |
| |
| if (terminal || this.graph.isCellBendable(cell)) |
| { |
| (mxUtils.bind(this, function(index) |
| { |
| var bend = this.createHandleShape(index); |
| this.initBend(bend, mxUtils.bind(this, mxUtils.bind(this, function() |
| { |
| if (this.dblClickRemoveEnabled) |
| { |
| this.removePoint(this.state, index); |
| } |
| }))); |
| |
| if (this.isHandleEnabled(i)) |
| { |
| bend.setCursor((terminal) ? mxConstants.CURSOR_TERMINAL_HANDLE : mxConstants.CURSOR_BEND_HANDLE); |
| } |
| |
| bends.push(bend); |
| |
| if (!terminal) |
| { |
| this.points.push(new mxPoint(0,0)); |
| bend.node.style.visibility = 'hidden'; |
| } |
| }))(i); |
| } |
| } |
| } |
| |
| return bends; |
| }; |
| |
| /** |
| * Function: createVirtualBends |
| * |
| * Creates and returns the bends used for modifying the edge. This is |
| * typically an array of <mxRectangleShapes>. |
| */ |
| mxEdgeHandler.prototype.createVirtualBends = function() |
| { |
| var cell = this.state.cell; |
| var last = this.abspoints[0]; |
| var bends = []; |
| |
| if (this.graph.isCellBendable(cell)) |
| { |
| for (var i = 1; i < this.abspoints.length; i++) |
| { |
| (mxUtils.bind(this, function(bend) |
| { |
| this.initBend(bend); |
| bend.setCursor(mxConstants.CURSOR_VIRTUAL_BEND_HANDLE); |
| bends.push(bend); |
| }))(this.createHandleShape()); |
| } |
| } |
| |
| return bends; |
| }; |
| |
| /** |
| * Function: isHandleEnabled |
| * |
| * Creates the shape used to display the given bend. |
| */ |
| mxEdgeHandler.prototype.isHandleEnabled = function(index) |
| { |
| return true; |
| }; |
| |
| /** |
| * Function: isHandleVisible |
| * |
| * Returns true if the handle at the given index is visible. |
| */ |
| mxEdgeHandler.prototype.isHandleVisible = function(index) |
| { |
| var source = this.state.getVisibleTerminalState(true); |
| var target = this.state.getVisibleTerminalState(false); |
| var geo = this.graph.getCellGeometry(this.state.cell); |
| var edgeStyle = (geo != null) ? this.graph.view.getEdgeStyle(this.state, geo.points, source, target) : null; |
| |
| return edgeStyle != mxEdgeStyle.EntityRelation || index == 0 || index == this.abspoints.length - 1; |
| }; |
| |
| /** |
| * Function: createHandleShape |
| * |
| * Creates the shape used to display the given bend. Note that the index may be |
| * null for special cases, such as when called from |
| * <mxElbowEdgeHandler.createVirtualBend>. Only images and rectangles should be |
| * returned if support for HTML labels with not foreign objects is required. |
| * Index if null for virtual handles. |
| */ |
| mxEdgeHandler.prototype.createHandleShape = function(index) |
| { |
| if (this.handleImage != null) |
| { |
| var shape = new mxImageShape(new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src); |
| |
| // Allows HTML rendering of the images |
| shape.preserveImageAspect = false; |
| |
| return shape; |
| } |
| else |
| { |
| var s = mxConstants.HANDLE_SIZE; |
| |
| if (this.preferHtml) |
| { |
| s -= 1; |
| } |
| |
| return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); |
| } |
| }; |
| |
| /** |
| * Function: createLabelHandleShape |
| * |
| * Creates the shape used to display the the label handle. |
| */ |
| mxEdgeHandler.prototype.createLabelHandleShape = function() |
| { |
| if (this.labelHandleImage != null) |
| { |
| var shape = new mxImageShape(new mxRectangle(0, 0, this.labelHandleImage.width, this.labelHandleImage.height), this.labelHandleImage.src); |
| |
| // Allows HTML rendering of the images |
| shape.preserveImageAspect = false; |
| |
| return shape; |
| } |
| else |
| { |
| var s = mxConstants.LABEL_HANDLE_SIZE; |
| return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.LABEL_HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); |
| } |
| }; |
| |
| /** |
| * Function: initBend |
| * |
| * Helper method to initialize the given bend. |
| * |
| * Parameters: |
| * |
| * bend - <mxShape> that represents the bend to be initialized. |
| */ |
| mxEdgeHandler.prototype.initBend = function(bend, dblClick) |
| { |
| if (this.preferHtml) |
| { |
| bend.dialect = mxConstants.DIALECT_STRICTHTML; |
| bend.init(this.graph.container); |
| } |
| else |
| { |
| bend.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? |
| mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; |
| bend.init(this.graph.getView().getOverlayPane()); |
| } |
| |
| mxEvent.redirectMouseEvents(bend.node, this.graph, this.state, |
| null, null, null, dblClick); |
| |
| // Fixes lost event tracking for images in quirks / IE8 standards |
| if (mxClient.IS_QUIRKS || document.documentMode == 8) |
| { |
| mxEvent.addListener(bend.node, 'dragstart', function(evt) |
| { |
| mxEvent.consume(evt); |
| |
| return false; |
| }); |
| } |
| |
| if (mxClient.IS_TOUCH) |
| { |
| bend.node.setAttribute('pointer-events', 'none'); |
| } |
| }; |
| |
| /** |
| * Function: getHandleForEvent |
| * |
| * Returns the index of the handle for the given event. |
| */ |
| mxEdgeHandler.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; |
| var minDistSq = null; |
| var result = null; |
| |
| function checkShape(shape) |
| { |
| if (shape != null && shape.node.style.display != 'none' && shape.node.style.visibility != 'hidden' && |
| (me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit)))) |
| { |
| var dx = me.getGraphX() - shape.bounds.getCenterX(); |
| var dy = me.getGraphY() - shape.bounds.getCenterY(); |
| var tmp = dx * dx + dy * dy; |
| |
| if (minDistSq == null || tmp <= minDistSq) |
| { |
| minDistSq = tmp; |
| |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| 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 (me.isSource(this.state.text) || checkShape(this.labelShape)) |
| { |
| result = mxEvent.LABEL_HANDLE; |
| } |
| |
| if (this.bends != null) |
| { |
| for (var i = 0; i < this.bends.length; i++) |
| { |
| if (checkShape(this.bends[i])) |
| { |
| result = i; |
| } |
| } |
| } |
| |
| if (this.virtualBends != null && this.isAddVirtualBendEvent(me)) |
| { |
| for (var i = 0; i < this.virtualBends.length; i++) |
| { |
| if (checkShape(this.virtualBends[i])) |
| { |
| result = mxEvent.VIRTUAL_HANDLE - i; |
| } |
| } |
| } |
| |
| return result; |
| }; |
| |
| /** |
| * Function: isAddVirtualBendEvent |
| * |
| * Returns true if the given event allows virtual bends to be added. This |
| * implementation returns true. |
| */ |
| mxEdgeHandler.prototype.isAddVirtualBendEvent = function(me) |
| { |
| return true; |
| }; |
| |
| /** |
| * Function: isCustomHandleEvent |
| * |
| * Returns true if the given event allows custom handles to be changed. This |
| * implementation returns true. |
| */ |
| mxEdgeHandler.prototype.isCustomHandleEvent = function(me) |
| { |
| return true; |
| }; |
| |
| /** |
| * Function: mouseDown |
| * |
| * Handles the event by checking if a special element of the handler |
| * was clicked, in which case the index parameter is non-null. The |
| * indices may be one of <LABEL_HANDLE> or the number of the respective |
| * control point. The source and target points are used for reconnecting |
| * the edge. |
| */ |
| mxEdgeHandler.prototype.mouseDown = function(sender, me) |
| { |
| var handle = this.getHandleForEvent(me); |
| |
| if (this.bends != null && this.bends[handle] != null) |
| { |
| var b = this.bends[handle].bounds; |
| this.snapPoint = new mxPoint(b.getCenterX(), b.getCenterY()); |
| } |
| |
| if (this.addEnabled && handle == null && this.isAddPointEvent(me.getEvent())) |
| { |
| this.addPoint(this.state, me.getEvent()); |
| me.consume(); |
| } |
| else if (handle != null && !me.isConsumed() && this.graph.isEnabled()) |
| { |
| if (this.removeEnabled && this.isRemovePointEvent(me.getEvent())) |
| { |
| this.removePoint(this.state, handle); |
| } |
| else if (handle != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(me.getCell())) |
| { |
| if (handle <= mxEvent.VIRTUAL_HANDLE) |
| { |
| mxUtils.setOpacity(this.virtualBends[mxEvent.VIRTUAL_HANDLE - handle].node, 100); |
| } |
| |
| this.start(me.getX(), me.getY(), handle); |
| } |
| |
| me.consume(); |
| } |
| }; |
| |
| /** |
| * Function: start |
| * |
| * Starts the handling of the mouse gesture. |
| */ |
| mxEdgeHandler.prototype.start = function(x, y, index) |
| { |
| this.startX = x; |
| this.startY = y; |
| |
| this.isSource = (this.bends == null) ? false : index == 0; |
| this.isTarget = (this.bends == null) ? false : index == this.bends.length - 1; |
| this.isLabel = index == mxEvent.LABEL_HANDLE; |
| |
| if (this.isSource || this.isTarget) |
| { |
| var cell = this.state.cell; |
| var terminal = this.graph.model.getTerminal(cell, this.isSource); |
| |
| if ((terminal == null && this.graph.isTerminalPointMovable(cell, this.isSource)) || |
| (terminal != null && this.graph.isCellDisconnectable(cell, terminal, this.isSource))) |
| { |
| this.index = index; |
| } |
| } |
| else |
| { |
| this.index = index; |
| } |
| |
| // Hides other custom handles |
| if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE) |
| { |
| if (this.customHandles != null) |
| { |
| for (var i = 0; i < this.customHandles.length; i++) |
| { |
| if (i != mxEvent.CUSTOM_HANDLE - this.index) |
| { |
| this.customHandles[i].setVisible(false); |
| } |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Function: clonePreviewState |
| * |
| * Returns a clone of the current preview state for the given point and terminal. |
| */ |
| mxEdgeHandler.prototype.clonePreviewState = function(point, terminal) |
| { |
| return this.state.clone(); |
| }; |
| |
| /** |
| * Function: getSnapToTerminalTolerance |
| * |
| * Returns the tolerance for the guides. Default value is |
| * gridSize * scale / 2. |
| */ |
| mxEdgeHandler.prototype.getSnapToTerminalTolerance = function() |
| { |
| return this.graph.gridSize * this.graph.view.scale / 2; |
| }; |
| |
| /** |
| * Function: updateHint |
| * |
| * Hook for subclassers do show details while the handler is active. |
| */ |
| mxEdgeHandler.prototype.updateHint = function(me, point) { }; |
| |
| /** |
| * Function: removeHint |
| * |
| * Hooks for subclassers to hide details when the handler gets inactive. |
| */ |
| mxEdgeHandler.prototype.removeHint = function() { }; |
| |
| /** |
| * Function: roundLength |
| * |
| * Hook for rounding the unscaled width or height. This uses Math.round. |
| */ |
| mxEdgeHandler.prototype.roundLength = function(length) |
| { |
| return Math.round(length); |
| }; |
| |
| /** |
| * Function: isSnapToTerminalsEvent |
| * |
| * Returns true if <snapToTerminals> is true and if alt is not pressed. |
| */ |
| mxEdgeHandler.prototype.isSnapToTerminalsEvent = function(me) |
| { |
| return this.snapToTerminals && !mxEvent.isAltDown(me.getEvent()); |
| }; |
| |
| /** |
| * Function: getPointForEvent |
| * |
| * Returns the point for the given event. |
| */ |
| mxEdgeHandler.prototype.getPointForEvent = function(me) |
| { |
| var view = this.graph.getView(); |
| var scale = view.scale; |
| var point = new mxPoint(this.roundLength(me.getGraphX() / scale) * scale, |
| this.roundLength(me.getGraphY() / scale) * scale); |
| |
| var tt = this.getSnapToTerminalTolerance(); |
| var overrideX = false; |
| var overrideY = false; |
| |
| if (tt > 0 && this.isSnapToTerminalsEvent(me)) |
| { |
| function snapToPoint(pt) |
| { |
| if (pt != null) |
| { |
| var x = pt.x; |
| |
| if (Math.abs(point.x - x) < tt) |
| { |
| point.x = x; |
| overrideX = true; |
| } |
| |
| var y = pt.y; |
| |
| if (Math.abs(point.y - y) < tt) |
| { |
| point.y = y; |
| overrideY = true; |
| } |
| } |
| } |
| |
| // Temporary function |
| function snapToTerminal(terminal) |
| { |
| if (terminal != null) |
| { |
| snapToPoint.call(this, new mxPoint(view.getRoutingCenterX(terminal), |
| view.getRoutingCenterY(terminal))); |
| } |
| }; |
| |
| snapToTerminal.call(this, this.state.getVisibleTerminalState(true)); |
| snapToTerminal.call(this, this.state.getVisibleTerminalState(false)); |
| |
| if (this.state.absolutePoints != null) |
| { |
| for (var i = 0; i < this.state.absolutePoints.length; i++) |
| { |
| snapToPoint.call(this, this.state.absolutePoints[i]); |
| } |
| } |
| } |
| |
| if (this.graph.isGridEnabledEvent(me.getEvent())) |
| { |
| var tr = view.translate; |
| |
| if (!overrideX) |
| { |
| point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale; |
| } |
| |
| if (!overrideY) |
| { |
| point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale; |
| } |
| } |
| |
| return point; |
| }; |
| |
| /** |
| * Function: getPreviewTerminalState |
| * |
| * Updates the given preview state taking into account the state of the constraint handler. |
| */ |
| mxEdgeHandler.prototype.getPreviewTerminalState = function(me) |
| { |
| this.constraintHandler.update(me, this.isSource, true, me.isSource(this.marker.highlight.shape) ? null : this.currentPoint); |
| |
| if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) |
| { |
| // Handles special case where grid is large and connection point is at actual point in which |
| // case the outline is not followed as long as we're < gridSize / 2 away from that point |
| if (this.marker.highlight != null && this.marker.highlight.state != null && |
| this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell) |
| { |
| // Direct repaint needed if cell already highlighted |
| if (this.marker.highlight.shape.stroke != 'transparent') |
| { |
| this.marker.highlight.shape.stroke = 'transparent'; |
| this.marker.highlight.repaint(); |
| } |
| } |
| else |
| { |
| this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent'); |
| } |
| |
| var model = this.graph.getModel(); |
| var other = this.graph.view.getTerminalPort(this.state, |
| this.graph.view.getState(model.getTerminal(this.state.cell, |
| !this.isSource)), !this.isSource); |
| var otherCell = (other != null) ? other.cell : null; |
| var source = (this.isSource) ? this.constraintHandler.currentFocus.cell : otherCell; |
| var target = (this.isSource) ? otherCell : this.constraintHandler.currentFocus.cell; |
| |
| // Updates the error message of the handler |
| this.error = this.validateConnection(source, target); |
| var result = null; |
| |
| if (this.error == null) |
| { |
| result = this.constraintHandler.currentFocus; |
| } |
| else |
| { |
| this.constraintHandler.reset(); |
| } |
| |
| return result; |
| } |
| else if (!this.graph.isIgnoreTerminalEvent(me.getEvent())) |
| { |
| this.marker.process(me); |
| |
| return this.marker.getValidState(); |
| } |
| else |
| { |
| this.marker.reset(); |
| |
| return null; |
| } |
| }; |
| |
| /** |
| * Function: getPreviewPoints |
| * |
| * Updates the given preview state taking into account the state of the constraint handler. |
| * |
| * Parameters: |
| * |
| * pt - <mxPoint> that contains the current pointer position. |
| * me - Optional <mxMouseEvent> that contains the current event. |
| */ |
| mxEdgeHandler.prototype.getPreviewPoints = function(pt, me) |
| { |
| var geometry = this.graph.getCellGeometry(this.state.cell); |
| var points = (geometry.points != null) ? geometry.points.slice() : null; |
| var point = new mxPoint(pt.x, pt.y); |
| var result = null; |
| |
| if (!this.isSource && !this.isTarget) |
| { |
| this.convertPoint(point, false); |
| |
| if (points == null) |
| { |
| points = [point]; |
| } |
| else |
| { |
| // Adds point from virtual bend |
| if (this.index <= mxEvent.VIRTUAL_HANDLE) |
| { |
| points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 0, point); |
| } |
| |
| // Removes point if dragged on terminal point |
| if (!this.isSource && !this.isTarget) |
| { |
| for (var i = 0; i < this.bends.length; i++) |
| { |
| if (i != this.index) |
| { |
| var bend = this.bends[i]; |
| |
| if (bend != null && mxUtils.contains(bend.bounds, pt.x, pt.y)) |
| { |
| if (this.index <= mxEvent.VIRTUAL_HANDLE) |
| { |
| points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 1); |
| } |
| else |
| { |
| points.splice(this.index - 1, 1); |
| } |
| |
| result = points; |
| } |
| } |
| } |
| |
| // Removes point if user tries to straighten a segment |
| if (result == null && this.straightRemoveEnabled && (me == null || !mxEvent.isAltDown(me.getEvent()))) |
| { |
| var tol = this.graph.tolerance * this.graph.tolerance; |
| var abs = this.state.absolutePoints.slice(); |
| abs[this.index] = pt; |
| |
| // Handes special case where removing waypoint affects tolerance (flickering) |
| var src = this.state.getVisibleTerminalState(true); |
| |
| if (src != null) |
| { |
| var c = this.graph.getConnectionConstraint(this.state, src, true); |
| |
| // Checks if point is not fixed |
| if (c == null || this.graph.getConnectionPoint(src, c) == null) |
| { |
| abs[0] = new mxPoint(src.view.getRoutingCenterX(src), src.view.getRoutingCenterY(src)); |
| } |
| } |
| |
| var trg = this.state.getVisibleTerminalState(false); |
| |
| if (trg != null) |
| { |
| var c = this.graph.getConnectionConstraint(this.state, trg, false); |
| |
| // Checks if point is not fixed |
| if (c == null || this.graph.getConnectionPoint(trg, c) == null) |
| { |
| abs[abs.length - 1] = new mxPoint(trg.view.getRoutingCenterX(trg), trg.view.getRoutingCenterY(trg)); |
| } |
| } |
| |
| function checkRemove(idx, tmp) |
| { |
| if (idx > 0 && idx < abs.length - 1 && |
| mxUtils.ptSegDistSq(abs[idx - 1].x, abs[idx - 1].y, |
| abs[idx + 1].x, abs[idx + 1].y, tmp.x, tmp.y) < tol) |
| { |
| points.splice(idx - 1, 1); |
| result = points; |
| } |
| }; |
| |
| // LATER: Check if other points can be removed if a segment is made straight |
| checkRemove(this.index, pt); |
| } |
| } |
| |
| // Updates existing point |
| if (result == null && this.index > mxEvent.VIRTUAL_HANDLE) |
| { |
| points[this.index - 1] = point; |
| } |
| } |
| } |
| else if (this.graph.resetEdgesOnConnect) |
| { |
| points = null; |
| } |
| |
| return (result != null) ? result : points; |
| }; |
| |
| /** |
| * Function: isOutlineConnectEvent |
| * |
| * Returns true if <outlineConnect> is true and the source of the event is the outline shape |
| * or shift is pressed. |
| */ |
| mxEdgeHandler.prototype.isOutlineConnectEvent = function(me) |
| { |
| var offset = mxUtils.getOffset(this.graph.container); |
| var evt = me.getEvent(); |
| |
| var clientX = mxEvent.getClientX(evt); |
| var clientY = mxEvent.getClientY(evt); |
| |
| var doc = document.documentElement; |
| var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0); |
| var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); |
| |
| var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left; |
| var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top; |
| |
| return this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) && |
| (me.isSource(this.marker.highlight.shape) || |
| (mxEvent.isAltDown(me.getEvent()) && me.getState() != null) || |
| this.marker.highlight.isHighlightAt(clientX, clientY) || |
| ((gridX != clientX || gridY != clientY) && me.getState() == null && |
| this.marker.highlight.isHighlightAt(gridX, gridY))); |
| }; |
| |
| /** |
| * Function: updatePreviewState |
| * |
| * Updates the given preview state taking into account the state of the constraint handler. |
| */ |
| mxEdgeHandler.prototype.updatePreviewState = function(edge, point, terminalState, me, outline) |
| { |
| // Computes the points for the edge style and terminals |
| var sourceState = (this.isSource) ? terminalState : this.state.getVisibleTerminalState(true); |
| var targetState = (this.isTarget) ? terminalState : this.state.getVisibleTerminalState(false); |
| |
| var sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true); |
| var targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false); |
| |
| var constraint = this.constraintHandler.currentConstraint; |
| |
| if (constraint == null && outline) |
| { |
| if (terminalState != null) |
| { |
| // Handles special case where mouse is on outline away from actual end point |
| // in which case the grid is ignored and mouse point is used instead |
| if (me.isSource(this.marker.highlight.shape)) |
| { |
| point = new mxPoint(me.getGraphX(), me.getGraphY()); |
| } |
| |
| constraint = this.graph.getOutlineConstraint(point, terminalState, me); |
| this.constraintHandler.setFocus(me, terminalState, this.isSource); |
| this.constraintHandler.currentConstraint = constraint; |
| this.constraintHandler.currentPoint = point; |
| } |
| else |
| { |
| constraint = new mxConnectionConstraint(); |
| } |
| } |
| |
| if (this.outlineConnect && this.marker.highlight != null && this.marker.highlight.shape != null) |
| { |
| var s = this.graph.view.scale; |
| |
| if (this.constraintHandler.currentConstraint != null && |
| this.constraintHandler.currentFocus != null) |
| { |
| this.marker.highlight.shape.stroke = (outline) ? mxConstants.OUTLINE_HIGHLIGHT_COLOR : 'transparent'; |
| this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s; |
| this.marker.highlight.repaint(); |
| } |
| else if (this.marker.hasValidState()) |
| { |
| this.marker.highlight.shape.stroke = (this.marker.getValidState() == me.getState()) ? |
| mxConstants.DEFAULT_VALID_COLOR : 'transparent'; |
| this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s; |
| this.marker.highlight.repaint(); |
| } |
| } |
| |
| if (this.isSource) |
| { |
| sourceConstraint = constraint; |
| } |
| else if (this.isTarget) |
| { |
| targetConstraint = constraint; |
| } |
| |
| if (this.isSource || this.isTarget) |
| { |
| if (constraint != null && constraint.point != null) |
| { |
| edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X] = constraint.point.x; |
| edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y] = constraint.point.y; |
| } |
| else |
| { |
| delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X]; |
| delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y]; |
| } |
| } |
| |
| edge.setVisibleTerminalState(sourceState, true); |
| edge.setVisibleTerminalState(targetState, false); |
| |
| if (!this.isSource || sourceState != null) |
| { |
| edge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint); |
| } |
| |
| if (!this.isTarget || targetState != null) |
| { |
| edge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint); |
| } |
| |
| if ((this.isSource || this.isTarget) && terminalState == null) |
| { |
| edge.setAbsoluteTerminalPoint(point, this.isSource); |
| |
| if (this.marker.getMarkedState() == null) |
| { |
| this.error = (this.graph.allowDanglingEdges) ? null : ''; |
| } |
| } |
| |
| edge.view.updatePoints(edge, this.points, sourceState, targetState); |
| edge.view.updateFloatingTerminalPoints(edge, sourceState, targetState); |
| }; |
| |
| /** |
| * Function: mouseMove |
| * |
| * Handles the event by updating the preview. |
| */ |
| mxEdgeHandler.prototype.mouseMove = function(sender, me) |
| { |
| if (this.index != null && this.marker != null) |
| { |
| this.currentPoint = this.getPointForEvent(me); |
| this.error = null; |
| |
| // Uses the current point from the constraint handler if available |
| if (!this.graph.isIgnoreTerminalEvent(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()) && this.snapPoint != null) |
| { |
| if (Math.abs(this.snapPoint.x - this.currentPoint.x) < Math.abs(this.snapPoint.y - this.currentPoint.y)) |
| { |
| this.currentPoint.x = this.snapPoint.x; |
| } |
| else |
| { |
| this.currentPoint.y = this.snapPoint.y; |
| } |
| } |
| |
| if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE) |
| { |
| if (this.customHandles != null) |
| { |
| this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me); |
| } |
| } |
| else if (this.isLabel) |
| { |
| this.label.x = this.currentPoint.x; |
| this.label.y = this.currentPoint.y; |
| } |
| else |
| { |
| this.points = this.getPreviewPoints(this.currentPoint, me); |
| var terminalState = (this.isSource || this.isTarget) ? this.getPreviewTerminalState(me) : null; |
| |
| if (this.constraintHandler.currentConstraint != null && |
| this.constraintHandler.currentFocus != null && |
| this.constraintHandler.currentPoint != null) |
| { |
| this.currentPoint = this.constraintHandler.currentPoint.clone(); |
| } |
| else if (this.outlineConnect) |
| { |
| // Need to check outline before cloning terminal state |
| var outline = (this.isSource || this.isTarget) ? this.isOutlineConnectEvent(me) : false |
| |
| if (outline) |
| { |
| terminalState = this.marker.highlight.state; |
| } |
| else if (terminalState != null && terminalState != me.getState() && this.marker.highlight.shape != null) |
| { |
| this.marker.highlight.shape.stroke = 'transparent'; |
| this.marker.highlight.repaint(); |
| terminalState = null; |
| } |
| } |
| |
| var clone = this.clonePreviewState(this.currentPoint, (terminalState != null) ? terminalState.cell : null); |
| this.updatePreviewState(clone, this.currentPoint, terminalState, me, outline); |
| |
| // Sets the color of the preview to valid or invalid, updates the |
| // points of the preview and redraws |
| var color = (this.error == null) ? this.marker.validColor : this.marker.invalidColor; |
| this.setPreviewColor(color); |
| this.abspoints = clone.absolutePoints; |
| this.active = true; |
| } |
| |
| // This should go before calling isOutlineConnectEvent above. As a workaround |
| // we add an offset of gridSize to the hint to avoid problem with hit detection |
| // in highlight.isHighlightAt (which uses comonentFromPoint) |
| this.updateHint(me, this.currentPoint); |
| this.drawPreview(); |
| mxEvent.consume(me.getEvent()); |
| me.consume(); |
| } |
| // Workaround for disabling the connect highlight when over handle |
| else if (mxClient.IS_IE && this.getHandleForEvent(me) != null) |
| { |
| me.consume(false); |
| } |
| }; |
| |
| /** |
| * Function: mouseUp |
| * |
| * Handles the event to applying the previewed changes on the edge by |
| * using <moveLabel>, <connect> or <changePoints>. |
| */ |
| mxEdgeHandler.prototype.mouseUp = function(sender, me) |
| { |
| // Workaround for wrong event source in Webkit |
| if (this.index != null && this.marker != null) |
| { |
| var edge = this.state.cell; |
| |
| // Ignores event if mouse has not been moved |
| if (me.getX() != this.startX || me.getY() != this.startY) |
| { |
| var clone = !this.graph.isIgnoreTerminalEvent(me.getEvent()) && this.graph.isCloneEvent(me.getEvent()) && |
| this.cloneEnabled && this.graph.isCellsCloneable(); |
| |
| // Displays the reason for not carriying out the change |
| // if there is an error message with non-zero length |
| if (this.error != null) |
| { |
| if (this.error.length > 0) |
| { |
| this.graph.validationAlert(this.error); |
| } |
| } |
| else if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE) |
| { |
| if (this.customHandles != null) |
| { |
| var model = this.graph.getModel(); |
| |
| model.beginUpdate(); |
| try |
| { |
| this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].execute(); |
| } |
| finally |
| { |
| model.endUpdate(); |
| } |
| } |
| } |
| else if (this.isLabel) |
| { |
| this.moveLabel(this.state, this.label.x, this.label.y); |
| } |
| else if (this.isSource || this.isTarget) |
| { |
| var terminal = null; |
| |
| if (this.constraintHandler.currentConstraint != null && |
| this.constraintHandler.currentFocus != null) |
| { |
| terminal = this.constraintHandler.currentFocus.cell; |
| } |
| |
| if (terminal == null && this.marker.hasValidState() && this.marker.highlight != null && |
| this.marker.highlight.shape != null && |
| this.marker.highlight.shape.stroke != 'transparent' && |
| this.marker.highlight.shape.stroke != 'white') |
| { |
| terminal = this.marker.validState.cell; |
| } |
| |
| if (terminal != null) |
| { |
| edge = this.connect(edge, terminal, this.isSource, clone, me); |
| } |
| else if (this.graph.isAllowDanglingEdges()) |
| { |
| var pt = this.abspoints[(this.isSource) ? 0 : this.abspoints.length - 1]; |
| pt.x = this.roundLength(pt.x / this.graph.view.scale - this.graph.view.translate.x); |
| pt.y = this.roundLength(pt.y / this.graph.view.scale - this.graph.view.translate.y); |
| |
| var pstate = this.graph.getView().getState( |
| this.graph.getModel().getParent(edge)); |
| |
| if (pstate != null) |
| { |
| pt.x -= pstate.origin.x; |
| pt.y -= pstate.origin.y; |
| } |
| |
| pt.x -= this.graph.panDx / this.graph.view.scale; |
| pt.y -= this.graph.panDy / this.graph.view.scale; |
| |
| // Destroys and recreates this handler |
| edge = this.changeTerminalPoint(edge, pt, this.isSource, clone); |
| } |
| } |
| else if (this.active) |
| { |
| edge = this.changePoints(edge, this.points, clone); |
| } |
| else |
| { |
| this.graph.getView().invalidate(this.state.cell); |
| this.graph.getView().validate(this.state.cell); |
| } |
| } |
| |
| // Resets the preview color the state of the handler if this |
| // handler has not been recreated |
| if (this.marker != null) |
| { |
| this.reset(); |
| |
| // Updates the selection if the edge has been cloned |
| if (edge != this.state.cell) |
| { |
| this.graph.setSelectionCell(edge); |
| } |
| } |
| |
| me.consume(); |
| } |
| }; |
| |
| /** |
| * Function: reset |
| * |
| * Resets the state of this handler. |
| */ |
| mxEdgeHandler.prototype.reset = function() |
| { |
| this.error = null; |
| this.index = null; |
| this.label = null; |
| this.points = null; |
| this.snapPoint = null; |
| this.active = false; |
| this.isLabel = false; |
| this.isSource = false; |
| this.isTarget = false; |
| |
| 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.marker != null) |
| { |
| this.marker.reset(); |
| } |
| |
| if (this.constraintHandler != null) |
| { |
| this.constraintHandler.reset(); |
| } |
| |
| if (this.customHandles != null) |
| { |
| for (var i = 0; i < this.customHandles.length; i++) |
| { |
| this.customHandles[i].reset(); |
| } |
| } |
| |
| this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR); |
| this.removeHint(); |
| this.redraw(); |
| }; |
| |
| /** |
| * Function: setPreviewColor |
| * |
| * Sets the color of the preview to the given value. |
| */ |
| mxEdgeHandler.prototype.setPreviewColor = function(color) |
| { |
| if (this.shape != null) |
| { |
| this.shape.stroke = color; |
| } |
| }; |
| |
| |
| /** |
| * Function: convertPoint |
| * |
| * Converts the given point in-place from screen to unscaled, untranslated |
| * graph coordinates and applies the grid. Returns the given, modified |
| * point instance. |
| * |
| * Parameters: |
| * |
| * point - <mxPoint> to be converted. |
| * gridEnabled - Boolean that specifies if the grid should be applied. |
| */ |
| mxEdgeHandler.prototype.convertPoint = function(point, gridEnabled) |
| { |
| var scale = this.graph.getView().getScale(); |
| var tr = this.graph.getView().getTranslate(); |
| |
| if (gridEnabled) |
| { |
| point.x = this.graph.snap(point.x); |
| point.y = this.graph.snap(point.y); |
| } |
| |
| point.x = Math.round(point.x / scale - tr.x); |
| point.y = Math.round(point.y / scale - tr.y); |
| |
| var pstate = this.graph.getView().getState( |
| this.graph.getModel().getParent(this.state.cell)); |
| |
| if (pstate != null) |
| { |
| point.x -= pstate.origin.x; |
| point.y -= pstate.origin.y; |
| } |
| |
| return point; |
| }; |
| |
| /** |
| * Function: moveLabel |
| * |
| * Changes the coordinates for the label of the given edge. |
| * |
| * Parameters: |
| * |
| * edge - <mxCell> that represents the edge. |
| * x - Integer that specifies the x-coordinate of the new location. |
| * y - Integer that specifies the y-coordinate of the new location. |
| */ |
| mxEdgeHandler.prototype.moveLabel = function(edgeState, x, y) |
| { |
| var model = this.graph.getModel(); |
| var geometry = model.getGeometry(edgeState.cell); |
| |
| if (geometry != null) |
| { |
| var scale = this.graph.getView().scale; |
| geometry = geometry.clone(); |
| |
| if (geometry.relative) |
| { |
| // Resets the relative location stored inside the geometry |
| var pt = this.graph.getView().getRelativePoint(edgeState, x, y); |
| geometry.x = Math.round(pt.x * 10000) / 10000; |
| geometry.y = Math.round(pt.y); |
| |
| // Resets the offset inside the geometry to find the offset |
| // from the resulting point |
| geometry.offset = new mxPoint(0, 0); |
| var pt = this.graph.view.getPoint(edgeState, geometry); |
| geometry.offset = new mxPoint(Math.round((x - pt.x) / scale), Math.round((y - pt.y) / scale)); |
| } |
| else |
| { |
| var points = edgeState.absolutePoints; |
| var p0 = points[0]; |
| var pe = points[points.length - 1]; |
| |
| if (p0 != null && pe != null) |
| { |
| var cx = p0.x + (pe.x - p0.x) / 2; |
| var cy = p0.y + (pe.y - p0.y) / 2; |
| |
| geometry.offset = new mxPoint(Math.round((x - cx) / scale), Math.round((y - cy) / scale)); |
| geometry.x = 0; |
| geometry.y = 0; |
| } |
| } |
| |
| model.setGeometry(edgeState.cell, geometry); |
| } |
| }; |
| |
| /** |
| * Function: connect |
| * |
| * Changes the terminal or terminal point of the given edge in the graph |
| * model. |
| * |
| * Parameters: |
| * |
| * edge - <mxCell> that represents the edge to be reconnected. |
| * terminal - <mxCell> that represents the new terminal. |
| * isSource - Boolean indicating if the new terminal is the source or |
| * target terminal. |
| * isClone - Boolean indicating if the new connection should be a clone of |
| * the old edge. |
| * me - <mxMouseEvent> that contains the mouse up event. |
| */ |
| mxEdgeHandler.prototype.connect = function(edge, terminal, isSource, isClone, me) |
| { |
| var model = this.graph.getModel(); |
| var parent = model.getParent(edge); |
| |
| model.beginUpdate(); |
| try |
| { |
| // Clones and adds the cell |
| if (isClone) |
| { |
| var clone = this.graph.cloneCells([edge])[0]; |
| model.add(parent, clone, model.getChildCount(parent)); |
| |
| var other = model.getTerminal(edge, !isSource); |
| this.graph.connectCell(clone, other, !isSource); |
| |
| edge = clone; |
| } |
| |
| var constraint = this.constraintHandler.currentConstraint; |
| |
| if (constraint == null) |
| { |
| constraint = new mxConnectionConstraint(); |
| } |
| |
| this.graph.connectCell(edge, terminal, isSource, constraint); |
| } |
| finally |
| { |
| model.endUpdate(); |
| } |
| |
| return edge; |
| }; |
| |
| /** |
| * Function: changeTerminalPoint |
| * |
| * Changes the terminal point of the given edge. |
| */ |
| mxEdgeHandler.prototype.changeTerminalPoint = function(edge, point, isSource, clone) |
| { |
| var model = this.graph.getModel(); |
| |
| model.beginUpdate(); |
| try |
| { |
| if (clone) |
| { |
| var parent = model.getParent(edge); |
| var terminal = model.getTerminal(edge, !isSource); |
| edge = this.graph.cloneCells([edge])[0]; |
| model.add(parent, edge, model.getChildCount(parent)); |
| model.setTerminal(edge, terminal, !isSource); |
| } |
| |
| var geo = model.getGeometry(edge); |
| |
| if (geo != null) |
| { |
| geo = geo.clone(); |
| geo.setTerminalPoint(point, isSource); |
| model.setGeometry(edge, geo); |
| this.graph.connectCell(edge, null, isSource, new mxConnectionConstraint()); |
| } |
| } |
| finally |
| { |
| model.endUpdate(); |
| } |
| |
| return edge; |
| }; |
| |
| /** |
| * Function: changePoints |
| * |
| * Changes the control points of the given edge in the graph model. |
| */ |
| mxEdgeHandler.prototype.changePoints = function(edge, points, clone) |
| { |
| var model = this.graph.getModel(); |
| model.beginUpdate(); |
| try |
| { |
| if (clone) |
| { |
| var parent = model.getParent(edge); |
| var source = model.getTerminal(edge, true); |
| var target = model.getTerminal(edge, false); |
| edge = this.graph.cloneCells([edge])[0]; |
| model.add(parent, edge, model.getChildCount(parent)); |
| model.setTerminal(edge, source, true); |
| model.setTerminal(edge, target, false); |
| } |
| |
| var geo = model.getGeometry(edge); |
| |
| if (geo != null) |
| { |
| geo = geo.clone(); |
| geo.points = points; |
| |
| model.setGeometry(edge, geo); |
| } |
| } |
| finally |
| { |
| model.endUpdate(); |
| } |
| |
| return edge; |
| }; |
| |
| /** |
| * Function: addPoint |
| * |
| * Adds a control point for the given state and event. |
| */ |
| mxEdgeHandler.prototype.addPoint = function(state, evt) |
| { |
| var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt), |
| mxEvent.getClientY(evt)); |
| var gridEnabled = this.graph.isGridEnabledEvent(evt); |
| this.convertPoint(pt, gridEnabled); |
| this.addPointAt(state, pt.x, pt.y); |
| mxEvent.consume(evt); |
| }; |
| |
| /** |
| * Function: addPointAt |
| * |
| * Adds a control point at the given point. |
| */ |
| mxEdgeHandler.prototype.addPointAt = function(state, x, y) |
| { |
| var geo = this.graph.getCellGeometry(state.cell); |
| var pt = new mxPoint(x, y); |
| |
| if (geo != null) |
| { |
| geo = geo.clone(); |
| var t = this.graph.view.translate; |
| var s = this.graph.view.scale; |
| var offset = new mxPoint(t.x * s, t.y * s); |
| |
| var parent = this.graph.model.getParent(this.state.cell); |
| |
| if (this.graph.model.isVertex(parent)) |
| { |
| var pState = this.graph.view.getState(parent); |
| offset = new mxPoint(pState.x, pState.y); |
| } |
| |
| var index = mxUtils.findNearestSegment(state, pt.x * s + offset.x, pt.y * s + offset.y); |
| |
| if (geo.points == null) |
| { |
| geo.points = [pt]; |
| } |
| else |
| { |
| geo.points.splice(index, 0, pt); |
| } |
| |
| this.graph.getModel().setGeometry(state.cell, geo); |
| this.refresh(); |
| this.redraw(); |
| } |
| }; |
| |
| /** |
| * Function: removePoint |
| * |
| * Removes the control point at the given index from the given state. |
| */ |
| mxEdgeHandler.prototype.removePoint = function(state, index) |
| { |
| if (index > 0 && index < this.abspoints.length - 1) |
| { |
| var geo = this.graph.getCellGeometry(this.state.cell); |
| |
| if (geo != null && geo.points != null) |
| { |
| geo = geo.clone(); |
| geo.points.splice(index - 1, 1); |
| this.graph.getModel().setGeometry(state.cell, geo); |
| this.refresh(); |
| this.redraw(); |
| } |
| } |
| }; |
| |
| /** |
| * Function: getHandleFillColor |
| * |
| * Returns the fillcolor for the handle at the given index. |
| */ |
| mxEdgeHandler.prototype.getHandleFillColor = function(index) |
| { |
| var isSource = index == 0; |
| var cell = this.state.cell; |
| var terminal = this.graph.getModel().getTerminal(cell, isSource); |
| var color = mxConstants.HANDLE_FILLCOLOR; |
| |
| if ((terminal != null && !this.graph.isCellDisconnectable(cell, terminal, isSource)) || |
| (terminal == null && !this.graph.isTerminalPointMovable(cell, isSource))) |
| { |
| color = mxConstants.LOCKED_HANDLE_FILLCOLOR; |
| } |
| else if (terminal != null && this.graph.isCellDisconnectable(cell, terminal, isSource)) |
| { |
| color = mxConstants.CONNECT_HANDLE_FILLCOLOR; |
| } |
| |
| return color; |
| }; |
| |
| /** |
| * Function: redraw |
| * |
| * Redraws the preview, and the bends- and label control points. |
| */ |
| mxEdgeHandler.prototype.redraw = function() |
| { |
| this.abspoints = this.state.absolutePoints.slice(); |
| this.redrawHandles(); |
| |
| var g = this.graph.getModel().getGeometry(this.state.cell); |
| var pts = g.points; |
| |
| if (this.bends != null && this.bends.length > 0) |
| { |
| if (pts != null) |
| { |
| if (this.points == null) |
| { |
| this.points = []; |
| } |
| |
| for (var i = 1; i < this.bends.length - 1; i++) |
| { |
| if (this.bends[i] != null && this.abspoints[i] != null) |
| { |
| this.points[i - 1] = pts[i - 1]; |
| } |
| } |
| } |
| } |
| |
| this.drawPreview(); |
| }; |
| |
| /** |
| * Function: redrawHandles |
| * |
| * Redraws the handles. |
| */ |
| mxEdgeHandler.prototype.redrawHandles = function() |
| { |
| var cell = this.state.cell; |
| |
| // Updates the handle for the label position |
| var b = this.labelShape.bounds; |
| this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y); |
| this.labelShape.bounds = new mxRectangle(Math.round(this.label.x - b.width / 2), |
| Math.round(this.label.y - b.height / 2), b.width, b.height); |
| |
| // Shows or hides the label handle depending on the label |
| var lab = this.graph.getLabel(cell); |
| this.labelShape.visible = (lab != null && lab.length > 0 && this.graph.isLabelMovable(cell)); |
| |
| if (this.bends != null && this.bends.length > 0) |
| { |
| var n = this.abspoints.length - 1; |
| |
| var p0 = this.abspoints[0]; |
| var x0 = p0.x; |
| var y0 = p0.y; |
| |
| b = this.bends[0].bounds; |
| this.bends[0].bounds = new mxRectangle(Math.floor(x0 - b.width / 2), |
| Math.floor(y0 - b.height / 2), b.width, b.height); |
| this.bends[0].fill = this.getHandleFillColor(0); |
| this.bends[0].redraw(); |
| |
| if (this.manageLabelHandle) |
| { |
| this.checkLabelHandle(this.bends[0].bounds); |
| } |
| |
| var pe = this.abspoints[n]; |
| var xn = pe.x; |
| var yn = pe.y; |
| |
| var bn = this.bends.length - 1; |
| b = this.bends[bn].bounds; |
| this.bends[bn].bounds = new mxRectangle(Math.floor(xn - b.width / 2), |
| Math.floor(yn - b.height / 2), b.width, b.height); |
| this.bends[bn].fill = this.getHandleFillColor(bn); |
| this.bends[bn].redraw(); |
| |
| if (this.manageLabelHandle) |
| { |
| this.checkLabelHandle(this.bends[bn].bounds); |
| } |
| |
| this.redrawInnerBends(p0, pe); |
| } |
| |
| if (this.abspoints != null && this.virtualBends != null && this.virtualBends.length > 0) |
| { |
| var last = this.abspoints[0]; |
| |
| for (var i = 0; i < this.virtualBends.length; i++) |
| { |
| if (this.virtualBends[i] != null && this.abspoints[i + 1] != null) |
| { |
| var pt = this.abspoints[i + 1]; |
| var b = this.virtualBends[i]; |
| var x = last.x + (pt.x - last.x) / 2; |
| var y = last.y + (pt.y - last.y) / 2; |
| b.bounds = new mxRectangle(Math.floor(x - b.bounds.width / 2), |
| Math.floor(y - b.bounds.height / 2), b.bounds.width, b.bounds.height); |
| b.redraw(); |
| mxUtils.setOpacity(b.node, this.virtualBendOpacity); |
| last = pt; |
| |
| if (this.manageLabelHandle) |
| { |
| this.checkLabelHandle(b.bounds); |
| } |
| } |
| } |
| } |
| |
| if (this.labelShape != null) |
| { |
| this.labelShape.redraw(); |
| } |
| |
| if (this.customHandles != null) |
| { |
| for (var i = 0; i < this.customHandles.length; i++) |
| { |
| this.customHandles[i].redraw(); |
| } |
| } |
| }; |
| |
| /** |
| * Function: hideHandles |
| * |
| * Shortcut to <hideSizers>. |
| */ |
| mxEdgeHandler.prototype.setHandlesVisible = function(visible) |
| { |
| if (this.bends != null) |
| { |
| for (var i = 0; i < this.bends.length; i++) |
| { |
| this.bends[i].node.style.display = (visible) ? '' : 'none'; |
| } |
| } |
| |
| if (this.virtualBends != null) |
| { |
| for (var i = 0; i < this.virtualBends.length; i++) |
| { |
| this.virtualBends[i].node.style.display = (visible) ? '' : 'none'; |
| } |
| } |
| |
| if (this.labelShape != null) |
| { |
| this.labelShape.node.style.display = (visible) ? '' : 'none'; |
| } |
| |
| if (this.customHandles != null) |
| { |
| for (var i = 0; i < this.customHandles.length; i++) |
| { |
| this.customHandles[i].setVisible(visible); |
| } |
| } |
| }; |
| |
| /** |
| * Function: redrawInnerBends |
| * |
| * Updates and redraws the inner bends. |
| * |
| * Parameters: |
| * |
| * p0 - <mxPoint> that represents the location of the first point. |
| * pe - <mxPoint> that represents the location of the last point. |
| */ |
| mxEdgeHandler.prototype.redrawInnerBends = function(p0, pe) |
| { |
| for (var i = 1; i < this.bends.length - 1; i++) |
| { |
| if (this.bends[i] != null) |
| { |
| if (this.abspoints[i] != null) |
| { |
| var x = this.abspoints[i].x; |
| var y = this.abspoints[i].y; |
| |
| var b = this.bends[i].bounds; |
| this.bends[i].node.style.visibility = 'visible'; |
| this.bends[i].bounds = new mxRectangle(Math.round(x - b.width / 2), |
| Math.round(y - b.height / 2), b.width, b.height); |
| |
| if (this.manageLabelHandle) |
| { |
| this.checkLabelHandle(this.bends[i].bounds); |
| } |
| else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(this.bends[i].bounds, this.labelShape.bounds)) |
| { |
| w = mxConstants.HANDLE_SIZE + 3; |
| h = mxConstants.HANDLE_SIZE + 3; |
| this.bends[i].bounds = new mxRectangle(Math.round(x - w / 2), Math.round(y - h / 2), w, h); |
| } |
| |
| this.bends[i].redraw(); |
| } |
| else |
| { |
| this.bends[i].destroy(); |
| this.bends[i] = null; |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Function: checkLabelHandle |
| * |
| * Checks if the label handle intersects the given bounds and moves it if it |
| * intersects. |
| */ |
| mxEdgeHandler.prototype.checkLabelHandle = function(b) |
| { |
| if (this.labelShape != null) |
| { |
| var b2 = this.labelShape.bounds; |
| |
| if (mxUtils.intersects(b, b2)) |
| { |
| if (b.getCenterY() < b2.getCenterY()) |
| { |
| b2.y = b.y + b.height; |
| } |
| else |
| { |
| b2.y = b.y - b2.height; |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Function: drawPreview |
| * |
| * Redraws the preview. |
| */ |
| mxEdgeHandler.prototype.drawPreview = function() |
| { |
| if (this.isLabel) |
| { |
| var b = this.labelShape.bounds; |
| var bounds = new mxRectangle(Math.round(this.label.x - b.width / 2), |
| Math.round(this.label.y - b.height / 2), b.width, b.height); |
| this.labelShape.bounds = bounds; |
| this.labelShape.redraw(); |
| } |
| else if (this.shape != null) |
| { |
| this.shape.apply(this.state); |
| this.shape.points = this.abspoints; |
| this.shape.scale = this.state.view.scale; |
| this.shape.isDashed = this.isSelectionDashed(); |
| this.shape.stroke = this.getSelectionColor(); |
| this.shape.strokewidth = this.getSelectionStrokeWidth() / this.shape.scale / this.shape.scale; |
| this.shape.isShadow = false; |
| this.shape.redraw(); |
| } |
| |
| if (this.parentHighlight != null) |
| { |
| this.parentHighlight.redraw(); |
| } |
| }; |
| |
| /** |
| * Function: refresh |
| * |
| * Refreshes the bends of this handler. |
| */ |
| mxEdgeHandler.prototype.refresh = function() |
| { |
| this.abspoints = this.getSelectionPoints(this.state); |
| this.points = []; |
| |
| if (this.shape != null) |
| { |
| this.shape.points = this.abspoints; |
| } |
| |
| if (this.bends != null) |
| { |
| this.destroyBends(this.bends); |
| this.bends = this.createBends(); |
| } |
| |
| if (this.virtualBends != null) |
| { |
| this.destroyBends(this.virtualBends); |
| this.virtualBends = this.createVirtualBends(); |
| } |
| |
| if (this.customHandles != null) |
| { |
| this.destroyBends(this.customHandles); |
| this.customHandles = this.createCustomHandles(); |
| } |
| |
| // Puts label node on top of bends |
| if (this.labelShape != null && this.labelShape.node != null && this.labelShape.node.parentNode != null) |
| { |
| this.labelShape.node.parentNode.appendChild(this.labelShape.node); |
| } |
| }; |
| |
| /** |
| * Function: destroyBends |
| * |
| * Destroys all elements in <bends>. |
| */ |
| mxEdgeHandler.prototype.destroyBends = function(bends) |
| { |
| if (bends != null) |
| { |
| for (var i = 0; i < bends.length; i++) |
| { |
| if (bends[i] != null) |
| { |
| bends[i].destroy(); |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Function: destroy |
| * |
| * Destroys the handler and all its resources and DOM nodes. This does |
| * normally not need to be called as handlers are destroyed automatically |
| * when the corresponding cell is deselected. |
| */ |
| mxEdgeHandler.prototype.destroy = function() |
| { |
| if (this.escapeHandler != null) |
| { |
| this.state.view.graph.removeListener(this.escapeHandler); |
| this.escapeHandler = null; |
| } |
| |
| if (this.marker != null) |
| { |
| this.marker.destroy(); |
| this.marker = null; |
| } |
| |
| if (this.shape != null) |
| { |
| this.shape.destroy(); |
| this.shape = null; |
| } |
| |
| if (this.parentHighlight != null) |
| { |
| this.parentHighlight.destroy(); |
| this.parentHighlight = null; |
| } |
| |
| if (this.labelShape != null) |
| { |
| this.labelShape.destroy(); |
| this.labelShape = null; |
| } |
| |
| if (this.constraintHandler != null) |
| { |
| this.constraintHandler.destroy(); |
| this.constraintHandler = null; |
| } |
| |
| this.destroyBends(this.virtualBends); |
| this.virtualBends = null; |
| |
| this.destroyBends(this.customHandles); |
| this.customHandles = null; |
| |
| this.destroyBends(this.bends); |
| this.bends = null; |
| |
| this.removeHint(); |
| }; |