sch_test/mxclient/js/handler/mxVertexHandler.js

2252 lines
56 KiB
JavaScript

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