2545 lines
63 KiB
JavaScript
2545 lines
63 KiB
JavaScript
|
/**
|
||
|
* 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 && state.shape != null)
|
||
|
{
|
||
|
this.state = state;
|
||
|
this.init();
|
||
|
|
||
|
// Handles escape keystrokes
|
||
|
this.escapeHandler = mxUtils.bind(this, function(sender, evt)
|
||
|
{
|
||
|
var dirty = this.index != null;
|
||
|
this.reset();
|
||
|
|
||
|
if (dirty)
|
||
|
{
|
||
|
this.graph.cellRenderer.redraw(this.state, false, state.view.isRendering());
|
||
|
}
|
||
|
});
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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.updateParentHighlight();
|
||
|
this.redraw();
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Function: isParentHighlightVisible
|
||
|
*
|
||
|
* Returns true if the parent highlight should be visible. This implementation
|
||
|
* always returns true.
|
||
|
*/
|
||
|
mxEdgeHandler.prototype.isParentHighlightVisible = mxVertexHandler.prototype.isParentHighlightVisible;
|
||
|
|
||
|
/**
|
||
|
* Function: updateParentHighlight
|
||
|
*
|
||
|
* Updates the highlight of the parent if <parentHighlightEnabled> is true.
|
||
|
*/
|
||
|
mxEdgeHandler.prototype.updateParentHighlight = mxVertexHandler.prototype.updateParentHighlight;
|
||
|
|
||
|
/**
|
||
|
* 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: isCellEnabled
|
||
|
*
|
||
|
* Returns true if the given cell allows new connections to be created. This implementation
|
||
|
* always returns true.
|
||
|
*/
|
||
|
mxEdgeHandler.prototype.isCellEnabled = function(cell)
|
||
|
{
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* 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: createParentHighlightShape
|
||
|
*
|
||
|
* Creates the shape used to draw the selection border.
|
||
|
*/
|
||
|
mxEdgeHandler.prototype.createParentHighlightShape = function(bounds)
|
||
|
{
|
||
|
var shape = new mxRectangleShape(mxRectangle.fromRectangle(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)
|
||
|
{
|
||
|
var result = null;
|
||
|
|
||
|
if (this.state != null)
|
||
|
{
|
||
|
// 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;
|
||
|
|
||
|
function checkShape(shape)
|
||
|
{
|
||
|
if (shape != null && shape.node != 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;
|
||
|
}
|
||
|
|
||
|
if (this.error != null || (result != null &&
|
||
|
!this.isCellEnabled(result.cell)))
|
||
|
{
|
||
|
this.constraintHandler.reset();
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
else if (!this.graph.isIgnoreTerminalEvent(me.getEvent()))
|
||
|
{
|
||
|
this.marker.process(me);
|
||
|
var state = this.marker.getValidState();
|
||
|
|
||
|
if (state != null && !this.isCellEnabled(state.cell))
|
||
|
{
|
||
|
this.constraintHandler.reset();
|
||
|
this.marker.reset();
|
||
|
}
|
||
|
|
||
|
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.graph.isCellConnectable(me.getCell()) &&
|
||
|
this.marker.getValidState() != me.getState()) ?
|
||
|
'transparent' : mxConstants.DEFAULT_VALID_COLOR;
|
||
|
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);
|
||
|
this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].positionChanged();
|
||
|
|
||
|
if (this.shape != null && this.shape.node != null)
|
||
|
{
|
||
|
this.shape.node.style.display = 'none';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
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.graph.isCellConnectable(me.getCell()) &&
|
||
|
this.marker.highlight.shape != null)
|
||
|
{
|
||
|
this.marker.highlight.shape.stroke = 'transparent';
|
||
|
this.marker.highlight.repaint();
|
||
|
terminalState = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (terminalState != null && !this.isCellEnabled(terminalState.cell))
|
||
|
{
|
||
|
terminalState = null;
|
||
|
this.marker.reset();
|
||
|
}
|
||
|
|
||
|
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.updateHint(me, this.currentPoint);
|
||
|
}
|
||
|
|
||
|
// 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.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)
|
||
|
{
|
||
|
if (this.shape != null && this.shape.node != null)
|
||
|
{
|
||
|
this.shape.node.style.display = '';
|
||
|
}
|
||
|
|
||
|
var edge = this.state.cell;
|
||
|
var index = this.index;
|
||
|
this.index = null;
|
||
|
|
||
|
// 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 (index <= mxEvent.CUSTOM_HANDLE && index > mxEvent.VIRTUAL_HANDLE)
|
||
|
{
|
||
|
if (this.customHandles != null)
|
||
|
{
|
||
|
var model = this.graph.getModel();
|
||
|
|
||
|
model.beginUpdate();
|
||
|
try
|
||
|
{
|
||
|
this.customHandles[mxEvent.CUSTOM_HANDLE - index].execute(me);
|
||
|
|
||
|
if (this.shape != null && this.shape.node != null)
|
||
|
{
|
||
|
this.shape.apply(this.state);
|
||
|
this.shape.redraw();
|
||
|
}
|
||
|
}
|
||
|
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)
|
||
|
{
|
||
|
var model = this.graph.getModel();
|
||
|
var parent = model.getParent(edge);
|
||
|
|
||
|
model.beginUpdate();
|
||
|
try
|
||
|
{
|
||
|
// Clones and adds the cell
|
||
|
if (clone)
|
||
|
{
|
||
|
var geo = model.getGeometry(edge);
|
||
|
var clone = this.graph.cloneCell(edge);
|
||
|
model.add(parent, clone, model.getChildCount(parent));
|
||
|
|
||
|
if (geo != null)
|
||
|
{
|
||
|
geo = geo.clone();
|
||
|
model.setGeometry(clone, geo);
|
||
|
}
|
||
|
|
||
|
var other = model.getTerminal(edge, !this.isSource);
|
||
|
this.graph.connectCell(clone, other, !this.isSource);
|
||
|
|
||
|
edge = clone;
|
||
|
}
|
||
|
|
||
|
edge = this.connect(edge, terminal, this.isSource, clone, me);
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
model.endUpdate();
|
||
|
}
|
||
|
}
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
else if (this.graph.isToggleEvent(me.getEvent()))
|
||
|
{
|
||
|
this.graph.selectCellForEvent(this.state.cell, me.getEvent());
|
||
|
}
|
||
|
|
||
|
// 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()
|
||
|
{
|
||
|
if (this.active)
|
||
|
{
|
||
|
this.refresh();
|
||
|
}
|
||
|
|
||
|
this.error = null;
|
||
|
this.index = null;
|
||
|
this.label = null;
|
||
|
this.points = null;
|
||
|
this.snapPoint = null;
|
||
|
this.isLabel = false;
|
||
|
this.isSource = false;
|
||
|
this.isTarget = false;
|
||
|
this.active = 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
|
||
|
{
|
||
|
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.cloneCell(edge);
|
||
|
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.cloneCell(edge);
|
||
|
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(ignoreHandles)
|
||
|
{
|
||
|
if (this.state != null)
|
||
|
{
|
||
|
this.abspoints = this.state.absolutePoints.slice();
|
||
|
var g = this.graph.getModel().getGeometry(this.state.cell);
|
||
|
|
||
|
if (g != null)
|
||
|
{
|
||
|
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();
|
||
|
|
||
|
if (!ignoreHandles)
|
||
|
{
|
||
|
this.redrawHandles();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* 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++)
|
||
|
{
|
||
|
var temp = this.customHandles[i].shape.node.style.display;
|
||
|
this.customHandles[i].redraw();
|
||
|
this.customHandles[i].shape.node.style.display = temp;
|
||
|
|
||
|
// Hides custom handles during text editing
|
||
|
this.customHandles[i].shape.node.style.visibility =
|
||
|
(this.isCustomHandleVisible(this.customHandles[i])) ?
|
||
|
'' : 'hidden';
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: isCustomHandleVisible
|
||
|
*
|
||
|
* Returns true if the given custom handle is visible.
|
||
|
*/
|
||
|
mxEdgeHandler.prototype.isCustomHandleVisible = function(handle)
|
||
|
{
|
||
|
return !this.graph.isEditing() && this.state.view.graph.getSelectionCount() == 1;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* 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()
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
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);
|
||
|
|
||
|
if (!this.labelShape.bounds.equals(bounds))
|
||
|
{
|
||
|
this.labelShape.bounds = bounds;
|
||
|
this.labelShape.redraw();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this.shape != null && !mxUtils.equalPoints(this.shape.points, this.abspoints))
|
||
|
{
|
||
|
this.shape.apply(this.state);
|
||
|
this.shape.points = this.abspoints.slice();
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
this.updateParentHighlight();
|
||
|
}
|
||
|
catch (e)
|
||
|
{
|
||
|
// ignore
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: refresh
|
||
|
*
|
||
|
* Refreshes the bends of this handler.
|
||
|
*/
|
||
|
mxEdgeHandler.prototype.refresh = function()
|
||
|
{
|
||
|
if (this.state != null)
|
||
|
{
|
||
|
this.abspoints = this.getSelectionPoints(this.state);
|
||
|
this.points = [];
|
||
|
|
||
|
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: isDestroyed
|
||
|
*
|
||
|
* Returns true if <destroy> was called.
|
||
|
*/
|
||
|
mxEdgeHandler.prototype.isDestroyed = function()
|
||
|
{
|
||
|
return this.shape == null;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* 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)
|
||
|
{
|
||
|
var parent = this.graph.model.getParent(this.state.cell);
|
||
|
var pstate = this.graph.view.getState(parent);
|
||
|
|
||
|
if (pstate != null && pstate.parentHighlight == this.parentHighlight)
|
||
|
{
|
||
|
pstate.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();
|
||
|
};
|