1866 lines
43 KiB
JavaScript
1866 lines
43 KiB
JavaScript
|
/**
|
||
|
* Copyright (c) 2006-2015, JGraph Ltd
|
||
|
* Copyright (c) 2006-2015, Gaudenz Alder
|
||
|
*/
|
||
|
/**
|
||
|
* Class: mxGraphHandler
|
||
|
*
|
||
|
* Graph event handler that handles selection. Individual cells are handled
|
||
|
* separately using <mxVertexHandler> or one of the edge handlers. These
|
||
|
* handlers are created using <mxGraph.createHandler> in
|
||
|
* <mxGraphSelectionModel.cellAdded>.
|
||
|
*
|
||
|
* To avoid the container to scroll a moved cell into view, set
|
||
|
* <scrollAfterMove> to false.
|
||
|
*
|
||
|
* Constructor: mxGraphHandler
|
||
|
*
|
||
|
* Constructs an event handler that creates handles for the
|
||
|
* selection cells.
|
||
|
*
|
||
|
* Parameters:
|
||
|
*
|
||
|
* graph - Reference to the enclosing <mxGraph>.
|
||
|
*/
|
||
|
function mxGraphHandler(graph)
|
||
|
{
|
||
|
this.graph = graph;
|
||
|
this.graph.addMouseListener(this);
|
||
|
|
||
|
// Repaints the handler after autoscroll
|
||
|
this.panHandler = mxUtils.bind(this, function()
|
||
|
{
|
||
|
if (!this.suspended)
|
||
|
{
|
||
|
this.updatePreview();
|
||
|
this.updateHint();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this.graph.addListener(mxEvent.PAN, this.panHandler);
|
||
|
|
||
|
// Handles escape keystrokes
|
||
|
this.escapeHandler = mxUtils.bind(this, function(sender, evt)
|
||
|
{
|
||
|
this.reset();
|
||
|
});
|
||
|
|
||
|
this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
|
||
|
|
||
|
// Updates the preview box for remote changes
|
||
|
this.refreshHandler = mxUtils.bind(this, function(sender, evt)
|
||
|
{
|
||
|
// Merges multiple pending calls
|
||
|
if (this.refreshThread)
|
||
|
{
|
||
|
window.clearTimeout(this.refreshThread);
|
||
|
}
|
||
|
|
||
|
// Waits for the states and handlers to be updated
|
||
|
this.refreshThread = window.setTimeout(mxUtils.bind(this, function()
|
||
|
{
|
||
|
this.refreshThread = null;
|
||
|
|
||
|
if (this.first != null && !this.suspended)
|
||
|
{
|
||
|
// Updates preview with no translate to compute bounding box
|
||
|
var dx = this.currentDx;
|
||
|
var dy = this.currentDy;
|
||
|
this.currentDx = 0;
|
||
|
this.currentDy = 0;
|
||
|
this.updatePreview();
|
||
|
this.bounds = this.graph.getView().getBounds(this.cells);
|
||
|
this.pBounds = this.getPreviewBounds(this.cells);
|
||
|
|
||
|
if (this.pBounds == null && !this.livePreviewUsed)
|
||
|
{
|
||
|
this.reset();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Restores translate and updates preview
|
||
|
this.currentDx = dx;
|
||
|
this.currentDy = dy;
|
||
|
this.updatePreview();
|
||
|
this.updateHint();
|
||
|
|
||
|
if (this.livePreviewUsed)
|
||
|
{
|
||
|
// Forces update to ignore last visible state
|
||
|
this.setHandlesVisibleForCells(
|
||
|
this.graph.selectionCellsHandler.
|
||
|
getHandledSelectionCells(), false, true);
|
||
|
this.updatePreview();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}), 0);
|
||
|
});
|
||
|
|
||
|
this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler);
|
||
|
this.graph.addListener(mxEvent.REFRESH, this.refreshHandler);
|
||
|
|
||
|
this.keyHandler = mxUtils.bind(this, function(e)
|
||
|
{
|
||
|
if (this.graph.container != null && this.graph.container.style.visibility != 'hidden' &&
|
||
|
this.first != null && !this.suspended)
|
||
|
{
|
||
|
var clone = this.graph.isCloneEvent(e) &&
|
||
|
this.graph.isCellsCloneable() &&
|
||
|
this.isCloneEnabled();
|
||
|
|
||
|
if (clone != this.cloning)
|
||
|
{
|
||
|
this.cloning = clone;
|
||
|
this.checkPreview();
|
||
|
this.updatePreview();
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
mxEvent.addListener(document, 'keydown', this.keyHandler);
|
||
|
mxEvent.addListener(document, 'keyup', this.keyHandler);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Variable: graph
|
||
|
*
|
||
|
* Reference to the enclosing <mxGraph>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.graph = null;
|
||
|
|
||
|
/**
|
||
|
* Variable: maxCells
|
||
|
*
|
||
|
* Defines the maximum number of cells to paint subhandles
|
||
|
* for. Default is 50 for Firefox and 20 for IE. Set this
|
||
|
* to 0 if you want an unlimited number of handles to be
|
||
|
* displayed. This is only recommended if the number of
|
||
|
* cells in the graph is limited to a small number, eg.
|
||
|
* 500.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.maxCells = (mxClient.IS_IE) ? 20 : 50;
|
||
|
|
||
|
/**
|
||
|
* Variable: enabled
|
||
|
*
|
||
|
* Specifies if events are handled. Default is true.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.enabled = true;
|
||
|
|
||
|
/**
|
||
|
* Variable: highlightEnabled
|
||
|
*
|
||
|
* Specifies if drop targets under the mouse should be enabled. Default is
|
||
|
* true.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.highlightEnabled = true;
|
||
|
|
||
|
/**
|
||
|
* Variable: cloneEnabled
|
||
|
*
|
||
|
* Specifies if cloning by control-drag is enabled. Default is true.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.cloneEnabled = true;
|
||
|
|
||
|
/**
|
||
|
* Variable: moveEnabled
|
||
|
*
|
||
|
* Specifies if moving is enabled. Default is true.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.moveEnabled = true;
|
||
|
|
||
|
/**
|
||
|
* Variable: guidesEnabled
|
||
|
*
|
||
|
* Specifies if other cells should be used for snapping the right, center or
|
||
|
* left side of the current selection. Default is false.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.guidesEnabled = false;
|
||
|
|
||
|
/**
|
||
|
* Variable: handlesVisible
|
||
|
*
|
||
|
* Whether the handles of the selection are currently visible.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.handlesVisible = true;
|
||
|
|
||
|
/**
|
||
|
* Variable: guide
|
||
|
*
|
||
|
* Holds the <mxGuide> instance that is used for alignment.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.guide = null;
|
||
|
|
||
|
/**
|
||
|
* Variable: currentDx
|
||
|
*
|
||
|
* Stores the x-coordinate of the current mouse move.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.currentDx = null;
|
||
|
|
||
|
/**
|
||
|
* Variable: currentDy
|
||
|
*
|
||
|
* Stores the y-coordinate of the current mouse move.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.currentDy = null;
|
||
|
|
||
|
/**
|
||
|
* Variable: updateCursor
|
||
|
*
|
||
|
* Specifies if a move cursor should be shown if the mouse is over a movable
|
||
|
* cell. Default is true.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.updateCursor = true;
|
||
|
|
||
|
/**
|
||
|
* Variable: selectEnabled
|
||
|
*
|
||
|
* Specifies if selecting is enabled. Default is true.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.selectEnabled = true;
|
||
|
|
||
|
/**
|
||
|
* Variable: removeCellsFromParent
|
||
|
*
|
||
|
* Specifies if cells may be moved out of their parents. Default is true.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.removeCellsFromParent = true;
|
||
|
|
||
|
/**
|
||
|
* Variable: removeEmptyParents
|
||
|
*
|
||
|
* If empty parents should be removed from the model after all child cells
|
||
|
* have been moved out. Default is true.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.removeEmptyParents = false;
|
||
|
|
||
|
/**
|
||
|
* Variable: connectOnDrop
|
||
|
*
|
||
|
* Specifies if drop events are interpreted as new connections if no other
|
||
|
* drop action is defined. Default is false.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.connectOnDrop = false;
|
||
|
|
||
|
/**
|
||
|
* Variable: scrollOnMove
|
||
|
*
|
||
|
* Specifies if the view should be scrolled so that a moved cell is
|
||
|
* visible. Default is true.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.scrollOnMove = true;
|
||
|
|
||
|
/**
|
||
|
* Variable: minimumSize
|
||
|
*
|
||
|
* Specifies the minimum number of pixels for the width and height of a
|
||
|
* selection border. Default is 6.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.minimumSize = 6;
|
||
|
|
||
|
/**
|
||
|
* Variable: previewColor
|
||
|
*
|
||
|
* Specifies the color of the preview shape. Default is black.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.previewColor = 'black';
|
||
|
|
||
|
/**
|
||
|
* Variable: htmlPreview
|
||
|
*
|
||
|
* Specifies if the graph container should be used for preview. If this is used
|
||
|
* then drop target detection relies entirely on <mxGraph.getCellAt> because
|
||
|
* the HTML preview does not "let events through". Default is false.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.htmlPreview = false;
|
||
|
|
||
|
/**
|
||
|
* Variable: shape
|
||
|
*
|
||
|
* Reference to the <mxShape> that represents the preview.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.shape = null;
|
||
|
|
||
|
/**
|
||
|
* Variable: scaleGrid
|
||
|
*
|
||
|
* Specifies if the grid should be scaled. Default is false.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.scaleGrid = false;
|
||
|
|
||
|
/**
|
||
|
* Variable: rotationEnabled
|
||
|
*
|
||
|
* Specifies if the bounding box should allow for rotation. Default is true.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.rotationEnabled = true;
|
||
|
|
||
|
/**
|
||
|
* Variable: maxLivePreview
|
||
|
*
|
||
|
* Maximum number of cells for which live preview should be used. Default is 0
|
||
|
* which means no live preview.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.maxLivePreview = 0;
|
||
|
|
||
|
/**
|
||
|
* Variable: allowLivePreview
|
||
|
*
|
||
|
* If live preview is allowed on this system. Default is true for systems with
|
||
|
* SVG support.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.allowLivePreview = mxClient.IS_SVG;
|
||
|
|
||
|
/**
|
||
|
* Function: isEnabled
|
||
|
*
|
||
|
* Returns <enabled>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.isEnabled = function()
|
||
|
{
|
||
|
return this.enabled;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: setEnabled
|
||
|
*
|
||
|
* Sets <enabled>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.setEnabled = function(value)
|
||
|
{
|
||
|
this.enabled = value;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: isCloneEnabled
|
||
|
*
|
||
|
* Returns <cloneEnabled>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.isCloneEnabled = function()
|
||
|
{
|
||
|
return this.cloneEnabled;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: setCloneEnabled
|
||
|
*
|
||
|
* Sets <cloneEnabled>.
|
||
|
*
|
||
|
* Parameters:
|
||
|
*
|
||
|
* value - Boolean that specifies the new clone enabled state.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.setCloneEnabled = function(value)
|
||
|
{
|
||
|
this.cloneEnabled = value;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: isMoveEnabled
|
||
|
*
|
||
|
* Returns <moveEnabled>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.isMoveEnabled = function()
|
||
|
{
|
||
|
return this.moveEnabled;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: setMoveEnabled
|
||
|
*
|
||
|
* Sets <moveEnabled>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.setMoveEnabled = function(value)
|
||
|
{
|
||
|
this.moveEnabled = value;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: isSelectEnabled
|
||
|
*
|
||
|
* Returns <selectEnabled>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.isSelectEnabled = function()
|
||
|
{
|
||
|
return this.selectEnabled;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: setSelectEnabled
|
||
|
*
|
||
|
* Sets <selectEnabled>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.setSelectEnabled = function(value)
|
||
|
{
|
||
|
this.selectEnabled = value;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: isRemoveCellsFromParent
|
||
|
*
|
||
|
* Returns <removeCellsFromParent>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.isRemoveCellsFromParent = function()
|
||
|
{
|
||
|
return this.removeCellsFromParent;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: setRemoveCellsFromParent
|
||
|
*
|
||
|
* Sets <removeCellsFromParent>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.setRemoveCellsFromParent = function(value)
|
||
|
{
|
||
|
this.removeCellsFromParent = value;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: isPropagateSelectionCell
|
||
|
*
|
||
|
* Returns true if the given cell and parent should propagate
|
||
|
* selection state to the parent.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.isPropagateSelectionCell = function(cell, immediate, me)
|
||
|
{
|
||
|
var parent = this.graph.model.getParent(cell);
|
||
|
|
||
|
if (immediate)
|
||
|
{
|
||
|
var geo = (this.graph.model.isEdge(cell)) ? null :
|
||
|
this.graph.getCellGeometry(cell);
|
||
|
|
||
|
return !this.graph.isSiblingSelected(cell) &&
|
||
|
((geo != null && geo.relative) ||
|
||
|
!this.graph.isSwimlane(parent));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return (!this.graph.isToggleEvent(me.getEvent()) ||
|
||
|
(!this.graph.isSiblingSelected(cell) &&
|
||
|
!this.graph.isCellSelected(cell) &&
|
||
|
(!this.graph.isSwimlane(parent)) ||
|
||
|
this.graph.isCellSelected(parent))) &&
|
||
|
(this.graph.isToggleEvent(me.getEvent()) ||
|
||
|
!this.graph.isCellSelected(parent));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: getInitialCellForEvent
|
||
|
*
|
||
|
* Hook to return initial cell for the given event. This returns
|
||
|
* the topmost cell that is not a swimlane or is selected.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.getInitialCellForEvent = function(me)
|
||
|
{
|
||
|
var state = me.getState();
|
||
|
|
||
|
if ((!this.graph.isToggleEvent(me.getEvent()) || !mxEvent.isAltDown(me.getEvent())) &&
|
||
|
state != null && !this.graph.isCellSelected(state.cell))
|
||
|
{
|
||
|
var model = this.graph.model;
|
||
|
var next = this.graph.view.getState(model.getParent(state.cell));
|
||
|
|
||
|
while (next != null && !this.graph.isCellSelected(next.cell) &&
|
||
|
(model.isVertex(next.cell) || model.isEdge(next.cell)) &&
|
||
|
this.isPropagateSelectionCell(state.cell, true, me))
|
||
|
{
|
||
|
state = next;
|
||
|
next = this.graph.view.getState(this.graph.getModel().getParent(state.cell));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (state != null) ? state.cell : null;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: isDelayedSelection
|
||
|
*
|
||
|
* Returns true if the cell or one of its ancestors is selected.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.isDelayedSelection = function(cell, me)
|
||
|
{
|
||
|
if (!this.graph.isToggleEvent(me.getEvent()) || !mxEvent.isAltDown(me.getEvent()))
|
||
|
{
|
||
|
while (cell != null)
|
||
|
{
|
||
|
if (this.graph.selectionCellsHandler.isHandled(cell))
|
||
|
{
|
||
|
return this.graph.cellEditor.getEditingCell() != cell;
|
||
|
}
|
||
|
|
||
|
cell = this.graph.model.getParent(cell);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return this.graph.isToggleEvent(me.getEvent()) && !mxEvent.isAltDown(me.getEvent());
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: selectDelayed
|
||
|
*
|
||
|
* Implements the delayed selection for the given mouse event.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.selectDelayed = function(me)
|
||
|
{
|
||
|
if (!this.graph.popupMenuHandler.isPopupTrigger(me))
|
||
|
{
|
||
|
var cell = me.getCell();
|
||
|
|
||
|
if (cell == null)
|
||
|
{
|
||
|
cell = this.cell;
|
||
|
}
|
||
|
|
||
|
this.selectCellForEvent(cell, me);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: selectCellForEvent
|
||
|
*
|
||
|
* Selects the given cell for the given <mxMouseEvent>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.selectCellForEvent = function(cell, me)
|
||
|
{
|
||
|
var state = this.graph.view.getState(cell);
|
||
|
|
||
|
if (state != null)
|
||
|
{
|
||
|
if (me.isSource(state.control))
|
||
|
{
|
||
|
this.graph.selectCellForEvent(cell, me.getEvent());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!this.graph.isToggleEvent(me.getEvent()) ||
|
||
|
!mxEvent.isAltDown(me.getEvent()))
|
||
|
{
|
||
|
var model = this.graph.getModel();
|
||
|
var parent = model.getParent(cell);
|
||
|
|
||
|
while (this.graph.view.getState(parent) != null &&
|
||
|
(model.isVertex(parent) || model.isEdge(parent)) &&
|
||
|
this.isPropagateSelectionCell(cell, false, me))
|
||
|
{
|
||
|
cell = parent;
|
||
|
parent = model.getParent(cell);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.graph.selectCellForEvent(cell, me.getEvent());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return cell;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: consumeMouseEvent
|
||
|
*
|
||
|
* Consumes the given mouse event. NOTE: This may be used to enable click
|
||
|
* events for links in labels on iOS as follows as consuming the initial
|
||
|
* touchStart disables firing the subsequent click event on the link.
|
||
|
*
|
||
|
* <code>
|
||
|
* mxGraphHandler.prototype.consumeMouseEvent = function(evtName, me)
|
||
|
* {
|
||
|
* var source = mxEvent.getSource(me.getEvent());
|
||
|
*
|
||
|
* if (!mxEvent.isTouchEvent(me.getEvent()) || source.nodeName != 'A')
|
||
|
* {
|
||
|
* me.consume();
|
||
|
* }
|
||
|
* }
|
||
|
* </code>
|
||
|
*/
|
||
|
mxGraphHandler.prototype.consumeMouseEvent = function(evtName, me)
|
||
|
{
|
||
|
me.consume();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: mouseDown
|
||
|
*
|
||
|
* Handles the event by selecing the given cell and creating a handle for
|
||
|
* it. By consuming the event all subsequent events of the gesture are
|
||
|
* redirected to this handler.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.mouseDown = function(sender, me)
|
||
|
{
|
||
|
if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
|
||
|
me.getState() != null && !mxEvent.isMultiTouchEvent(me.getEvent()))
|
||
|
{
|
||
|
var cell = this.getInitialCellForEvent(me);
|
||
|
this.delayedSelection = this.isDelayedSelection(cell, me);
|
||
|
this.cell = null;
|
||
|
|
||
|
if (this.isSelectEnabled() && !this.delayedSelection)
|
||
|
{
|
||
|
this.graph.selectCellForEvent(cell, me.getEvent());
|
||
|
}
|
||
|
|
||
|
if (this.isMoveEnabled())
|
||
|
{
|
||
|
var model = this.graph.model;
|
||
|
var geo = model.getGeometry(cell);
|
||
|
|
||
|
if (this.graph.isCellMovable(cell) && ((!model.isEdge(cell) || this.graph.getSelectionCount() > 1 ||
|
||
|
(geo.points != null && geo.points.length > 0) || model.getTerminal(cell, true) == null ||
|
||
|
model.getTerminal(cell, false) == null) || this.graph.allowDanglingEdges ||
|
||
|
(this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable())))
|
||
|
{
|
||
|
this.start(cell, me.getX(), me.getY());
|
||
|
}
|
||
|
else if (this.delayedSelection)
|
||
|
{
|
||
|
this.cell = cell;
|
||
|
}
|
||
|
|
||
|
this.cellWasClicked = true;
|
||
|
this.consumeMouseEvent(mxEvent.MOUSE_DOWN, me);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: getGuideStates
|
||
|
*
|
||
|
* Creates an array of cell states which should be used as guides.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.getGuideStates = function()
|
||
|
{
|
||
|
var parent = this.graph.getDefaultParent();
|
||
|
var model = this.graph.getModel();
|
||
|
|
||
|
var filter = mxUtils.bind(this, function(cell)
|
||
|
{
|
||
|
return this.graph.view.getState(cell) != null &&
|
||
|
model.isVertex(cell) &&
|
||
|
model.getGeometry(cell) != null &&
|
||
|
!model.getGeometry(cell).relative;
|
||
|
});
|
||
|
|
||
|
return this.graph.view.getCellStates(model.filterDescendants(filter, parent));
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: getCells
|
||
|
*
|
||
|
* Returns the cells to be modified by this handler. This implementation
|
||
|
* returns all selection cells that are movable, or the given initial cell if
|
||
|
* the given cell is not selected and movable. This handles the case of moving
|
||
|
* unselectable or unselected cells.
|
||
|
*
|
||
|
* Parameters:
|
||
|
*
|
||
|
* initialCell - <mxCell> that triggered this handler.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.getCells = function(initialCell)
|
||
|
{
|
||
|
if (!this.delayedSelection && this.graph.isCellMovable(initialCell))
|
||
|
{
|
||
|
return [initialCell];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return this.graph.getMovableCells(this.graph.getSelectionCells());
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: getPreviewBounds
|
||
|
*
|
||
|
* Returns the <mxRectangle> used as the preview bounds for
|
||
|
* moving the given cells.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.getPreviewBounds = function(cells)
|
||
|
{
|
||
|
var bounds = this.getBoundingBox(cells);
|
||
|
|
||
|
if (bounds != null)
|
||
|
{
|
||
|
// Corrects width and height
|
||
|
bounds.width = Math.max(0, bounds.width - 1);
|
||
|
bounds.height = Math.max(0, bounds.height - 1);
|
||
|
|
||
|
if (bounds.width < this.minimumSize)
|
||
|
{
|
||
|
var dx = this.minimumSize - bounds.width;
|
||
|
bounds.x -= dx / 2;
|
||
|
bounds.width = this.minimumSize;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bounds.x = Math.round(bounds.x);
|
||
|
bounds.width = Math.ceil(bounds.width);
|
||
|
}
|
||
|
|
||
|
var tr = this.graph.view.translate;
|
||
|
var s = this.graph.view.scale;
|
||
|
|
||
|
if (bounds.height < this.minimumSize)
|
||
|
{
|
||
|
var dy = this.minimumSize - bounds.height;
|
||
|
bounds.y -= dy / 2;
|
||
|
bounds.height = this.minimumSize;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bounds.y = Math.round(bounds.y);
|
||
|
bounds.height = Math.ceil(bounds.height);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return bounds;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: getBoundingBox
|
||
|
*
|
||
|
* Returns the union of the <mxCellStates> for the given array of <mxCells>.
|
||
|
* For vertices, this method uses the bounding box of the corresponding shape
|
||
|
* if one exists. The bounding box of the corresponding text label and all
|
||
|
* controls and overlays are ignored. See also: <mxGraphView.getBounds> and
|
||
|
* <mxGraph.getBoundingBox>.
|
||
|
*
|
||
|
* Parameters:
|
||
|
*
|
||
|
* cells - Array of <mxCells> whose bounding box should be returned.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.getBoundingBox = function(cells)
|
||
|
{
|
||
|
var result = null;
|
||
|
|
||
|
if (cells != null && cells.length > 0)
|
||
|
{
|
||
|
var model = this.graph.getModel();
|
||
|
|
||
|
for (var i = 0; i < cells.length; i++)
|
||
|
{
|
||
|
if (model.isVertex(cells[i]) || model.isEdge(cells[i]))
|
||
|
{
|
||
|
var state = this.graph.view.getState(cells[i]);
|
||
|
|
||
|
if (state != null)
|
||
|
{
|
||
|
var bbox = state;
|
||
|
|
||
|
if (model.isVertex(cells[i]) && state.shape != null && state.shape.boundingBox != null)
|
||
|
{
|
||
|
bbox = state.shape.boundingBox;
|
||
|
}
|
||
|
|
||
|
if (result == null)
|
||
|
{
|
||
|
result = mxRectangle.fromRectangle(bbox);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result.add(bbox);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: createPreviewShape
|
||
|
*
|
||
|
* Creates the shape used to draw the preview for the given bounds.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.createPreviewShape = function(bounds)
|
||
|
{
|
||
|
var shape = new mxRectangleShape(bounds, null, this.previewColor);
|
||
|
shape.isDashed = true;
|
||
|
|
||
|
if (this.htmlPreview)
|
||
|
{
|
||
|
shape.dialect = mxConstants.DIALECT_STRICTHTML;
|
||
|
shape.init(this.graph.container);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Makes sure to use either VML or SVG shapes in order to implement
|
||
|
// event-transparency on the background area of the rectangle since
|
||
|
// HTML shapes do not let mouseevents through even when transparent
|
||
|
shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
|
||
|
mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
|
||
|
shape.init(this.graph.getView().getOverlayPane());
|
||
|
shape.pointerEvents = false;
|
||
|
|
||
|
// Workaround for artifacts on iOS
|
||
|
if (mxClient.IS_IOS)
|
||
|
{
|
||
|
shape.getSvgScreenOffset = function()
|
||
|
{
|
||
|
return 0;
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return shape;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: start
|
||
|
*
|
||
|
* Starts the handling of the mouse gesture.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.start = function(cell, x, y, cells)
|
||
|
{
|
||
|
this.cell = cell;
|
||
|
this.first = mxUtils.convertPoint(this.graph.container, x, y);
|
||
|
this.cells = (cells != null) ? cells : this.getCells(this.cell);
|
||
|
this.bounds = this.graph.getView().getBounds(this.cells);
|
||
|
this.pBounds = this.getPreviewBounds(this.cells);
|
||
|
this.allCells = new mxDictionary();
|
||
|
this.cloning = false;
|
||
|
this.cellCount = 0;
|
||
|
|
||
|
for (var i = 0; i < this.cells.length; i++)
|
||
|
{
|
||
|
this.cellCount += this.addStates(this.cells[i], this.allCells);
|
||
|
}
|
||
|
|
||
|
if (this.guidesEnabled)
|
||
|
{
|
||
|
this.guide = new mxGuide(this.graph, this.getGuideStates());
|
||
|
var parent = this.graph.model.getParent(cell);
|
||
|
var ignore = this.graph.model.getChildCount(parent) < 2;
|
||
|
|
||
|
// Uses connected states as guides
|
||
|
var connected = new mxDictionary();
|
||
|
var opps = this.graph.getOpposites(this.graph.getEdges(this.cell), this.cell);
|
||
|
|
||
|
for (var i = 0; i < opps.length; i++)
|
||
|
{
|
||
|
var state = this.graph.view.getState(opps[i]);
|
||
|
|
||
|
if (state != null && !connected.get(state))
|
||
|
{
|
||
|
connected.put(state, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.guide.isStateIgnored = mxUtils.bind(this, function(state)
|
||
|
{
|
||
|
var p = this.graph.model.getParent(state.cell);
|
||
|
|
||
|
return state.cell != null && ((!this.cloning &&
|
||
|
this.isCellMoving(state.cell)) ||
|
||
|
(state.cell != (this.target || parent) && !ignore &&
|
||
|
!connected.get(state) &&
|
||
|
(this.target == null || this.graph.model.getChildCount(
|
||
|
this.target) >= 2) && p != (this.target || parent)));
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: addStates
|
||
|
*
|
||
|
* Adds the states for the given cell recursively to the given dictionary.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.addStates = function(cell, dict)
|
||
|
{
|
||
|
var state = this.graph.view.getState(cell);
|
||
|
var count = 0;
|
||
|
|
||
|
if (state != null && dict.get(cell) == null)
|
||
|
{
|
||
|
dict.put(cell, state);
|
||
|
count++;
|
||
|
|
||
|
var childCount = this.graph.model.getChildCount(cell);
|
||
|
|
||
|
for (var i = 0; i < childCount; i++)
|
||
|
{
|
||
|
count += this.addStates(this.graph.model.getChildAt(cell, i), dict);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: isCellMoving
|
||
|
*
|
||
|
* Returns true if the given cell is currently being moved.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.isCellMoving = function(cell)
|
||
|
{
|
||
|
return this.allCells.get(cell) != null;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: useGuidesForEvent
|
||
|
*
|
||
|
* Returns true if the guides should be used for the given <mxMouseEvent>.
|
||
|
* This implementation returns <mxGuide.isEnabledForEvent>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.useGuidesForEvent = function(me)
|
||
|
{
|
||
|
return (this.guide != null) ? this.guide.isEnabledForEvent(me.getEvent()) &&
|
||
|
!this.graph.isConstrainedEvent(me.getEvent()) : true;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Function: snap
|
||
|
*
|
||
|
* Snaps the given vector to the grid and returns the given mxPoint instance.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.snap = function(vector)
|
||
|
{
|
||
|
var scale = (this.scaleGrid) ? this.graph.view.scale : 1;
|
||
|
|
||
|
vector.x = this.graph.snap(vector.x / scale) * scale;
|
||
|
vector.y = this.graph.snap(vector.y / scale) * scale;
|
||
|
|
||
|
return vector;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: getDelta
|
||
|
*
|
||
|
* Returns an <mxPoint> that represents the vector for moving the cells
|
||
|
* for the given <mxMouseEvent>.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.getDelta = function(me)
|
||
|
{
|
||
|
var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
|
||
|
|
||
|
return new mxPoint(point.x - this.first.x - this.graph.panDx,
|
||
|
point.y - this.first.y - this.graph.panDy);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: updateHint
|
||
|
*
|
||
|
* Hook for subclassers do show details while the handler is active.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.updateHint = function(me) { };
|
||
|
|
||
|
/**
|
||
|
* Function: removeHint
|
||
|
*
|
||
|
* Hooks for subclassers to hide details when the handler gets inactive.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.removeHint = function() { };
|
||
|
|
||
|
/**
|
||
|
* Function: roundLength
|
||
|
*
|
||
|
* Hook for rounding the unscaled vector. Allows for half steps in the raster so
|
||
|
* numbers coming in should be rounded if no half steps are allowed (ie for non
|
||
|
* aligned standard moving where pixel steps should be preferred).
|
||
|
*/
|
||
|
mxGraphHandler.prototype.roundLength = function(length)
|
||
|
{
|
||
|
return Math.round(length * 100) / 100;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: isValidDropTarget
|
||
|
*
|
||
|
* Returns true if the given cell is a valid drop target.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.isValidDropTarget = function(target, me)
|
||
|
{
|
||
|
return this.graph.model.getParent(this.cell) != target;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: checkPreview
|
||
|
*
|
||
|
* Updates the preview if cloning state has changed.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.checkPreview = function()
|
||
|
{
|
||
|
if (this.livePreviewActive && this.cloning)
|
||
|
{
|
||
|
this.resetLivePreview();
|
||
|
this.livePreviewActive = false;
|
||
|
}
|
||
|
else if (this.maxLivePreview >= this.cellCount && !this.livePreviewActive && this.allowLivePreview)
|
||
|
{
|
||
|
if (!this.cloning || !this.livePreviewActive)
|
||
|
{
|
||
|
this.livePreviewActive = true;
|
||
|
this.livePreviewUsed = true;
|
||
|
}
|
||
|
}
|
||
|
else if (!this.livePreviewUsed && this.shape == null)
|
||
|
{
|
||
|
this.shape = this.createPreviewShape(this.bounds);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: mouseMove
|
||
|
*
|
||
|
* Handles the event by highlighting possible drop targets and updating the
|
||
|
* preview.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.mouseMove = function(sender, me)
|
||
|
{
|
||
|
var graph = this.graph;
|
||
|
|
||
|
if (!me.isConsumed() && graph.isMouseDown && this.cell != null &&
|
||
|
this.first != null && this.bounds != null && !this.suspended)
|
||
|
{
|
||
|
// Stops moving if a multi touch event is received
|
||
|
if (mxEvent.isMultiTouchEvent(me.getEvent()))
|
||
|
{
|
||
|
this.reset();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var delta = this.getDelta(me);
|
||
|
var tol = graph.tolerance;
|
||
|
|
||
|
if (this.shape != null || this.livePreviewActive || Math.abs(delta.x) > tol || Math.abs(delta.y) > tol)
|
||
|
{
|
||
|
// Highlight is used for highlighting drop targets
|
||
|
if (this.highlight == null)
|
||
|
{
|
||
|
this.highlight = new mxCellHighlight(this.graph,
|
||
|
mxConstants.DROP_TARGET_COLOR, 3);
|
||
|
}
|
||
|
|
||
|
var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
|
||
|
var gridEnabled = graph.isGridEnabledEvent(me.getEvent());
|
||
|
var cell = me.getCell();
|
||
|
var hideGuide = true;
|
||
|
var target = null;
|
||
|
this.cloning = clone;
|
||
|
|
||
|
if (graph.isDropEnabled() && this.highlightEnabled)
|
||
|
{
|
||
|
// Contains a call to getCellAt to find the cell under the mouse
|
||
|
target = graph.getDropTarget(this.cells, me.getEvent(), cell, clone);
|
||
|
}
|
||
|
|
||
|
var state = graph.getView().getState(target);
|
||
|
var highlight = false;
|
||
|
|
||
|
if (state != null && (clone || this.isValidDropTarget(target, me)))
|
||
|
{
|
||
|
if (this.target != target)
|
||
|
{
|
||
|
this.target = target;
|
||
|
this.setHighlightColor(mxConstants.DROP_TARGET_COLOR);
|
||
|
}
|
||
|
|
||
|
highlight = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.target = null;
|
||
|
|
||
|
if (this.connectOnDrop && cell != null && this.cells.length == 1 &&
|
||
|
graph.getModel().isVertex(cell) && graph.isCellConnectable(cell))
|
||
|
{
|
||
|
state = graph.getView().getState(cell);
|
||
|
|
||
|
if (state != null)
|
||
|
{
|
||
|
var error = graph.getEdgeValidationError(null, this.cell, cell);
|
||
|
var color = (error == null) ?
|
||
|
mxConstants.VALID_COLOR :
|
||
|
mxConstants.INVALID_CONNECT_TARGET_COLOR;
|
||
|
this.setHighlightColor(color);
|
||
|
highlight = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (state != null && highlight)
|
||
|
{
|
||
|
this.highlight.highlight(state);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.highlight.hide();
|
||
|
}
|
||
|
|
||
|
if (this.guide != null && this.useGuidesForEvent(me))
|
||
|
{
|
||
|
delta = this.guide.move(this.bounds, delta, gridEnabled, clone);
|
||
|
hideGuide = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
delta = this.graph.snapDelta(delta, this.bounds, !gridEnabled, false, false);
|
||
|
}
|
||
|
|
||
|
if (this.guide != null && hideGuide)
|
||
|
{
|
||
|
this.guide.hide();
|
||
|
}
|
||
|
|
||
|
// Constrained movement if shift key is pressed
|
||
|
if (graph.isConstrainedEvent(me.getEvent()))
|
||
|
{
|
||
|
if (Math.abs(delta.x) > Math.abs(delta.y))
|
||
|
{
|
||
|
delta.y = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
delta.x = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.checkPreview();
|
||
|
|
||
|
if (this.currentDx != delta.x || this.currentDy != delta.y)
|
||
|
{
|
||
|
this.currentDx = delta.x;
|
||
|
this.currentDy = delta.y;
|
||
|
this.updatePreview();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.updateHint(me);
|
||
|
this.consumeMouseEvent(mxEvent.MOUSE_MOVE, me);
|
||
|
|
||
|
// Cancels the bubbling of events to the container so
|
||
|
// that the droptarget is not reset due to an mouseMove
|
||
|
// fired on the container with no associated state.
|
||
|
mxEvent.consume(me.getEvent());
|
||
|
}
|
||
|
else if ((this.isMoveEnabled() || this.isCloneEnabled()) && this.updateCursor && !me.isConsumed() &&
|
||
|
(me.getState() != null || me.sourceState != null) && !graph.isMouseDown)
|
||
|
{
|
||
|
var cursor = graph.getCursorForMouseEvent(me);
|
||
|
|
||
|
if (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell()))
|
||
|
{
|
||
|
if (graph.getModel().isEdge(me.getCell()))
|
||
|
{
|
||
|
cursor = mxConstants.CURSOR_MOVABLE_EDGE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cursor = mxConstants.CURSOR_MOVABLE_VERTEX;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Sets the cursor on the original source state under the mouse
|
||
|
// instead of the event source state which can be the parent
|
||
|
if (cursor != null && me.sourceState != null)
|
||
|
{
|
||
|
me.sourceState.setCursor(cursor);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: updatePreview
|
||
|
*
|
||
|
* Updates the bounds of the preview shape.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.updatePreview = function(remote)
|
||
|
{
|
||
|
if (this.livePreviewUsed && !remote)
|
||
|
{
|
||
|
if (this.cells != null)
|
||
|
{
|
||
|
this.setHandlesVisibleForCells(
|
||
|
this.graph.selectionCellsHandler.
|
||
|
getHandledSelectionCells(), false);
|
||
|
this.updateLivePreview(this.currentDx, this.currentDy);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.updatePreviewShape();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: updatePreviewShape
|
||
|
*
|
||
|
* Updates the bounds of the preview shape.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.updatePreviewShape = function()
|
||
|
{
|
||
|
if (this.shape != null && this.pBounds != null)
|
||
|
{
|
||
|
this.shape.bounds = new mxRectangle(Math.round(this.pBounds.x + this.currentDx),
|
||
|
Math.round(this.pBounds.y + this.currentDy), this.pBounds.width, this.pBounds.height);
|
||
|
this.shape.redraw();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: updateLivePreview
|
||
|
*
|
||
|
* Updates the bounds of the preview shape.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.updateLivePreview = function(dx, dy)
|
||
|
{
|
||
|
if (!this.suspended)
|
||
|
{
|
||
|
var states = [];
|
||
|
|
||
|
if (this.allCells != null)
|
||
|
{
|
||
|
this.allCells.visit(mxUtils.bind(this, function(key, state)
|
||
|
{
|
||
|
var realState = this.graph.view.getState(state.cell);
|
||
|
|
||
|
// Checks if cell was removed or replaced
|
||
|
if (realState != state)
|
||
|
{
|
||
|
state.destroy();
|
||
|
|
||
|
if (realState != null)
|
||
|
{
|
||
|
this.allCells.put(state.cell, realState);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.allCells.remove(state.cell);
|
||
|
}
|
||
|
|
||
|
state = realState;
|
||
|
}
|
||
|
|
||
|
if (state != null)
|
||
|
{
|
||
|
// Saves current state
|
||
|
var tempState = state.clone();
|
||
|
states.push([state, tempState]);
|
||
|
|
||
|
// Makes transparent for events to detect drop targets
|
||
|
if (state.shape != null)
|
||
|
{
|
||
|
if (state.shape.originalPointerEvents == null)
|
||
|
{
|
||
|
state.shape.originalPointerEvents = state.shape.pointerEvents;
|
||
|
}
|
||
|
|
||
|
state.shape.pointerEvents = false;
|
||
|
|
||
|
if (state.text != null)
|
||
|
{
|
||
|
if (state.text.originalPointerEvents == null)
|
||
|
{
|
||
|
state.text.originalPointerEvents = state.text.pointerEvents;
|
||
|
}
|
||
|
|
||
|
state.text.pointerEvents = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Temporarily changes position
|
||
|
if (this.graph.model.isVertex(state.cell))
|
||
|
{
|
||
|
state.x += dx;
|
||
|
state.y += dy;
|
||
|
|
||
|
// Draws the live preview
|
||
|
if (!this.cloning)
|
||
|
{
|
||
|
state.view.graph.cellRenderer.redraw(state, true);
|
||
|
|
||
|
// Forces redraw of connected edges after all states
|
||
|
// have been updated but avoids update of state
|
||
|
state.view.invalidate(state.cell);
|
||
|
state.invalid = false;
|
||
|
|
||
|
// Hides folding icon
|
||
|
if (state.control != null && state.control.node != null)
|
||
|
{
|
||
|
state.control.node.style.visibility = 'hidden';
|
||
|
}
|
||
|
}
|
||
|
// Clone live preview may use text bounds
|
||
|
else if (state.text != null)
|
||
|
{
|
||
|
state.text.updateBoundingBox();
|
||
|
|
||
|
// Fixes preview box for edge labels
|
||
|
if (state.text.boundingBox != null)
|
||
|
{
|
||
|
state.text.boundingBox.x += dx;
|
||
|
state.text.boundingBox.y += dy;
|
||
|
}
|
||
|
|
||
|
if (state.text.unrotatedBoundingBox != null)
|
||
|
{
|
||
|
state.text.unrotatedBoundingBox.x += dx;
|
||
|
state.text.unrotatedBoundingBox.y += dy;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
// Resets the handler if everything was removed
|
||
|
if (states.length == 0)
|
||
|
{
|
||
|
this.reset();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Redraws connected edges
|
||
|
var s = this.graph.view.scale;
|
||
|
|
||
|
for (var i = 0; i < states.length; i++)
|
||
|
{
|
||
|
var state = states[i][0];
|
||
|
|
||
|
if (this.graph.model.isEdge(state.cell))
|
||
|
{
|
||
|
var geometry = this.graph.getCellGeometry(state.cell);
|
||
|
var points = [];
|
||
|
|
||
|
if (geometry != null && geometry.points != null)
|
||
|
{
|
||
|
for (var j = 0; j < geometry.points.length; j++)
|
||
|
{
|
||
|
if (geometry.points[j] != null)
|
||
|
{
|
||
|
points.push(new mxPoint(
|
||
|
geometry.points[j].x + dx / s,
|
||
|
geometry.points[j].y + dy / s));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var source = state.visibleSourceState;
|
||
|
var target = state.visibleTargetState;
|
||
|
var pts = states[i][1].absolutePoints;
|
||
|
|
||
|
if (source == null || !this.isCellMoving(source.cell))
|
||
|
{
|
||
|
var pt0 = pts[0];
|
||
|
state.setAbsoluteTerminalPoint(new mxPoint(pt0.x + dx, pt0.y + dy), true);
|
||
|
source = null;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
state.view.updateFixedTerminalPoint(state, source, true,
|
||
|
this.graph.getConnectionConstraint(state, source, true));
|
||
|
}
|
||
|
|
||
|
if (target == null || !this.isCellMoving(target.cell))
|
||
|
{
|
||
|
var ptn = pts[pts.length - 1];
|
||
|
state.setAbsoluteTerminalPoint(new mxPoint(ptn.x + dx, ptn.y + dy), false);
|
||
|
target = null;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
state.view.updateFixedTerminalPoint(state, target, false,
|
||
|
this.graph.getConnectionConstraint(state, target, false));
|
||
|
}
|
||
|
|
||
|
state.view.updatePoints(state, points, source, target);
|
||
|
state.view.updateFloatingTerminalPoints(state, source, target);
|
||
|
state.view.updateEdgeLabelOffset(state);
|
||
|
state.invalid = false;
|
||
|
|
||
|
// Draws the live preview but avoids update of state
|
||
|
if (!this.cloning)
|
||
|
{
|
||
|
state.view.graph.cellRenderer.redraw(state, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.graph.view.validate();
|
||
|
this.redrawHandles(states);
|
||
|
this.resetPreviewStates(states);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: redrawHandles
|
||
|
*
|
||
|
* Redraws the preview shape for the given states array.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.redrawHandles = function(states)
|
||
|
{
|
||
|
for (var i = 0; i < states.length; i++)
|
||
|
{
|
||
|
var handler = this.graph.selectionCellsHandler.getHandler(states[i][0].cell);
|
||
|
|
||
|
if (handler != null)
|
||
|
{
|
||
|
handler.redraw(true);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: resetPreviewStates
|
||
|
*
|
||
|
* Resets the given preview states array.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.resetPreviewStates = function(states)
|
||
|
{
|
||
|
for (var i = 0; i < states.length; i++)
|
||
|
{
|
||
|
states[i][0].setState(states[i][1]);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: suspend
|
||
|
*
|
||
|
* Suspends the livew preview.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.suspend = function()
|
||
|
{
|
||
|
if (!this.suspended)
|
||
|
{
|
||
|
if (this.livePreviewUsed)
|
||
|
{
|
||
|
this.updateLivePreview(0, 0);
|
||
|
}
|
||
|
|
||
|
if (this.shape != null)
|
||
|
{
|
||
|
this.shape.node.style.visibility = 'hidden';
|
||
|
}
|
||
|
|
||
|
if (this.guide != null)
|
||
|
{
|
||
|
this.guide.setVisible(false);
|
||
|
}
|
||
|
|
||
|
this.suspended = true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: resume
|
||
|
*
|
||
|
* Suspends the livew preview.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.resume = function()
|
||
|
{
|
||
|
if (this.suspended)
|
||
|
{
|
||
|
this.suspended = null;
|
||
|
|
||
|
if (this.livePreviewUsed)
|
||
|
{
|
||
|
this.livePreviewActive = true;
|
||
|
}
|
||
|
|
||
|
if (this.shape != null)
|
||
|
{
|
||
|
this.shape.node.style.visibility = 'visible';
|
||
|
}
|
||
|
|
||
|
if (this.guide != null)
|
||
|
{
|
||
|
this.guide.setVisible(true);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: resetLivePreview
|
||
|
*
|
||
|
* Resets the livew preview.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.resetLivePreview = function()
|
||
|
{
|
||
|
if (this.allCells != null)
|
||
|
{
|
||
|
this.allCells.visit(mxUtils.bind(this, function(key, state)
|
||
|
{
|
||
|
// Restores event handling
|
||
|
if (state.shape != null && state.shape.originalPointerEvents != null)
|
||
|
{
|
||
|
state.shape.pointerEvents = state.shape.originalPointerEvents;
|
||
|
state.shape.originalPointerEvents = null;
|
||
|
|
||
|
// Forces repaint even if not moved to update pointer events
|
||
|
state.shape.bounds = null;
|
||
|
|
||
|
if (state.text != null)
|
||
|
{
|
||
|
state.text.pointerEvents = state.text.originalPointerEvents;
|
||
|
state.text.originalPointerEvents = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Shows folding icon
|
||
|
if (state.control != null && state.control.node != null &&
|
||
|
state.control.node.style.visibility == 'hidden')
|
||
|
{
|
||
|
state.control.node.style.visibility = '';
|
||
|
}
|
||
|
|
||
|
// Fixes preview box for edge labels
|
||
|
if (!this.cloning)
|
||
|
{
|
||
|
if (state.text != null)
|
||
|
{
|
||
|
state.text.updateBoundingBox();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Forces repaint of connected edges
|
||
|
state.view.invalidate(state.cell);
|
||
|
}));
|
||
|
|
||
|
// Repaints all invalid states
|
||
|
this.graph.view.validate();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: setHandlesVisibleForCells
|
||
|
*
|
||
|
* Sets wether the handles attached to the given cells are visible.
|
||
|
*
|
||
|
* Parameters:
|
||
|
*
|
||
|
* cells - Array of <mxCells>.
|
||
|
* visible - Boolean that specifies if the handles should be visible.
|
||
|
* force - Forces an update of the handler regardless of the last used value.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.setHandlesVisibleForCells = function(cells, visible, force)
|
||
|
{
|
||
|
if (force || this.handlesVisible != visible)
|
||
|
{
|
||
|
this.handlesVisible = visible;
|
||
|
|
||
|
for (var i = 0; i < cells.length; i++)
|
||
|
{
|
||
|
var handler = this.graph.selectionCellsHandler.getHandler(cells[i]);
|
||
|
|
||
|
if (handler != null)
|
||
|
{
|
||
|
handler.setHandlesVisible(visible);
|
||
|
|
||
|
if (visible)
|
||
|
{
|
||
|
handler.redraw();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: setHighlightColor
|
||
|
*
|
||
|
* Sets the color of the rectangle used to highlight drop targets.
|
||
|
*
|
||
|
* Parameters:
|
||
|
*
|
||
|
* color - String that represents the new highlight color.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.setHighlightColor = function(color)
|
||
|
{
|
||
|
if (this.highlight != null)
|
||
|
{
|
||
|
this.highlight.setHighlightColor(color);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: mouseUp
|
||
|
*
|
||
|
* Handles the event by applying the changes to the selection cells.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.mouseUp = function(sender, me)
|
||
|
{
|
||
|
if (!me.isConsumed())
|
||
|
{
|
||
|
if (this.livePreviewUsed)
|
||
|
{
|
||
|
this.resetLivePreview();
|
||
|
}
|
||
|
|
||
|
if (this.cell != null && this.first != null && (this.shape != null || this.livePreviewUsed) &&
|
||
|
this.currentDx != null && this.currentDy != null)
|
||
|
{
|
||
|
var graph = this.graph;
|
||
|
var cell = me.getCell();
|
||
|
|
||
|
if (this.connectOnDrop && this.target == null && cell != null && graph.getModel().isVertex(cell) &&
|
||
|
graph.isCellConnectable(cell) && graph.isEdgeValid(null, this.cell, cell))
|
||
|
{
|
||
|
graph.connectionHandler.connect(this.cell, cell, me.getEvent());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
|
||
|
var scale = graph.getView().scale;
|
||
|
var dx = this.roundLength(this.currentDx / scale);
|
||
|
var dy = this.roundLength(this.currentDy / scale);
|
||
|
var target = this.target;
|
||
|
|
||
|
if (graph.isSplitEnabled() && graph.isSplitTarget(target, this.cells, me.getEvent()))
|
||
|
{
|
||
|
graph.splitEdge(target, this.cells, null, dx, dy,
|
||
|
me.getGraphX(), me.getGraphY());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (this.isSelectEnabled() && this.delayedSelection && this.cell != null)
|
||
|
{
|
||
|
this.selectDelayed(me);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Consumes the event if a cell was initially clicked
|
||
|
if (this.cellWasClicked)
|
||
|
{
|
||
|
this.consumeMouseEvent(mxEvent.MOUSE_UP, me);
|
||
|
}
|
||
|
|
||
|
this.reset();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: reset
|
||
|
*
|
||
|
* Resets the state of this handler.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.reset = function()
|
||
|
{
|
||
|
if (this.livePreviewUsed)
|
||
|
{
|
||
|
this.resetLivePreview();
|
||
|
this.setHandlesVisibleForCells(
|
||
|
this.graph.selectionCellsHandler.
|
||
|
getHandledSelectionCells(), true);
|
||
|
}
|
||
|
|
||
|
this.destroyShapes();
|
||
|
this.removeHint();
|
||
|
|
||
|
this.delayedSelection = false;
|
||
|
this.livePreviewActive = null;
|
||
|
this.livePreviewUsed = null;
|
||
|
this.cellWasClicked = false;
|
||
|
this.suspended = null;
|
||
|
this.currentDx = null;
|
||
|
this.currentDy = null;
|
||
|
this.cellCount = null;
|
||
|
this.cloning = false;
|
||
|
this.allCells = null;
|
||
|
this.pBounds = null;
|
||
|
this.guides = null;
|
||
|
this.target = null;
|
||
|
this.first = null;
|
||
|
this.cells = null;
|
||
|
this.cell = null;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: shouldRemoveCellsFromParent
|
||
|
*
|
||
|
* Returns true if the given cells should be removed from the parent for the specified
|
||
|
* mousereleased event.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt)
|
||
|
{
|
||
|
if (this.graph.getModel().isVertex(parent))
|
||
|
{
|
||
|
var pState = this.graph.getView().getState(parent);
|
||
|
|
||
|
if (pState != null)
|
||
|
{
|
||
|
var pt = mxUtils.convertPoint(this.graph.container,
|
||
|
mxEvent.getClientX(evt), mxEvent.getClientY(evt));
|
||
|
var alpha = mxUtils.toRadians(mxUtils.getValue(pState.style, mxConstants.STYLE_ROTATION) || 0);
|
||
|
|
||
|
if (alpha != 0)
|
||
|
{
|
||
|
var cos = Math.cos(-alpha);
|
||
|
var sin = Math.sin(-alpha);
|
||
|
var cx = new mxPoint(pState.getCenterX(), pState.getCenterY());
|
||
|
pt = mxUtils.getRotatedPoint(pt, cos, sin, cx);
|
||
|
}
|
||
|
|
||
|
return !mxUtils.contains(pState, pt.x, pt.y);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: moveCells
|
||
|
*
|
||
|
* Moves the given cells by the specified amount.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.moveCells = function(cells, dx, dy, clone, target, evt)
|
||
|
{
|
||
|
if (clone)
|
||
|
{
|
||
|
cells = this.graph.getCloneableCells(cells);
|
||
|
}
|
||
|
|
||
|
// Removes cells from parent
|
||
|
var parent = this.graph.getModel().getParent(this.cell);
|
||
|
|
||
|
if (target == null && this.isRemoveCellsFromParent() &&
|
||
|
this.shouldRemoveCellsFromParent(parent, cells, evt))
|
||
|
{
|
||
|
target = this.graph.getDefaultParent();
|
||
|
}
|
||
|
|
||
|
// Cloning into locked cells is not allowed
|
||
|
clone = clone && !this.graph.isCellLocked(target || this.graph.getDefaultParent());
|
||
|
|
||
|
this.graph.getModel().beginUpdate();
|
||
|
try
|
||
|
{
|
||
|
var parents = [];
|
||
|
|
||
|
// Removes parent if all child cells are removed
|
||
|
if (!clone && target != null && this.removeEmptyParents)
|
||
|
{
|
||
|
// Collects all non-selected parents
|
||
|
var dict = new mxDictionary();
|
||
|
|
||
|
for (var i = 0; i < cells.length; i++)
|
||
|
{
|
||
|
dict.put(cells[i], true);
|
||
|
}
|
||
|
|
||
|
// LATER: Recurse up the cell hierarchy
|
||
|
for (var i = 0; i < cells.length; i++)
|
||
|
{
|
||
|
var par = this.graph.model.getParent(cells[i]);
|
||
|
|
||
|
if (par != null && !dict.get(par))
|
||
|
{
|
||
|
dict.put(par, true);
|
||
|
parents.push(par);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Passes all selected cells in order to correctly clone or move into
|
||
|
// the target cell. The method checks for each cell if its movable.
|
||
|
cells = this.graph.moveCells(cells, dx, dy, clone, target, evt);
|
||
|
|
||
|
// Removes parent if all child cells are removed
|
||
|
var temp = [];
|
||
|
|
||
|
for (var i = 0; i < parents.length; i++)
|
||
|
{
|
||
|
if (this.shouldRemoveParent(parents[i]))
|
||
|
{
|
||
|
temp.push(parents[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.graph.removeCells(temp, false);
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
this.graph.getModel().endUpdate();
|
||
|
}
|
||
|
|
||
|
// Selects the new cells if cells have been cloned
|
||
|
if (clone)
|
||
|
{
|
||
|
this.graph.setSelectionCells(cells);
|
||
|
}
|
||
|
|
||
|
if (this.isSelectEnabled() && this.scrollOnMove)
|
||
|
{
|
||
|
this.graph.scrollCellToVisible(cells[0]);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: shouldRemoveParent
|
||
|
*
|
||
|
* Returns true if the given parent should be removed after removal of child cells.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.shouldRemoveParent = function(parent)
|
||
|
{
|
||
|
var state = this.graph.view.getState(parent);
|
||
|
|
||
|
return state != null && (this.graph.model.isEdge(state.cell) || this.graph.model.isVertex(state.cell)) &&
|
||
|
this.graph.isCellDeletable(state.cell) && this.graph.model.getChildCount(state.cell) == 0 &&
|
||
|
this.graph.isTransparentState(state);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: destroyShapes
|
||
|
*
|
||
|
* Destroy the preview and highlight shapes.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.destroyShapes = function()
|
||
|
{
|
||
|
// Destroys the preview dashed rectangle
|
||
|
if (this.shape != null)
|
||
|
{
|
||
|
this.shape.destroy();
|
||
|
this.shape = null;
|
||
|
}
|
||
|
|
||
|
if (this.guide != null)
|
||
|
{
|
||
|
this.guide.destroy();
|
||
|
this.guide = null;
|
||
|
}
|
||
|
|
||
|
// Destroys the drop target highlight
|
||
|
if (this.highlight != null)
|
||
|
{
|
||
|
this.highlight.destroy();
|
||
|
this.highlight = null;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Function: destroy
|
||
|
*
|
||
|
* Destroys the handler and all its resources and DOM nodes.
|
||
|
*/
|
||
|
mxGraphHandler.prototype.destroy = function()
|
||
|
{
|
||
|
this.graph.removeMouseListener(this);
|
||
|
this.graph.removeListener(this.panHandler);
|
||
|
|
||
|
if (this.escapeHandler != null)
|
||
|
{
|
||
|
this.graph.removeListener(this.escapeHandler);
|
||
|
this.escapeHandler = null;
|
||
|
}
|
||
|
|
||
|
if (this.refreshHandler != null)
|
||
|
{
|
||
|
this.graph.getModel().removeListener(this.refreshHandler);
|
||
|
this.graph.removeListener(this.refreshHandler);
|
||
|
this.refreshHandler = null;
|
||
|
}
|
||
|
|
||
|
mxEvent.removeListener(document, 'keydown', this.keyHandler);
|
||
|
mxEvent.removeListener(document, 'keyup', this.keyHandler);
|
||
|
|
||
|
this.destroyShapes();
|
||
|
this.removeHint();
|
||
|
};
|