summaryrefslogtreecommitdiff
path: root/src/js/view
diff options
context:
space:
mode:
authoradhitya2016-04-11 15:10:54 +0000
committeradhitya2016-04-11 15:10:54 +0000
commit92f3207b50a1caca07df5c5b238212af3358905b (patch)
tree38c92f9649c6f1016d2ef70fa2fd33c86b437cba /src/js/view
parentab5fb6e125d82fdd5818aea3ce370c43c2293ddd (diff)
downloadxcos-on-web-92f3207b50a1caca07df5c5b238212af3358905b.tar.gz
xcos-on-web-92f3207b50a1caca07df5c5b238212af3358905b.tar.bz2
xcos-on-web-92f3207b50a1caca07df5c5b238212af3358905b.zip
Revert last two commits - Keyboard shortcuts are not working
Diffstat (limited to 'src/js/view')
-rw-r--r--src/js/view/mxCellEditor.js522
-rw-r--r--src/js/view/mxCellOverlay.js233
-rw-r--r--src/js/view/mxCellRenderer.js1480
-rw-r--r--src/js/view/mxCellState.js375
-rw-r--r--src/js/view/mxCellStatePreview.js223
-rw-r--r--src/js/view/mxConnectionConstraint.js42
-rw-r--r--src/js/view/mxEdgeStyle.js1302
-rw-r--r--src/js/view/mxGraph.js11176
-rw-r--r--src/js/view/mxGraphSelectionModel.js435
-rw-r--r--src/js/view/mxGraphView.js2545
-rw-r--r--src/js/view/mxLayoutManager.js375
-rw-r--r--src/js/view/mxMultiplicity.js257
-rw-r--r--src/js/view/mxOutline.js649
-rw-r--r--src/js/view/mxPerimeter.js484
-rw-r--r--src/js/view/mxPrintPreview.js801
-rw-r--r--src/js/view/mxSpaceManager.js460
-rw-r--r--src/js/view/mxStyleRegistry.js70
-rw-r--r--src/js/view/mxStylesheet.js266
-rw-r--r--src/js/view/mxSwimlaneManager.js449
-rw-r--r--src/js/view/mxTemporaryCellStates.js105
20 files changed, 22249 insertions, 0 deletions
diff --git a/src/js/view/mxCellEditor.js b/src/js/view/mxCellEditor.js
new file mode 100644
index 0000000..2086cca
--- /dev/null
+++ b/src/js/view/mxCellEditor.js
@@ -0,0 +1,522 @@
+/**
+ * $Id: mxCellEditor.js,v 1.62 2012-12-11 16:59:31 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellEditor
+ *
+ * In-place editor for the graph. To control this editor, use
+ * <mxGraph.invokesStopCellEditing>, <mxGraph.enterStopsCellEditing> and
+ * <mxGraph.escapeEnabled>. If <mxGraph.enterStopsCellEditing> is true then
+ * ctrl-enter or shift-enter can be used to create a linefeed. The F2 and
+ * escape keys can always be used to stop editing. To customize the location
+ * of the textbox in the graph, override <getEditorBounds> as follows:
+ *
+ * (code)
+ * graph.cellEditor.getEditorBounds = function(state)
+ * {
+ * var result = mxCellEditor.prototype.getEditorBounds.apply(this, arguments);
+ *
+ * if (this.graph.getModel().isEdge(state.cell))
+ * {
+ * result.x = state.getCenterX() - result.width / 2;
+ * result.y = state.getCenterY() - result.height / 2;
+ * }
+ *
+ * return result;
+ * };
+ * (end)
+ *
+ * The textarea uses the mxCellEditor CSS class. You can modify this class in
+ * your custom CSS. Note: You should modify the CSS after loading the client
+ * in the page.
+ *
+ * Example:
+ *
+ * To only allow numeric input in the in-place editor, use the following code.
+ *
+ * (code)
+ * var text = graph.cellEditor.textarea;
+ *
+ * mxEvent.addListener(text, 'keydown', function (evt)
+ * {
+ * if (!(evt.keyCode >= 48 && evt.keyCode <= 57) &&
+ * !(evt.keyCode >= 96 && evt.keyCode <= 105))
+ * {
+ * mxEvent.consume(evt);
+ * }
+ * });
+ * (end)
+ *
+ * Initial values:
+ *
+ * To implement an initial value for cells without a label, use the
+ * <emptyLabelText> variable.
+ *
+ * Resize in Chrome:
+ *
+ * Resize of the textarea is disabled by default. If you want to enable
+ * this feature extend <init> and set this.textarea.style.resize = ''.
+ *
+ * Constructor: mxCellEditor
+ *
+ * Constructs a new in-place editor for the specified graph.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxCellEditor(graph)
+{
+ this.graph = graph;
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellEditor.prototype.graph = null;
+
+/**
+ * Variable: textarea
+ *
+ * Holds the input textarea. Note that this may be null before the first
+ * edit. Instantiated in <init>.
+ */
+mxCellEditor.prototype.textarea = null;
+
+/**
+ * Variable: editingCell
+ *
+ * Reference to the <mxCell> that is currently being edited.
+ */
+mxCellEditor.prototype.editingCell = null;
+
+/**
+ * Variable: trigger
+ *
+ * Reference to the event that was used to start editing.
+ */
+mxCellEditor.prototype.trigger = null;
+
+/**
+ * Variable: modified
+ *
+ * Specifies if the label has been modified.
+ */
+mxCellEditor.prototype.modified = false;
+
+/**
+ * Variable: emptyLabelText
+ *
+ * Text to be displayed for empty labels. Default is ''. This can be set
+ * to eg. "[Type Here]" to easier visualize editing of empty labels. The
+ * value is only displayed before the first keystroke and is never used
+ * as the actual editin value.
+ */
+mxCellEditor.prototype.emptyLabelText = '';
+
+/**
+ * Variable: textNode
+ *
+ * Reference to the label DOM node that has been hidden.
+ */
+mxCellEditor.prototype.textNode = '';
+
+/**
+ * Function: init
+ *
+ * Creates the <textarea> and installs the event listeners. The key handler
+ * updates the <modified> state.
+ */
+mxCellEditor.prototype.init = function ()
+{
+ this.textarea = document.createElement('textarea');
+
+ this.textarea.className = 'mxCellEditor';
+ this.textarea.style.position = 'absolute';
+ this.textarea.style.overflow = 'visible';
+
+ this.textarea.setAttribute('cols', '20');
+ this.textarea.setAttribute('rows', '4');
+
+ if (mxClient.IS_GC)
+ {
+ this.textarea.style.resize = 'none';
+ }
+
+ mxEvent.addListener(this.textarea, 'blur', mxUtils.bind(this, function(evt)
+ {
+ this.focusLost();
+ }));
+
+ mxEvent.addListener(this.textarea, 'keydown', mxUtils.bind(this, function(evt)
+ {
+ if (!mxEvent.isConsumed(evt))
+ {
+ if (evt.keyCode == 113 /* F2 */ || (this.graph.isEnterStopsCellEditing() &&
+ evt.keyCode == 13 /* Enter */ && !mxEvent.isControlDown(evt) &&
+ !mxEvent.isShiftDown(evt)))
+ {
+ this.graph.stopEditing(false);
+ mxEvent.consume(evt);
+ }
+ else if (evt.keyCode == 27 /* Escape */)
+ {
+ this.graph.stopEditing(true);
+ mxEvent.consume(evt);
+ }
+ else
+ {
+ // Clears the initial empty label on the first keystroke
+ if (this.clearOnChange)
+ {
+ this.clearOnChange = false;
+ this.textarea.value = '';
+ }
+
+ // Updates the modified flag for storing the value
+ this.setModified(true);
+ }
+ }
+ }));
+};
+
+/**
+ * Function: isModified
+ *
+ * Returns <modified>.
+ */
+mxCellEditor.prototype.isModified = function()
+{
+ return this.modified;
+};
+
+/**
+ * Function: setModified
+ *
+ * Sets <modified> to the specified boolean value.
+ */
+mxCellEditor.prototype.setModified = function(value)
+{
+ this.modified = value;
+};
+
+/**
+ * Function: focusLost
+ *
+ * Called if the textarea has lost focus.
+ */
+mxCellEditor.prototype.focusLost = function()
+{
+ this.stopEditing(!this.graph.isInvokesStopCellEditing());
+};
+
+/**
+ * Function: startEditing
+ *
+ * Starts the editor for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to start editing.
+ * trigger - Optional mouse event that triggered the editor.
+ */
+mxCellEditor.prototype.startEditing = function(cell, trigger)
+{
+ // Lazy instantiates textarea to save memory in IE
+ if (this.textarea == null)
+ {
+ this.init();
+ }
+
+ this.stopEditing(true);
+ var state = this.graph.getView().getState(cell);
+
+ if (state != null)
+ {
+ this.editingCell = cell;
+ this.trigger = trigger;
+ this.textNode = null;
+
+ if (state.text != null && this.isHideLabel(state))
+ {
+ this.textNode = state.text.node;
+ this.textNode.style.visibility = 'hidden';
+ }
+
+ // Configures the style of the in-place editor
+ var scale = this.graph.getView().scale;
+ var size = mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) * scale;
+ var family = mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY);
+ var color = mxUtils.getValue(state.style, mxConstants.STYLE_FONTCOLOR, 'black');
+ var align = (this.graph.model.isEdge(state.cell)) ? mxConstants.ALIGN_LEFT :
+ mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
+ var bold = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+ mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD;
+ var italic = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+ mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC;
+ var uline = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+ mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE;
+
+ this.textarea.style.fontSize = size + 'px';
+ this.textarea.style.fontFamily = family;
+ this.textarea.style.textAlign = align;
+ this.textarea.style.color = color;
+ this.textarea.style.fontWeight = (bold) ? 'bold' : 'normal';
+ this.textarea.style.fontStyle = (italic) ? 'italic' : '';
+ this.textarea.style.textDecoration = (uline) ? 'underline' : '';
+
+ // Specifies the bounds of the editor box
+ var bounds = this.getEditorBounds(state);
+
+ this.textarea.style.left = bounds.x + 'px';
+ this.textarea.style.top = bounds.y + 'px';
+ this.textarea.style.width = bounds.width + 'px';
+ this.textarea.style.height = bounds.height + 'px';
+ this.textarea.style.zIndex = 5;
+
+ var value = this.getInitialValue(state, trigger);
+
+ // Uses an optional text value for empty labels which is cleared
+ // when the first keystroke appears. This makes it easier to see
+ // that a label is being edited even if the label is empty.
+ if (value == null || value.length == 0)
+ {
+ value = this.getEmptyLabelText();
+ this.clearOnChange = true;
+ }
+ else
+ {
+ this.clearOnChange = false;
+ }
+
+ this.setModified(false);
+ this.textarea.value = value;
+ this.graph.container.appendChild(this.textarea);
+
+ if (this.textarea.style.display != 'none')
+ {
+ // FIXME: Doesn't bring up the virtual keyboard on iPad
+ this.textarea.focus();
+ this.textarea.select();
+ }
+ }
+};
+
+/**
+ * Function: stopEditing
+ *
+ * Stops the editor and applies the value if cancel is false.
+ */
+mxCellEditor.prototype.stopEditing = function(cancel)
+{
+ cancel = cancel || false;
+
+ if (this.editingCell != null)
+ {
+ if (this.textNode != null)
+ {
+ this.textNode.style.visibility = 'visible';
+ this.textNode = null;
+ }
+
+ if (!cancel && this.isModified())
+ {
+ this.graph.labelChanged(this.editingCell, this.getCurrentValue(), this.trigger);
+ }
+
+ this.editingCell = null;
+ this.trigger = null;
+ this.textarea.blur();
+ this.textarea.parentNode.removeChild(this.textarea);
+ }
+};
+
+/**
+ * Function: getInitialValue
+ *
+ * Gets the initial editing value for the given cell.
+ */
+mxCellEditor.prototype.getInitialValue = function(state, trigger)
+{
+ return this.graph.getEditingValue(state.cell, trigger);
+};
+
+/**
+ * Function: getCurrentValue
+ *
+ * Returns the current editing value.
+ */
+mxCellEditor.prototype.getCurrentValue = function()
+{
+ return this.textarea.value.replace(/\r/g, '');
+};
+
+/**
+ * Function: isHideLabel
+ *
+ * Returns true if the label should be hidden while the cell is being
+ * edited.
+ */
+mxCellEditor.prototype.isHideLabel = function(state)
+{
+ return true;
+};
+
+/**
+ * Function: getMinimumSize
+ *
+ * Returns the minimum width and height for editing the given state.
+ */
+mxCellEditor.prototype.getMinimumSize = function(state)
+{
+ var scale = this.graph.getView().scale;
+
+ return new mxRectangle(0, 0, (state.text == null) ? 30 : state.text.size * scale + 20,
+ (this.textarea.style.textAlign == 'left') ? 120 : 40);
+};
+
+/**
+ * Function: getEditorBounds
+ *
+ * Returns the <mxRectangle> that defines the bounds of the editor.
+ */
+mxCellEditor.prototype.getEditorBounds = function(state)
+{
+ var isEdge = this.graph.getModel().isEdge(state.cell);
+ var scale = this.graph.getView().scale;
+ var minSize = this.getMinimumSize(state);
+ var minWidth = minSize.width;
+ var minHeight = minSize.height;
+
+ var spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 2) * scale;
+ var spacingTop = (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0)) * scale + spacing;
+ var spacingRight = (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0)) * scale + spacing;
+ var spacingBottom = (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0)) * scale + spacing;
+ var spacingLeft = (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0)) * scale + spacing;
+
+ var result = new mxRectangle(state.x, state.y,
+ Math.max(minWidth, state.width - spacingLeft - spacingRight),
+ Math.max(minHeight, state.height - spacingTop - spacingBottom));
+
+ if (isEdge)
+ {
+ result.x = state.absoluteOffset.x;
+ result.y = state.absoluteOffset.y;
+
+ if (state.text != null && state.text.boundingBox != null)
+ {
+ // Workaround for label containing just spaces in which case
+ // the bounding box location contains negative numbers
+ if (state.text.boundingBox.x > 0)
+ {
+ result.x = state.text.boundingBox.x;
+ }
+
+ if (state.text.boundingBox.y > 0)
+ {
+ result.y = state.text.boundingBox.y;
+ }
+ }
+ }
+ else if (state.text != null && state.text.boundingBox != null)
+ {
+ result.x = Math.min(result.x, state.text.boundingBox.x);
+ result.y = Math.min(result.y, state.text.boundingBox.y);
+ }
+
+ result.x += spacingLeft;
+ result.y += spacingTop;
+
+ if (state.text != null && state.text.boundingBox != null)
+ {
+ if (!isEdge)
+ {
+ result.width = Math.max(result.width, state.text.boundingBox.width);
+ result.height = Math.max(result.height, state.text.boundingBox.height);
+ }
+ else
+ {
+ result.width = Math.max(minWidth, state.text.boundingBox.width);
+ result.height = Math.max(minHeight, state.text.boundingBox.height);
+ }
+ }
+
+ // Applies the horizontal and vertical label positions
+ if (this.graph.getModel().isVertex(state.cell))
+ {
+ var horizontal = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+
+ if (horizontal == mxConstants.ALIGN_LEFT)
+ {
+ result.x -= state.width;
+ }
+ else if (horizontal == mxConstants.ALIGN_RIGHT)
+ {
+ result.x += state.width;
+ }
+
+ var vertical = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+
+ if (vertical == mxConstants.ALIGN_TOP)
+ {
+ result.y -= state.height;
+ }
+ else if (vertical == mxConstants.ALIGN_BOTTOM)
+ {
+ result.y += state.height;
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getEmptyLabelText
+ *
+ * Returns the initial label value to be used of the label of the given
+ * cell is empty. This label is displayed and cleared on the first keystroke.
+ * This implementation returns <emptyLabelText>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which a text for an empty editing box should be
+ * returned.
+ */
+mxCellEditor.prototype.getEmptyLabelText = function (cell)
+{
+ return this.emptyLabelText;
+};
+
+/**
+ * Function: getEditingCell
+ *
+ * Returns the cell that is currently being edited or null if no cell is
+ * being edited.
+ */
+mxCellEditor.prototype.getEditingCell = function ()
+{
+ return this.editingCell;
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the editor and removes all associated resources.
+ */
+mxCellEditor.prototype.destroy = function ()
+{
+ if (this.textarea != null)
+ {
+ mxEvent.release(this.textarea);
+
+ if (this.textarea.parentNode != null)
+ {
+ this.textarea.parentNode.removeChild(this.textarea);
+ }
+
+ this.textarea = null;
+ }
+};
diff --git a/src/js/view/mxCellOverlay.js b/src/js/view/mxCellOverlay.js
new file mode 100644
index 0000000..316e2c4
--- /dev/null
+++ b/src/js/view/mxCellOverlay.js
@@ -0,0 +1,233 @@
+/**
+ * $Id: mxCellOverlay.js,v 1.18 2012-12-06 15:58:44 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellOverlay
+ *
+ * Extends <mxEventSource> to implement a graph overlay, represented by an icon
+ * and a tooltip. Overlays can handle and fire <click> events and are added to
+ * the graph using <mxGraph.addCellOverlay>, and removed using
+ * <mxGraph.removeCellOverlay>, or <mxGraph.removeCellOverlays> to remove all overlays.
+ * The <mxGraph.getCellOverlays> function returns the array of overlays for a given
+ * cell in a graph. If multiple overlays exist for the same cell, then
+ * <getBounds> should be overridden in at least one of the overlays.
+ *
+ * Overlays appear on top of all cells in a special layer. If this is not
+ * desirable, then the image must be rendered as part of the shape or label of
+ * the cell instead.
+ *
+ * Example:
+ *
+ * The following adds a new overlays for a given vertex and selects the cell
+ * if the overlay is clicked.
+ *
+ * (code)
+ * var overlay = new mxCellOverlay(img, html);
+ * graph.addCellOverlay(vertex, overlay);
+ * overlay.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ * var cell = evt.getProperty('cell');
+ * graph.setSelectionCell(cell);
+ * });
+ * (end)
+ *
+ * For cell overlays to be printed use <mxPrintPreview.printOverlays>.
+ *
+ * Event: mxEvent.CLICK
+ *
+ * Fires when the user clicks on the overlay. The <code>event</code> property
+ * contains the corresponding mouse event and the <code>cell</code> property
+ * contains the cell. For touch devices this is fired if the element receives
+ * a touchend event.
+ *
+ * Constructor: mxCellOverlay
+ *
+ * Constructs a new overlay using the given image and tooltip.
+ *
+ * Parameters:
+ *
+ * image - <mxImage> that represents the icon to be displayed.
+ * tooltip - Optional string that specifies the tooltip.
+ * align - Optional horizontal alignment for the overlay. Possible
+ * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>
+ * (default).
+ * verticalAlign - Vertical alignment for the overlay. Possible
+ * values are <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>
+ * (default).
+ */
+function mxCellOverlay(image, tooltip, align, verticalAlign, offset, cursor)
+{
+ this.image = image;
+ this.tooltip = tooltip;
+ this.align = (align != null) ? align : this.align;
+ this.verticalAlign = (verticalAlign != null) ? verticalAlign : this.verticalAlign;
+ this.offset = (offset != null) ? offset : new mxPoint();
+ this.cursor = (cursor != null) ? cursor : 'help';
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxCellOverlay.prototype = new mxEventSource();
+mxCellOverlay.prototype.constructor = mxCellOverlay;
+
+/**
+ * Variable: image
+ *
+ * Holds the <mxImage> to be used as the icon.
+ */
+mxCellOverlay.prototype.image = null;
+
+/**
+ * Variable: tooltip
+ *
+ * Holds the optional string to be used as the tooltip.
+ */
+mxCellOverlay.prototype.tooltip = null;
+
+/**
+ * Variable: align
+ *
+ * Holds the horizontal alignment for the overlay. Default is
+ * <mxConstants.ALIGN_RIGHT>. For edges, the overlay always appears in the
+ * center of the edge.
+ */
+mxCellOverlay.prototype.align = mxConstants.ALIGN_RIGHT;
+
+/**
+ * Variable: verticalAlign
+ *
+ * Holds the vertical alignment for the overlay. Default is
+ * <mxConstants.ALIGN_BOTTOM>. For edges, the overlay always appears in the
+ * center of the edge.
+ */
+mxCellOverlay.prototype.verticalAlign = mxConstants.ALIGN_BOTTOM;
+
+/**
+ * Variable: offset
+ *
+ * Holds the offset as an <mxPoint>. The offset will be scaled according to the
+ * current scale.
+ */
+mxCellOverlay.prototype.offset = null;
+
+/**
+ * Variable: cursor
+ *
+ * Holds the cursor for the overlay. Default is 'help'.
+ */
+mxCellOverlay.prototype.cursor = null;
+
+/**
+ * Variable: defaultOverlap
+ *
+ * Defines the overlapping for the overlay, that is, the proportional distance
+ * from the origin to the point defined by the alignment. Default is 0.5.
+ */
+mxCellOverlay.prototype.defaultOverlap = 0.5;
+
+/**
+ * Function: getBounds
+ *
+ * Returns the bounds of the overlay for the given <mxCellState> as an
+ * <mxRectangle>. This should be overridden when using multiple overlays
+ * per cell so that the overlays do not overlap.
+ *
+ * The following example will place the overlay along an edge (where
+ * x=[-1..1] from the start to the end of the edge and y is the
+ * orthogonal offset in px).
+ *
+ * (code)
+ * overlay.getBounds = function(state)
+ * {
+ * var bounds = mxCellOverlay.prototype.getBounds.apply(this, arguments);
+ *
+ * if (state.view.graph.getModel().isEdge(state.cell))
+ * {
+ * var pt = state.view.getPoint(state, {x: 0, y: 0, relative: true});
+ *
+ * bounds.x = pt.x - bounds.width / 2;
+ * bounds.y = pt.y - bounds.height / 2;
+ * }
+ *
+ * return bounds;
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the current state of the
+ * associated cell.
+ */
+mxCellOverlay.prototype.getBounds = function(state)
+{
+ var isEdge = state.view.graph.getModel().isEdge(state.cell);
+ var s = state.view.scale;
+ var pt = null;
+
+ var w = this.image.width;
+ var h = this.image.height;
+
+ if (isEdge)
+ {
+ var pts = state.absolutePoints;
+
+ if (pts.length % 2 == 1)
+ {
+ pt = pts[Math.floor(pts.length / 2)];
+ }
+ else
+ {
+ var idx = pts.length / 2;
+ var p0 = pts[idx-1];
+ var p1 = pts[idx];
+ pt = new mxPoint(p0.x + (p1.x - p0.x) / 2,
+ p0.y + (p1.y - p0.y) / 2);
+ }
+ }
+ else
+ {
+ pt = new mxPoint();
+
+ if (this.align == mxConstants.ALIGN_LEFT)
+ {
+ pt.x = state.x;
+ }
+ else if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ pt.x = state.x + state.width / 2;
+ }
+ else
+ {
+ pt.x = state.x + state.width;
+ }
+
+ if (this.verticalAlign == mxConstants.ALIGN_TOP)
+ {
+ pt.y = state.y;
+ }
+ else if (this.verticalAlign == mxConstants.ALIGN_MIDDLE)
+ {
+ pt.y = state.y + state.height / 2;
+ }
+ else
+ {
+ pt.y = state.y + state.height;
+ }
+ }
+
+ return new mxRectangle(pt.x - (w * this.defaultOverlap - this.offset.x) * s,
+ pt.y - (h * this.defaultOverlap - this.offset.y) * s, w * s, h * s);
+};
+
+/**
+ * Function: toString
+ *
+ * Returns the textual representation of the overlay to be used as the
+ * tooltip. This implementation returns <tooltip>.
+ */
+mxCellOverlay.prototype.toString = function()
+{
+ return this.tooltip;
+};
diff --git a/src/js/view/mxCellRenderer.js b/src/js/view/mxCellRenderer.js
new file mode 100644
index 0000000..6b506ad
--- /dev/null
+++ b/src/js/view/mxCellRenderer.js
@@ -0,0 +1,1480 @@
+/**
+ * $Id: mxCellRenderer.js,v 1.189 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellRenderer
+ *
+ * Renders cells into a document object model. The <defaultShapes> is a global
+ * map of shapename, constructor pairs that is used in all instances. You can
+ * get a list of all available shape names using the following code.
+ *
+ * In general the cell renderer is in charge of creating, redrawing and
+ * destroying the shape and label associated with a cell state, as well as
+ * some other graphical objects, namely controls and overlays. The shape
+ * hieararchy in the display (ie. the hierarchy in which the DOM nodes
+ * appear in the document) does not reflect the cell hierarchy. The shapes
+ * are a (flat) sequence of shapes and labels inside the draw pane of the
+ * graph view, with some exceptions, namely the HTML labels being placed
+ * directly inside the graph container for certain browsers.
+ *
+ * (code)
+ * mxLog.show();
+ * for (var i in mxCellRenderer.prototype.defaultShapes)
+ * {
+ * mxLog.debug(i);
+ * }
+ * (end)
+ *
+ * Constructor: mxCellRenderer
+ *
+ * Constructs a new cell renderer with the following built-in shapes:
+ * arrow, rectangle, ellipse, rhombus, image, line, label, cylinder,
+ * swimlane, connector, actor and cloud.
+ */
+function mxCellRenderer()
+{
+ this.shapes = mxUtils.clone(this.defaultShapes);
+};
+
+/**
+ * Variable: shapes
+ *
+ * Array that maps from shape names to shape constructors. All entries
+ * in <defaultShapes> are added to this array.
+ */
+mxCellRenderer.prototype.shapes = null;
+
+/**
+ * Variable: defaultEdgeShape
+ *
+ * Defines the default shape for edges. Default is <mxConnector>.
+ */
+mxCellRenderer.prototype.defaultEdgeShape = mxConnector;
+
+/**
+ * Variable: defaultVertexShape
+ *
+ * Defines the default shape for vertices. Default is <mxRectangleShape>.
+ */
+mxCellRenderer.prototype.defaultVertexShape = mxRectangleShape;
+
+/**
+ * Variable: defaultShapes
+ *
+ * Static array that contains the globally registered shapes which are
+ * known to all instances of this class. For adding instance-specific
+ * shapes you should use <registerShape> on the instance. For adding
+ * a shape to this array you can use the following code:
+ *
+ * (code)
+ * mxCellRenderer.prototype.defaultShapes['myshape'] = myShape;
+ * (end)
+ *
+ * Where 'myshape' is the key under which the shape is to be registered
+ * and myShape is the name of the constructor function.
+ */
+mxCellRenderer.prototype.defaultShapes = new Object();
+
+// Adds default shapes into the default shapes array
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ARROW] = mxArrow;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_RECTANGLE] = mxRectangleShape;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ELLIPSE] = mxEllipse;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_DOUBLE_ELLIPSE] = mxDoubleEllipse;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_RHOMBUS] = mxRhombus;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_IMAGE] = mxImageShape;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_LINE] = mxLine;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_LABEL] = mxLabel;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CYLINDER] = mxCylinder;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_SWIMLANE] = mxSwimlane;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CONNECTOR] = mxConnector;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ACTOR] = mxActor;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CLOUD] = mxCloud;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_TRIANGLE] = mxTriangle;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_HEXAGON] = mxHexagon;
+
+/**
+ * Function: registerShape
+ *
+ * Registers the given constructor under the specified key in this instance
+ * of the renderer.
+ *
+ * Example:
+ *
+ * (code)
+ * this.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
+ * (end)
+ *
+ * Parameters:
+ *
+ * key - String representing the shape name.
+ * shape - Constructor of the <mxShape> subclass.
+ */
+mxCellRenderer.prototype.registerShape = function(key, shape)
+{
+ this.shapes[key] = shape;
+};
+
+/**
+ * Function: initialize
+ *
+ * Initializes the display for the given cell state. This is required once
+ * after the cell state has been created. This is invoked in
+ * mxGraphView.createState.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the display should be initialized.
+ * rendering - Optional boolean that specifies if the cell should actually
+ * be initialized for any given DOM node. If this is false then init
+ * will not be called on the shape.
+ */
+mxCellRenderer.prototype.initialize = function(state, rendering)
+{
+ var model = state.view.graph.getModel();
+
+ if (state.view.graph.container != null && state.shape == null &&
+ state.cell != state.view.currentRoot &&
+ (model.isVertex(state.cell) || model.isEdge(state.cell)))
+ {
+ this.createShape(state);
+
+ if (state.shape != null && (rendering == null || rendering))
+ {
+ this.initializeShape(state);
+
+ // Maintains the model order in the DOM
+ if (state.view.graph.ordered || model.isEdge(state.cell))
+ {
+ //state.orderChanged = true;
+ state.invalidOrder = true;
+ }
+ else if (state.view.graph.keepEdgesInForeground && this.firstEdge != null)
+ {
+ if (this.firstEdge.parentNode == state.shape.node.parentNode)
+ {
+ this.insertState(state, this.firstEdge);
+ }
+ else
+ {
+ this.firstEdge = null;
+ }
+ }
+
+ state.shape.scale = state.view.scale;
+
+ this.createCellOverlays(state);
+ this.installListeners(state);
+ }
+ }
+};
+
+/**
+ * Function: initializeShape
+ *
+ * Initializes the shape in the given state by calling its init method with
+ * the correct container.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be initialized.
+ */
+mxCellRenderer.prototype.initializeShape = function(state)
+{
+ state.shape.init(state.view.getDrawPane());
+};
+
+/**
+ * Returns the previous state that has a shape inside the given parent.
+ */
+mxCellRenderer.prototype.getPreviousStateInContainer = function(state, container)
+{
+ var result = null;
+ var graph = state.view.graph;
+ var model = graph.getModel();
+ var child = state.cell;
+ var p = model.getParent(child);
+
+ while (p != null && result == null)
+ {
+ result = this.findPreviousStateInContainer(graph, p, child, container);
+ child = p;
+ p = model.getParent(child);
+ }
+
+ return result;
+};
+
+/**
+ * Returns the previous state that has a shape inside the given parent.
+ */
+mxCellRenderer.prototype.findPreviousStateInContainer = function(graph, cell, stop, container)
+{
+ // Recurse first
+ var result = null;
+ var model = graph.getModel();
+
+ if (stop != null)
+ {
+ var start = cell.getIndex(stop);
+
+ for (var i = start - 1; i >= 0 && result == null; i--)
+ {
+ result = this.findPreviousStateInContainer(graph, model.getChildAt(cell, i), null, container);
+ }
+ }
+ else
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = childCount - 1; i >= 0 && result == null; i--)
+ {
+ result = this.findPreviousStateInContainer(graph, model.getChildAt(cell, i), null, container);
+ }
+ }
+
+ if (result == null)
+ {
+ result = graph.view.getState(cell);
+
+ if (result != null && (result.shape == null || result.shape.node == null ||
+ result.shape.node.parentNode != container))
+ {
+ result = null;
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: order
+ *
+ * Orders the DOM node of the shape for the given state according to the
+ * position of the corresponding cell in the graph model.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose shape's DOM node should be ordered.
+ */
+mxCellRenderer.prototype.order = function(state)
+{
+ var container = state.shape.node.parentNode;
+ var previous = this.getPreviousStateInContainer(state, container);
+ var nextNode = container.firstChild;
+
+ if (previous != null)
+ {
+ nextNode = previous.shape.node;
+
+ if (previous.text != null && previous.text.node != null &&
+ previous.text.node.parentNode == container)
+ {
+ nextNode = previous.text.node;
+ }
+
+ nextNode = nextNode.nextSibling;
+ }
+
+ this.insertState(state, nextNode);
+};
+
+/**
+ * Function: orderEdge
+ *
+ * Orders the DOM node of the shape for the given edge's state according to
+ * the <mxGraph.keepEdgesInBackground> and <mxGraph.keepEdgesInBackground>
+ * rules.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose shape's DOM node should be ordered.
+ */
+mxCellRenderer.prototype.orderEdge = function(state)
+{
+ var view = state.view;
+ var model = view.graph.getModel();
+
+ // Moves edges to the foreground/background
+ if (view.graph.keepEdgesInForeground)
+ {
+ if (this.firstEdge == null || this.firstEdge.parentNode == null ||
+ this.firstEdge.parentNode != state.shape.node.parentNode)
+ {
+ this.firstEdge = state.shape.node;
+ }
+ }
+ else if (view.graph.keepEdgesInBackground)
+ {
+ var node = state.shape.node;
+ var parent = node.parentNode;
+
+ // Keeps the DOM node in front of its parent
+ var pcell = model.getParent(state.cell);
+ var pstate = view.getState(pcell);
+
+ if (pstate != null && pstate.shape != null && pstate.shape.node != null)
+ {
+ var child = pstate.shape.node.nextSibling;
+
+ if (child != null && child != node)
+ {
+ this.insertState(state, child);
+ }
+ }
+ else
+ {
+ var child = parent.firstChild;
+
+ if (child != null && child != node)
+ {
+ this.insertState(state, child);
+ }
+ }
+ }
+};
+
+/**
+ * Function: insertState
+ *
+ * Inserts the given state before the given node into its parent.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be created.
+ */
+mxCellRenderer.prototype.insertState = function(state, nextNode)
+{
+ state.shape.node.parentNode.insertBefore(state.shape.node, nextNode);
+
+ if (state.text != null && state.text.node != null &&
+ state.text.node.parentNode == state.shape.node.parentNode)
+ {
+ state.shape.node.parentNode.insertBefore(state.text.node, state.shape.node.nextSibling);
+ }
+};
+
+/**
+ * Function: createShape
+ *
+ * Creates the shape for the given cell state. The shape is configured
+ * using <configureShape>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be created.
+ */
+mxCellRenderer.prototype.createShape = function(state)
+{
+ if (state.style != null)
+ {
+ // Checks if there is a stencil for the name and creates
+ // a shape instance for the stencil if one exists
+ var key = state.style[mxConstants.STYLE_SHAPE];
+ var stencil = mxStencilRegistry.getStencil(key);
+
+ if (stencil != null)
+ {
+ state.shape = new mxStencilShape(stencil);
+ }
+ else
+ {
+ var ctor = this.getShapeConstructor(state);
+ state.shape = new ctor();
+ }
+
+ // Sets the initial bounds and points (will be updated in redraw)
+ state.shape.points = state.absolutePoints;
+ state.shape.bounds = new mxRectangle(
+ state.x, state.y, state.width, state.height);
+ state.shape.dialect = state.view.graph.dialect;
+
+ this.configureShape(state);
+ }
+};
+
+/**
+ * Function: getShapeConstructor
+ *
+ * Returns the constructor to be used for creating the shape.
+ */
+mxCellRenderer.prototype.getShapeConstructor = function(state)
+{
+ var key = state.style[mxConstants.STYLE_SHAPE];
+ var ctor = (key != null) ? this.shapes[key] : null;
+
+ if (ctor == null)
+ {
+ ctor = (state.view.graph.getModel().isEdge(state.cell)) ?
+ this.defaultEdgeShape : this.defaultVertexShape;
+ }
+
+ return ctor;
+};
+
+/**
+ * Function: configureShape
+ *
+ * Configures the shape for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be configured.
+ */
+mxCellRenderer.prototype.configureShape = function(state)
+{
+ state.shape.apply(state);
+ var image = state.view.graph.getImage(state);
+
+ if (image != null)
+ {
+ state.shape.image = image;
+ }
+
+ var indicator = state.view.graph.getIndicatorColor(state);
+ var key = state.view.graph.getIndicatorShape(state);
+ var ctor = (key != null) ? this.shapes[key] : null;
+
+ // Configures the indicator shape or image
+ if (indicator != null)
+ {
+ state.shape.indicatorShape = ctor;
+ state.shape.indicatorColor = indicator;
+ state.shape.indicatorGradientColor =
+ state.view.graph.getIndicatorGradientColor(state);
+ state.shape.indicatorDirection =
+ state.style[mxConstants.STYLE_INDICATOR_DIRECTION];
+ }
+ else
+ {
+ var indicator = state.view.graph.getIndicatorImage(state);
+
+ if (indicator != null)
+ {
+ state.shape.indicatorImage = indicator;
+ }
+ }
+
+ this.postConfigureShape(state);
+};
+
+/**
+ * Function: postConfigureShape
+ *
+ * Replaces any reserved words used for attributes, eg. inherit,
+ * indicated or swimlane for colors in the shape for the given state.
+ * This implementation resolves these keywords on the fill, stroke
+ * and gradient color keys.
+ */
+mxCellRenderer.prototype.postConfigureShape = function(state)
+{
+ if (state.shape != null)
+ {
+ this.resolveColor(state, 'indicatorColor', mxConstants.STYLE_FILLCOLOR);
+ this.resolveColor(state, 'indicatorGradientColor', mxConstants.STYLE_GRADIENTCOLOR);
+ this.resolveColor(state, 'fill', mxConstants.STYLE_FILLCOLOR);
+ this.resolveColor(state, 'stroke', mxConstants.STYLE_STROKECOLOR);
+ this.resolveColor(state, 'gradient', mxConstants.STYLE_GRADIENTCOLOR);
+ }
+};
+
+/**
+ * Function: resolveColor
+ *
+ * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
+ * the respective color on the shape.
+ */
+mxCellRenderer.prototype.resolveColor = function(state, field, key)
+{
+ var value = state.shape[field];
+ var graph = state.view.graph;
+ var referenced = null;
+
+ if (value == 'inherit')
+ {
+ referenced = graph.model.getParent(state.cell);
+ }
+ else if (value == 'swimlane')
+ {
+ if (graph.model.getTerminal(state.cell, false) != null)
+ {
+ referenced = graph.model.getTerminal(state.cell, false);
+ }
+ else
+ {
+ referenced = state.cell;
+ }
+
+ referenced = graph.getSwimlane(referenced);
+ key = graph.swimlaneIndicatorColorAttribute;
+ }
+ else if (value == 'indicated')
+ {
+ state.shape[field] = state.shape.indicatorColor;
+ }
+
+ if (referenced != null)
+ {
+ var rstate = graph.getView().getState(referenced);
+ state.shape[field] = null;
+
+ if (rstate != null)
+ {
+ if (rstate.shape != null && field != 'indicatorColor')
+ {
+ state.shape[field] = rstate.shape[field];
+ }
+ else
+ {
+ state.shape[field] = rstate.style[key];
+ }
+ }
+ }
+};
+
+/**
+ * Function: getLabelValue
+ *
+ * Returns the value to be used for the label.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.getLabelValue = function(state)
+{
+ var graph = state.view.graph;
+ var value = graph.getLabel(state.cell);
+
+ if (!graph.isHtmlLabel(state.cell) && !mxUtils.isNode(value) &&
+ graph.dialect != mxConstants.DIALECT_SVG && value != null)
+ {
+ value = mxUtils.htmlEntities(value, false);
+ }
+
+ return value;
+};
+
+/**
+ * Function: createLabel
+ *
+ * Creates the label for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.createLabel = function(state, value)
+{
+ var graph = state.view.graph;
+ var isEdge = graph.getModel().isEdge(state.cell);
+
+ if (state.style[mxConstants.STYLE_FONTSIZE] > 0 ||
+ state.style[mxConstants.STYLE_FONTSIZE] == null)
+ {
+ // Avoids using DOM node for empty labels
+ var isForceHtml = (graph.isHtmlLabel(state.cell) ||
+ (value != null && mxUtils.isNode(value))) &&
+ graph.dialect == mxConstants.DIALECT_SVG;
+
+ state.text = new mxText(value, new mxRectangle(),
+ (state.style[mxConstants.STYLE_ALIGN] ||
+ mxConstants.ALIGN_CENTER),
+ graph.getVerticalAlign(state),
+ state.style[mxConstants.STYLE_FONTCOLOR],
+ state.style[mxConstants.STYLE_FONTFAMILY],
+ state.style[mxConstants.STYLE_FONTSIZE],
+ state.style[mxConstants.STYLE_FONTSTYLE],
+ state.style[mxConstants.STYLE_SPACING],
+ state.style[mxConstants.STYLE_SPACING_TOP],
+ state.style[mxConstants.STYLE_SPACING_RIGHT],
+ state.style[mxConstants.STYLE_SPACING_BOTTOM],
+ state.style[mxConstants.STYLE_SPACING_LEFT],
+ state.style[mxConstants.STYLE_HORIZONTAL],
+ state.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],
+ state.style[mxConstants.STYLE_LABEL_BORDERCOLOR],
+ graph.isWrapping(state.cell) && graph.isHtmlLabel(state.cell),
+ graph.isLabelClipped(state.cell),
+ state.style[mxConstants.STYLE_OVERFLOW],
+ state.style[mxConstants.STYLE_LABEL_PADDING]);
+ state.text.opacity = state.style[mxConstants.STYLE_TEXT_OPACITY];
+
+ state.text.dialect = (isForceHtml) ?
+ mxConstants.DIALECT_STRICTHTML :
+ state.view.graph.dialect;
+ this.initializeLabel(state);
+
+ // Workaround for touch devices routing all events for a mouse
+ // gesture (down, move, up) via the initial DOM node. IE is even
+ // worse in that it redirects the event via the initial DOM node
+ // but the event source is the node under the mouse, so we need
+ // to check if this is the case and force getCellAt for the
+ // subsequent mouseMoves and the final mouseUp.
+ var forceGetCell = false;
+
+ var getState = function(evt)
+ {
+ var result = state;
+
+ if (mxClient.IS_TOUCH || forceGetCell)
+ {
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+
+ // Dispatches the drop event to the graph which
+ // consumes and executes the source function
+ var pt = mxUtils.convertPoint(graph.container, x, y);
+ result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+ }
+
+ return result;
+ };
+
+ // TODO: Add handling for gestures
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(state.text.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, state));
+ forceGetCell = graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG';
+ }
+ })
+ );
+
+ mxEvent.addListener(state.text.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, getState(evt)));
+ }
+ })
+ );
+
+ mxEvent.addListener(state.text.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt, getState(evt)));
+ forceGetCell = false;
+ }
+ })
+ );
+
+ mxEvent.addListener(state.text.node, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.dblClick(evt, state.cell);
+ mxEvent.consume(evt);
+ }
+ })
+ );
+ }
+};
+
+/**
+ * Function: initializeLabel
+ *
+ * Initiailzes the label with a suitable container.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label should be initialized.
+ */
+mxCellRenderer.prototype.initializeLabel = function(state)
+{
+ var graph = state.view.graph;
+
+ if (state.text.dialect != mxConstants.DIALECT_SVG)
+ {
+ // Adds the text to the container if the dialect is not SVG and we
+ // have an SVG-based browser which doesn't support foreignObjects
+ if (mxClient.IS_SVG && mxClient.NO_FO)
+ {
+ state.text.init(graph.container);
+ }
+ else if (mxUtils.isVml(state.view.getDrawPane()))
+ {
+ if (state.shape.label != null)
+ {
+ state.text.init(state.shape.label);
+ }
+ else
+ {
+ state.text.init(state.shape.node);
+ }
+ }
+ }
+
+ if (state.text.node == null)
+ {
+ state.text.init(state.view.getDrawPane());
+
+ if (state.shape != null && state.text != null)
+ {
+ state.shape.node.parentNode.insertBefore(
+ state.text.node, state.shape.node.nextSibling);
+ }
+ }
+};
+
+/**
+ * Function: createCellOverlays
+ *
+ * Creates the actual shape for showing the overlay for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the overlay should be created.
+ */
+mxCellRenderer.prototype.createCellOverlays = function(state)
+{
+ var graph = state.view.graph;
+ var overlays = graph.getCellOverlays(state.cell);
+ var dict = null;
+
+ if (overlays != null)
+ {
+ dict = new mxDictionary();
+
+ for (var i = 0; i < overlays.length; i++)
+ {
+ var shape = (state.overlays != null) ? state.overlays.remove(overlays[i]) : null;
+
+ if (shape == null)
+ {
+ var tmp = new mxImageShape(new mxRectangle(),
+ overlays[i].image.src);
+ tmp.dialect = state.view.graph.dialect;
+ tmp.preserveImageAspect = false;
+ tmp.overlay = overlays[i];
+ this.initializeOverlay(state, tmp);
+ this.installCellOverlayListeners(state, overlays[i], tmp);
+
+ if (overlays[i].cursor != null)
+ {
+ tmp.node.style.cursor = overlays[i].cursor;
+ }
+
+ dict.put(overlays[i], tmp);
+ }
+ else
+ {
+ dict.put(overlays[i], shape);
+ }
+ }
+ }
+
+ // Removes unused
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ shape.destroy();
+ });
+ }
+
+ state.overlays = dict;
+};
+
+/**
+ * Function: initializeOverlay
+ *
+ * Initializes the given overlay.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the overlay should be created.
+ * overlay - <mxImageShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.initializeOverlay = function(state, overlay)
+{
+ overlay.init(state.view.getOverlayPane());
+};
+
+/**
+ * Function: installOverlayListeners
+ *
+ * Installs the listeners for the given <mxCellState>, <mxCellOverlay> and
+ * <mxShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape)
+{
+ var graph = state.view.graph;
+
+ mxEvent.addListener(shape.node, 'click', function (evt)
+ {
+ if (graph.isEditing())
+ {
+ graph.stopEditing(!graph.isInvokesStopCellEditing());
+ }
+
+ overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+ 'event', evt, 'cell', state.cell));
+ });
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+
+ mxEvent.addListener(shape.node, md, function (evt)
+ {
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(shape.node, mm, function (evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, state));
+ });
+
+ if (mxClient.IS_TOUCH)
+ {
+ mxEvent.addListener(shape.node, 'touchend', function (evt)
+ {
+ overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+ 'event', evt, 'cell', state.cell));
+ });
+ }
+};
+
+/**
+ * Function: createControl
+ *
+ * Creates the control for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the control should be created.
+ */
+mxCellRenderer.prototype.createControl = function(state)
+{
+ var graph = state.view.graph;
+ var image = graph.getFoldingImage(state);
+
+ if (graph.foldingEnabled && image != null)
+ {
+ if (state.control == null)
+ {
+ var b = new mxRectangle(0, 0, image.width, image.height);
+ state.control = new mxImageShape(b, image.src);
+ state.control.dialect = graph.dialect;
+ state.control.preserveImageAspect = false;
+
+ this.initControl(state, state.control, true, function (evt)
+ {
+ if (graph.isEnabled())
+ {
+ var collapse = !graph.isCellCollapsed(state.cell);
+ graph.foldCells(collapse, false, [state.cell]);
+ mxEvent.consume(evt);
+ }
+ });
+ }
+ }
+ else if (state.control != null)
+ {
+ state.control.destroy();
+ state.control = null;
+ }
+};
+
+/**
+ * Function: initControl
+ *
+ * Initializes the given control and returns the corresponding DOM node.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the control should be initialized.
+ * control - <mxShape> to be initialized.
+ * handleEvents - Boolean indicating if mousedown and mousemove should fire events via the graph.
+ * clickHandler - Optional function to implement clicks on the control.
+ */
+mxCellRenderer.prototype.initControl = function(state, control, handleEvents, clickHandler)
+{
+ var graph = state.view.graph;
+
+ // In the special case where the label is in HTML and the display is SVG the image
+ // should go into the graph container directly in order to be clickable. Otherwise
+ // it is obscured by the HTML label that overlaps the cell.
+ var isForceHtml = graph.isHtmlLabel(state.cell) &&
+ mxClient.NO_FO &&
+ graph.dialect == mxConstants.DIALECT_SVG;
+
+ if (isForceHtml)
+ {
+ control.dialect = mxConstants.DIALECT_PREFERHTML;
+ control.init(graph.container);
+ control.node.style.zIndex = 1;
+ }
+ else
+ {
+ control.init(state.view.getOverlayPane());
+ }
+
+ var node = control.innerNode || control.node;
+
+ if (clickHandler)
+ {
+ if (graph.isEnabled())
+ {
+ node.style.cursor = 'pointer';
+ }
+
+ mxEvent.addListener(node, 'click', clickHandler);
+ }
+
+ if (handleEvents)
+ {
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+
+ mxEvent.addListener(node, md, function (evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(node, mm, function (evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
+ });
+ }
+
+ return node;
+};
+
+/**
+ * Function: isShapeEvent
+ *
+ * Returns true if the event is for the shape of the given state. This
+ * implementation always returns true.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose shape fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isShapeEvent = function(state, evt)
+{
+ return true;
+};
+
+/**
+ * Function: isLabelEvent
+ *
+ * Returns true if the event is for the label of the given state. This
+ * implementation always returns true.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isLabelEvent = function(state, evt)
+{
+ return true;
+};
+
+/**
+ * Function: installListeners
+ *
+ * Installs the event listeners for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the event listeners should be isntalled.
+ */
+mxCellRenderer.prototype.installListeners = function(state)
+{
+ var graph = state.view.graph;
+
+ // Receives events from transparent backgrounds
+ if (graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ var events = 'all';
+
+ // Disabled fill-events on non-filled edges
+ if (graph.getModel().isEdge(state.cell) && state.shape.stroke != null &&
+ (state.shape.fill == null || state.shape.fill == mxConstants.NONE))
+ {
+ events = 'visibleStroke';
+ }
+
+ // Specifies the event-processing on the shape
+ if (state.shape.innerNode != null)
+ {
+ state.shape.innerNode.setAttribute('pointer-events', events);
+ }
+ else
+ {
+ state.shape.node.setAttribute('pointer-events', events);
+ }
+ }
+
+ // Workaround for touch devices routing all events for a mouse
+ // gesture (down, move, up) via the initial DOM node. Same for
+ // HTML images in all IE versions (VML images are working).
+ var getState = function(evt)
+ {
+ var result = state;
+
+ if ((graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG') || mxClient.IS_TOUCH)
+ {
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+
+ // Dispatches the drop event to the graph which
+ // consumes and executes the source function
+ var pt = mxUtils.convertPoint(graph.container, x, y);
+ result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+ }
+
+ return result;
+ };
+
+ // Experimental support for two-finger pinch to resize cells
+ var gestureInProgress = false;
+
+ mxEvent.addListener(state.shape.node, 'gesturestart',
+ mxUtils.bind(this, function(evt)
+ {
+ // FIXME: Breaks encapsulation to reset the double
+ // tap event handling when gestures take place
+ graph.lastTouchTime = 0;
+
+ gestureInProgress = true;
+ mxEvent.consume(evt);
+ })
+ );
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(state.shape.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isShapeEvent(state, evt) && !gestureInProgress)
+ {
+ // Redirects events from the "event-transparent" region of
+ // a swimlane to the graph. This is only required in HTML,
+ // SVG and VML do not fire mouse events on transparent
+ // backgrounds.
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : state));
+ }
+ else if (gestureInProgress)
+ {
+ mxEvent.consume(evt);
+ }
+ })
+ );
+
+ mxEvent.addListener(state.shape.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isShapeEvent(state, evt) && !gestureInProgress)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : getState(evt)));
+ }
+ else if (gestureInProgress)
+ {
+ mxEvent.consume(evt);
+ }
+ })
+ );
+
+ mxEvent.addListener(state.shape.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isShapeEvent(state, evt) && !gestureInProgress)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : getState(evt)));
+ }
+ else if (gestureInProgress)
+ {
+ mxEvent.consume(evt);
+ }
+ })
+ );
+
+ // Experimental handling for gestures. Double-tap handling is implemented
+ // in mxGraph.fireMouseEvent.
+ var dc = (mxClient.IS_TOUCH) ? 'gestureend' : 'dblclick';
+
+ mxEvent.addListener(state.shape.node, dc,
+ mxUtils.bind(this, function(evt)
+ {
+ gestureInProgress = false;
+
+ if (dc == 'gestureend')
+ {
+ // FIXME: Breaks encapsulation to reset the double
+ // tap event handling when gestures take place
+ graph.lastTouchTime = 0;
+
+ if (graph.gestureEnabled)
+ {
+ graph.handleGesture(state, evt);
+ mxEvent.consume(evt);
+ }
+ }
+ else if (this.isShapeEvent(state, evt))
+ {
+ graph.dblClick(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : state.cell);
+ mxEvent.consume(evt);
+ }
+ })
+ );
+};
+
+/**
+ * Function: redrawLabel
+ *
+ * Redraws the label for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label should be redrawn.
+ */
+mxCellRenderer.prototype.redrawLabel = function(state)
+{
+ var value = this.getLabelValue(state);
+
+ // FIXME: Add label always if HTML label and NO_FO
+ if (state.text == null && value != null && (mxUtils.isNode(value) || value.length > 0))
+ {
+ this.createLabel(state, value);
+ }
+ else if (state.text != null && (value == null || value.length == 0))
+ {
+ state.text.destroy();
+ state.text = null;
+ }
+
+ if (state.text != null)
+ {
+ var graph = state.view.graph;
+ var wrapping = graph.isWrapping(state.cell);
+ var clipping = graph.isLabelClipped(state.cell);
+ var bounds = this.getLabelBounds(state);
+
+ if (state.text.value != value || state.text.isWrapping != wrapping ||
+ state.text.isClipping != clipping || state.text.scale != state.view.scale ||
+ !state.text.bounds.equals(bounds))
+ {
+ state.text.value = value;
+ state.text.bounds = bounds;
+ state.text.scale = this.getTextScale(state);
+ state.text.isWrapping = wrapping;
+ state.text.isClipping = clipping;
+
+ state.text.redraw();
+ }
+ }
+};
+
+/**
+ * Function: getTextScale
+ *
+ * Returns the scaling used for the label of the given state
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label scale should be returned.
+ */
+mxCellRenderer.prototype.getTextScale = function(state)
+{
+ return state.view.scale;
+};
+
+/**
+ * Function: getLabelBounds
+ *
+ * Returns the bounds to be used to draw the label of the given state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label bounds should be returned.
+ */
+mxCellRenderer.prototype.getLabelBounds = function(state)
+{
+ var graph = state.view.graph;
+ var isEdge = graph.getModel().isEdge(state.cell);
+ var bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y);
+
+ if (!isEdge)
+ {
+ bounds.x += state.x;
+ bounds.y += state.y;
+
+ // Minimum of 1 fixes alignment bug in HTML labels
+ bounds.width = Math.max(1, state.width);
+ bounds.height = Math.max(1, state.height);
+
+ if (graph.isSwimlane(state.cell))
+ {
+ var scale = graph.view.scale;
+ var size = graph.getStartSize(state.cell);
+
+ if (size.width > 0)
+ {
+ bounds.width = size.width * scale;
+ }
+ else if (size.height > 0)
+ {
+ bounds.height = size.height * scale;
+ }
+ }
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: redrawCellOverlays
+ *
+ * Redraws the overlays for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose overlays should be redrawn.
+ */
+mxCellRenderer.prototype.redrawCellOverlays = function(state)
+{
+ this.createCellOverlays(state);
+
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ var bounds = shape.overlay.getBounds(state);
+
+ if (shape.bounds == null || shape.scale != state.view.scale ||
+ !shape.bounds.equals(bounds))
+ {
+ shape.bounds = bounds;
+ shape.scale = state.view.scale;
+ shape.redraw();
+ }
+ });
+ }
+};
+
+/**
+ * Function: redrawControl
+ *
+ * Redraws the control for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose control should be redrawn.
+ */
+mxCellRenderer.prototype.redrawControl = function(state)
+{
+ if (state.control != null)
+ {
+ var bounds = this.getControlBounds(state);
+ var s = state.view.scale;
+
+ if (state.control.scale != s || !state.control.bounds.equals(bounds))
+ {
+ state.control.bounds = bounds;
+ state.control.scale = s;
+ state.control.redraw();
+ }
+ }
+};
+
+/**
+ * Function: getControlBounds
+ *
+ * Returns the bounds to be used to draw the control (folding icon) of the
+ * given state.
+ */
+mxCellRenderer.prototype.getControlBounds = function(state)
+{
+ if (state.control != null)
+ {
+ var oldScale = state.control.scale;
+ var w = state.control.bounds.width / oldScale;
+ var h = state.control.bounds.height / oldScale;
+ var s = state.view.scale;
+
+ return (state.view.graph.getModel().isEdge(state.cell)) ?
+ new mxRectangle(state.x + state.width / 2 - w / 2 * s,
+ state.y + state.height / 2 - h / 2 * s, w * s, h * s)
+ : new mxRectangle(state.x + w / 2 * s,
+ state.y + h / 2 * s, w * s, h * s);
+ }
+
+ return null;
+};
+
+/**
+ * Function: redraw
+ *
+ * Updates the bounds or points and scale of the shapes for the given cell
+ * state. This is called in mxGraphView.validatePoints as the last step of
+ * updating all cells.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shapes should be updated.
+ * force - Optional boolean that specifies if the cell should be reconfiured
+ * and redrawn without any additional checks.
+ * rendering - Optional boolean that specifies if the cell should actually
+ * be drawn into the DOM. If this is false then redraw and/or reconfigure
+ * will not be called on the shape.
+ */
+mxCellRenderer.prototype.redraw = function(state, force, rendering)
+{
+ if (state.shape != null)
+ {
+ var model = state.view.graph.getModel();
+ var isEdge = model.isEdge(state.cell);
+ reconfigure = (force != null) ? force : false;
+
+ // Handles changes of the collapse icon
+ this.createControl(state);
+
+ // Handles changes to the order in the DOM
+ if (state.orderChanged || state.invalidOrder)
+ {
+ if (state.view.graph.ordered)
+ {
+ this.order(state);
+ }
+ else
+ {
+ // Assert state.cell is edge
+ this.orderEdge(state);
+ }
+
+ // Required to update inherited styles
+ reconfigure = state.orderChanged;
+ }
+
+ delete state.invalidOrder;
+ delete state.orderChanged;
+
+ // Checks if the style in the state is different from the style
+ // in the shape and re-applies the style if required
+ if (!reconfigure && !mxUtils.equalEntries(state.shape.style, state.style))
+ {
+ reconfigure = true;
+ }
+
+ // Reconfiures the shape after an order or style change
+ if (reconfigure)
+ {
+ this.configureShape(state);
+ state.shape.reconfigure();
+ }
+
+ // Redraws the cell if required
+ if (force || state.shape.bounds == null || state.shape.scale != state.view.scale ||
+ !state.shape.bounds.equals(state) ||
+ !mxUtils.equalPoints(state.shape.points, state.absolutePoints))
+ {
+ // FIXME: Move indicator color update into shape.redraw
+// var indicator = state.view.graph.getIndicatorColor(state);
+// if (indicator != null)
+// {
+// state.shape.indicatorColor = indicator;
+// }
+
+ if (state.absolutePoints != null)
+ {
+ state.shape.points = state.absolutePoints.slice();
+ }
+ else
+ {
+ state.shape.points = null;
+ }
+
+ state.shape.bounds = new mxRectangle(
+ state.x, state.y, state.width, state.height);
+ state.shape.scale = state.view.scale;
+
+ if (rendering == null || rendering)
+ {
+ state.shape.redraw();
+ }
+ else
+ {
+ state.shape.updateBoundingBox();
+ }
+ }
+
+ // Updates the text label, overlays and control
+ if (rendering == null || rendering)
+ {
+ this.redrawLabel(state);
+ this.redrawCellOverlays(state);
+ this.redrawControl(state);
+ }
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the shapes associated with the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shapes should be destroyed.
+ */
+mxCellRenderer.prototype.destroy = function(state)
+{
+ if (state.shape != null)
+ {
+ if (state.text != null)
+ {
+ state.text.destroy();
+ state.text = null;
+ }
+
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ shape.destroy();
+ });
+
+ state.overlays = null;
+ }
+
+ if (state.control != null)
+ {
+ state.control.destroy();
+ state.control = null;
+ }
+
+ state.shape.destroy();
+ state.shape = null;
+ }
+};
diff --git a/src/js/view/mxCellState.js b/src/js/view/mxCellState.js
new file mode 100644
index 0000000..7e7a3b0
--- /dev/null
+++ b/src/js/view/mxCellState.js
@@ -0,0 +1,375 @@
+/**
+ * $Id: mxCellState.js,v 1.42 2012-03-19 10:47:08 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellState
+ *
+ * Represents the current state of a cell in a given <mxGraphView>.
+ *
+ * For edges, the edge label position is stored in <absoluteOffset>.
+ *
+ * The size for oversize labels can be retrieved using the boundingBox property
+ * of the <text> field as shown below.
+ *
+ * (code)
+ * var bbox = (state.text != null) ? state.text.boundingBox : null;
+ * (end)
+ *
+ * Constructor: mxCellState
+ *
+ * Constructs a new object that represents the current state of the given
+ * cell in the specified view.
+ *
+ * Parameters:
+ *
+ * view - <mxGraphView> that contains the state.
+ * cell - <mxCell> that this state represents.
+ * style - Array of key, value pairs that constitute the style.
+ */
+function mxCellState(view, cell, style)
+{
+ this.view = view;
+ this.cell = cell;
+ this.style = style;
+
+ this.origin = new mxPoint();
+ this.absoluteOffset = new mxPoint();
+};
+
+/**
+ * Extends mxRectangle.
+ */
+mxCellState.prototype = new mxRectangle();
+mxCellState.prototype.constructor = mxCellState;
+
+/**
+ * Variable: view
+ *
+ * Reference to the enclosing <mxGraphView>.
+ */
+mxCellState.prototype.view = null;
+
+/**
+ * Variable: cell
+ *
+ * Reference to the <mxCell> that is represented by this state.
+ */
+mxCellState.prototype.cell = null;
+
+/**
+ * Variable: style
+ *
+ * Contains an array of key, value pairs that represent the style of the
+ * cell.
+ */
+mxCellState.prototype.style = null;
+
+/**
+ * Variable: invalid
+ *
+ * Specifies if the state is invalid. Default is true.
+ */
+mxCellState.prototype.invalid = true;
+
+/**
+ * Variable: invalidOrder
+ *
+ * Specifies if the cell has an invalid order. For internal use. Default is
+ * false.
+ */
+mxCellState.prototype.invalidOrder = false;
+
+/**
+ * Variable: orderChanged
+ *
+ * Specifies if the cell has changed order and the display needs to be
+ * updated.
+ */
+mxCellState.prototype.orderChanged = false;
+
+/**
+ * Variable: origin
+ *
+ * <mxPoint> that holds the origin for all child cells. Default is a new
+ * empty <mxPoint>.
+ */
+mxCellState.prototype.origin = null;
+
+/**
+ * Variable: absolutePoints
+ *
+ * Holds an array of <mxPoints> that represent the absolute points of an
+ * edge.
+ */
+mxCellState.prototype.absolutePoints = null;
+
+/**
+ * Variable: absoluteOffset
+ *
+ * <mxPoint> that holds the absolute offset. For edges, this is the
+ * absolute coordinates of the label position. For vertices, this is the
+ * offset of the label relative to the top, left corner of the vertex.
+ */
+mxCellState.prototype.absoluteOffset = null;
+
+/**
+ * Variable: visibleSourceState
+ *
+ * Caches the visible source terminal state.
+ */
+mxCellState.prototype.visibleSourceState = null;
+
+/**
+ * Variable: visibleTargetState
+ *
+ * Caches the visible target terminal state.
+ */
+mxCellState.prototype.visibleTargetState = null;
+
+/**
+ * Variable: terminalDistance
+ *
+ * Caches the distance between the end points for an edge.
+ */
+mxCellState.prototype.terminalDistance = 0;
+
+/**
+ * Variable: length
+ *
+ * Caches the length of an edge.
+ */
+mxCellState.prototype.length = 0;
+
+/**
+ * Variable: segments
+ *
+ * Array of numbers that represent the cached length of each segment of the
+ * edge.
+ */
+mxCellState.prototype.segments = null;
+
+/**
+ * Variable: shape
+ *
+ * Holds the <mxShape> that represents the cell graphically.
+ */
+mxCellState.prototype.shape = null;
+
+/**
+ * Variable: text
+ *
+ * Holds the <mxText> that represents the label of the cell. Thi smay be
+ * null if the cell has no label.
+ */
+mxCellState.prototype.text = null;
+
+/**
+ * Function: getPerimeterBounds
+ *
+ * Returns the <mxRectangle> that should be used as the perimeter of the
+ * cell.
+ *
+ * Parameters:
+ *
+ * border - Optional border to be added around the perimeter bounds.
+ * bounds - Optional <mxRectangle> to be used as the initial bounds.
+ */
+mxCellState.prototype.getPerimeterBounds = function (border, bounds)
+{
+ border = border || 0;
+ bounds = (bounds != null) ? bounds : new mxRectangle(this.x, this.y, this.width, this.height);
+
+ if (this.shape != null && this.shape.stencil != null)
+ {
+ var aspect = this.shape.stencil.computeAspect(this, bounds, null);
+
+ bounds.x = aspect.x;
+ bounds.y = aspect.y;
+ bounds.width = this.shape.stencil.w0 * aspect.width;
+ bounds.height = this.shape.stencil.h0 * aspect.height;
+ }
+
+ if (border != 0)
+ {
+ bounds.grow(border);
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: setAbsoluteTerminalPoint
+ *
+ * Sets the first or last point in <absolutePoints> depending on isSource.
+ *
+ * Parameters:
+ *
+ * point - <mxPoint> that represents the terminal point.
+ * isSource - Boolean that specifies if the first or last point should
+ * be assigned.
+ */
+mxCellState.prototype.setAbsoluteTerminalPoint = function (point, isSource)
+{
+ if (isSource)
+ {
+ if (this.absolutePoints == null)
+ {
+ this.absolutePoints = [];
+ }
+
+ if (this.absolutePoints.length == 0)
+ {
+ this.absolutePoints.push(point);
+ }
+ else
+ {
+ this.absolutePoints[0] = point;
+ }
+ }
+ else
+ {
+ if (this.absolutePoints == null)
+ {
+ this.absolutePoints = [];
+ this.absolutePoints.push(null);
+ this.absolutePoints.push(point);
+ }
+ else if (this.absolutePoints.length == 1)
+ {
+ this.absolutePoints.push(point);
+ }
+ else
+ {
+ this.absolutePoints[this.absolutePoints.length - 1] = point;
+ }
+ }
+};
+
+/**
+ * Function: setCursor
+ *
+ * Sets the given cursor on the shape and text shape.
+ */
+mxCellState.prototype.setCursor = function (cursor)
+{
+ if (this.shape != null)
+ {
+ this.shape.setCursor(cursor);
+ }
+
+ if (this.text != null)
+ {
+ this.text.setCursor(cursor);
+ }
+};
+
+/**
+ * Function: getVisibleTerminal
+ *
+ * Returns the visible source or target terminal cell.
+ *
+ * Parameters:
+ *
+ * source - Boolean that specifies if the source or target cell should be
+ * returned.
+ */
+mxCellState.prototype.getVisibleTerminal = function (source)
+{
+ var tmp = this.getVisibleTerminalState(source);
+
+ return (tmp != null) ? tmp.cell : null;
+};
+
+/**
+ * Function: getVisibleTerminalState
+ *
+ * Returns the visible source or target terminal state.
+ *
+ * Parameters:
+ *
+ * source - Boolean that specifies if the source or target state should be
+ * returned.
+ */
+mxCellState.prototype.getVisibleTerminalState = function (source)
+{
+ return (source) ? this.visibleSourceState : this.visibleTargetState;
+};
+
+/**
+ * Function: setVisibleTerminalState
+ *
+ * Sets the visible source or target terminal state.
+ *
+ * Parameters:
+ *
+ * terminalState - <mxCellState> that represents the terminal.
+ * source - Boolean that specifies if the source or target state should be set.
+ */
+mxCellState.prototype.setVisibleTerminalState = function (terminalState, source)
+{
+ if (source)
+ {
+ this.visibleSourceState = terminalState;
+ }
+ else
+ {
+ this.visibleTargetState = terminalState;
+ }
+};
+
+/**
+ * Destructor: destroy
+ *
+ * Destroys the state and all associated resources.
+ */
+mxCellState.prototype.destroy = function ()
+{
+ this.view.graph.cellRenderer.destroy(this);
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of this <mxPoint>.
+ */
+mxCellState.prototype.clone = function()
+{
+ var clone = new mxCellState(this.view, this.cell, this.style);
+
+ // Clones the absolute points
+ if (this.absolutePoints != null)
+ {
+ clone.absolutePoints = [];
+
+ for (var i = 0; i < this.absolutePoints.length; i++)
+ {
+ clone.absolutePoints[i] = this.absolutePoints[i].clone();
+ }
+ }
+
+ if (this.origin != null)
+ {
+ clone.origin = this.origin.clone();
+ }
+
+ if (this.absoluteOffset != null)
+ {
+ clone.absoluteOffset = this.absoluteOffset.clone();
+ }
+
+ if (this.boundingBox != null)
+ {
+ clone.boundingBox = this.boundingBox.clone();
+ }
+
+ clone.terminalDistance = this.terminalDistance;
+ clone.segments = this.segments;
+ clone.length = this.length;
+ clone.x = this.x;
+ clone.y = this.y;
+ clone.width = this.width;
+ clone.height = this.height;
+
+ return clone;
+};
diff --git a/src/js/view/mxCellStatePreview.js b/src/js/view/mxCellStatePreview.js
new file mode 100644
index 0000000..b853748
--- /dev/null
+++ b/src/js/view/mxCellStatePreview.js
@@ -0,0 +1,223 @@
+/**
+ * $Id: mxCellStatePreview.js,v 1.6 2012-10-26 07:19:11 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxCellStatePreview
+ *
+ * Implements a live preview for moving cells.
+ *
+ * Constructor: mxCellStatePreview
+ *
+ * Constructs a move preview for the given graph.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxCellStatePreview(graph)
+{
+ this.graph = graph;
+ this.deltas = new Object();
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellStatePreview.prototype.graph = null;
+
+/**
+ * Variable: deltas
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellStatePreview.prototype.deltas = null;
+
+/**
+ * Variable: count
+ *
+ * Contains the number of entries in the map.
+ */
+mxCellStatePreview.prototype.count = 0;
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if this contains no entries.
+ */
+mxCellStatePreview.prototype.isEmpty = function()
+{
+ return this.count == 0;
+};
+
+/**
+ * Function: moveState
+ */
+mxCellStatePreview.prototype.moveState = function(state, dx, dy, add, includeEdges)
+{
+ add = (add != null) ? add : true;
+ includeEdges = (includeEdges != null) ? includeEdges : true;
+ var id = mxCellPath.create(state.cell);
+ var delta = this.deltas[id];
+
+ if (delta == null)
+ {
+ delta = new mxPoint(dx, dy);
+ this.deltas[id] = delta;
+ this.count++;
+ }
+ else
+ {
+ if (add)
+ {
+ delta.X += dx;
+ delta.Y += dy;
+ }
+ else
+ {
+ delta.X = dx;
+ delta.Y = dy;
+ }
+ }
+
+ if (includeEdges)
+ {
+ this.addEdges(state);
+ }
+
+ return delta;
+};
+
+/**
+ * Function: show
+ */
+mxCellStatePreview.prototype.show = function(visitor)
+{
+ var model = this.graph.getModel();
+ var root = model.getRoot();
+
+ // Translates the states in step
+ for (var id in this.deltas)
+ {
+ var cell = mxCellPath.resolve(root, id);
+ var state = this.graph.view.getState(cell);
+ var delta = this.deltas[id];
+ var parentState = this.graph.view.getState(
+ model.getParent(cell));
+ this.translateState(parentState, state, delta.x, delta.y);
+ }
+
+ // Revalidates the states in step
+ for (var id in this.deltas)
+ {
+ var cell = mxCellPath.resolve(root, id);
+ var state = this.graph.view.getState(cell);
+ var delta = this.deltas[id];
+ var parentState = this.graph.view.getState(
+ model.getParent(cell));
+ this.revalidateState(parentState, state, delta.x, delta.y, visitor);
+ }
+};
+
+/**
+ * Function: translateState
+ */
+mxCellStatePreview.prototype.translateState = function(parentState, state, dx, dy)
+{
+ if (state != null)
+ {
+ var model = this.graph.getModel();
+
+ if (model.isVertex(state.cell))
+ {
+ // LATER: Use hashtable to store initial state bounds
+ state.invalid = true;
+ this.graph.view.validateBounds(parentState, state.cell);
+ var geo = model.getGeometry(state.cell);
+ var id = mxCellPath.create(state.cell);
+
+ // Moves selection cells and non-relative vertices in
+ // the first phase so that edge terminal points will
+ // be updated in the second phase
+ if ((dx != 0 || dy != 0) && geo != null &&
+ (!geo.relative || this.deltas[id] != null))
+ {
+ state.x += dx;
+ state.y += dy;
+ }
+ }
+
+ var childCount = model.getChildCount(state.cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.translateState(state, this.graph.view.getState(
+ model.getChildAt(state.cell, i)), dx, dy);
+ }
+ }
+};
+
+/**
+ * Function: revalidateState
+ */
+mxCellStatePreview.prototype.revalidateState = function(parentState, state, dx, dy, visitor)
+{
+ if (state != null)
+ {
+ // Updates the edge terminal points and restores the
+ // (relative) positions of any (relative) children
+ state.invalid = true;
+ this.graph.view.validatePoints(parentState, state.cell);
+
+ // Moves selection vertices which are relative
+ var id = mxCellPath.create(state.cell);
+ var model = this.graph.getModel();
+ var geo = this.graph.getCellGeometry(state.cell);
+
+ if ((dx != 0 || dy != 0) && geo != null && geo.relative &&
+ model.isVertex(state.cell) && (parentState == null ||
+ model.isVertex(parentState.cell) || this.deltas[id] != null))
+ {
+ state.x += dx;
+ state.y += dy;
+
+ this.graph.cellRenderer.redraw(state);
+ }
+
+ // Invokes the visitor on the given state
+ if (visitor != null)
+ {
+ visitor(state);
+ }
+
+ var childCount = model.getChildCount(state.cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.revalidateState(state, this.graph.view.getState(model.getChildAt(
+ state.cell, i)), dx, dy, visitor);
+ }
+ }
+};
+
+/**
+ * Function: addEdges
+ */
+mxCellStatePreview.prototype.addEdges = function(state)
+{
+ var model = this.graph.getModel();
+ var edgeCount = model.getEdgeCount(state.cell);
+
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var s = this.graph.view.getState(model.getEdgeAt(state.cell, i));
+
+ if (s != null)
+ {
+ this.moveState(s, 0, 0);
+ }
+ }
+};
diff --git a/src/js/view/mxConnectionConstraint.js b/src/js/view/mxConnectionConstraint.js
new file mode 100644
index 0000000..70f457f
--- /dev/null
+++ b/src/js/view/mxConnectionConstraint.js
@@ -0,0 +1,42 @@
+/**
+ * $Id: mxConnectionConstraint.js,v 1.2 2010-04-29 09:33:52 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxConnectionConstraint
+ *
+ * Defines an object that contains the constraints about how to connect one
+ * side of an edge to its terminal.
+ *
+ * Constructor: mxConnectionConstraint
+ *
+ * Constructs a new connection constraint for the given point and boolean
+ * arguments.
+ *
+ * Parameters:
+ *
+ * point - Optional <mxPoint> that specifies the fixed location of the point
+ * in relative coordinates. Default is null.
+ * perimeter - Optional boolean that specifies if the fixed point should be
+ * projected onto the perimeter of the terminal. Default is true.
+ */
+function mxConnectionConstraint(point, perimeter)
+{
+ this.point = point;
+ this.perimeter = (perimeter != null) ? perimeter : true;
+};
+
+/**
+ * Variable: point
+ *
+ * <mxPoint> that specifies the fixed location of the connection point.
+ */
+mxConnectionConstraint.prototype.point = null;
+
+/**
+ * Variable: perimeter
+ *
+ * Boolean that specifies if the point should be projected onto the perimeter
+ * of the terminal.
+ */
+mxConnectionConstraint.prototype.perimeter = null;
diff --git a/src/js/view/mxEdgeStyle.js b/src/js/view/mxEdgeStyle.js
new file mode 100644
index 0000000..41493d6
--- /dev/null
+++ b/src/js/view/mxEdgeStyle.js
@@ -0,0 +1,1302 @@
+/**
+ * $Id: mxEdgeStyle.js,v 1.68 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxEdgeStyle =
+{
+ /**
+ * Class: mxEdgeStyle
+ *
+ * Provides various edge styles to be used as the values for
+ * <mxConstants.STYLE_EDGE> in a cell style.
+ *
+ * Example:
+ *
+ * (code)
+ * var style = stylesheet.getDefaultEdgeStyle();
+ * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
+ * (end)
+ *
+ * Sets the default edge style to <ElbowConnector>.
+ *
+ * Custom edge style:
+ *
+ * To write a custom edge style, a function must be added to the mxEdgeStyle
+ * object as follows:
+ *
+ * (code)
+ * mxEdgeStyle.MyStyle = function(state, source, target, points, result)
+ * {
+ * if (source != null && target != null)
+ * {
+ * var pt = new mxPoint(target.getCenterX(), source.getCenterY());
+ *
+ * if (mxUtils.contains(source, pt.x, pt.y))
+ * {
+ * pt.y = source.y + source.height;
+ * }
+ *
+ * result.push(pt);
+ * }
+ * };
+ * (end)
+ *
+ * In the above example, a right angle is created using a point on the
+ * horizontal center of the target vertex and the vertical center of the source
+ * vertex. The code checks if that point intersects the source vertex and makes
+ * the edge straight if it does. The point is then added into the result array,
+ * which acts as the return value of the function.
+ *
+ * The new edge style should then be registered in the <mxStyleRegistry> as follows:
+ * (code)
+ * mxStyleRegistry.putValue('myEdgeStyle', mxEdgeStyle.MyStyle);
+ * (end)
+ *
+ * The custom edge style above can now be used in a specific edge as follows:
+ *
+ * (code)
+ * model.setStyle(edge, 'edgeStyle=myEdgeStyle');
+ * (end)
+ *
+ * Note that the key of the <mxStyleRegistry> entry for the function should
+ * be used in string values, unless <mxGraphView.allowEval> is true, in
+ * which case you can also use mxEdgeStyle.MyStyle for the value in the
+ * cell style above.
+ *
+ * Or it can be used for all edges in the graph as follows:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultEdgeStyle();
+ * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.MyStyle;
+ * (end)
+ *
+ * Note that the object can be used directly when programmatically setting
+ * the value, but the key in the <mxStyleRegistry> should be used when
+ * setting the value via a key, value pair in a cell style.
+ *
+ * Function: EntityRelation
+ *
+ * Implements an entity relation style for edges (as used in database
+ * schema diagrams). At the time the function is called, the result
+ * array contains a placeholder (null) for the first absolute point,
+ * that is, the point where the edge and source terminal are connected.
+ * The implementation of the style then adds all intermediate waypoints
+ * except for the last point, that is, the connection point between the
+ * edge and the target terminal. The first ant the last point in the
+ * result array are then replaced with mxPoints that take into account
+ * the terminal's perimeter and next point on the edge.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the edge to be updated.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ * points - List of relative control points.
+ * result - Array of <mxPoints> that represent the actual points of the
+ * edge.
+ */
+ EntityRelation: function (state, source, target, points, result)
+ {
+ var view = state.view;
+ var graph = view.graph;
+ var segment = mxUtils.getValue(state.style,
+ mxConstants.STYLE_SEGMENT,
+ mxConstants.ENTITY_SEGMENT) * view.scale;
+
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length-1];
+
+ var isSourceLeft = false;
+
+ if (p0 != null)
+ {
+ source = new mxCellState();
+ source.x = p0.x;
+ source.y = p0.y;
+ }
+ else if (source != null)
+ {
+ var constraint = mxUtils.getPortConstraints(source, state, true, mxConstants.DIRECTION_MASK_NONE);
+
+ if (constraint != mxConstants.DIRECTION_MASK_NONE)
+ {
+ isSourceLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
+ }
+ else
+ {
+ var sourceGeometry = graph.getCellGeometry(source.cell);
+
+ if (sourceGeometry.relative)
+ {
+ isSourceLeft = sourceGeometry.x <= 0.5;
+ }
+ else if (target != null)
+ {
+ isSourceLeft = target.x + target.width < source.x;
+ }
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ var isTargetLeft = true;
+
+ if (pe != null)
+ {
+ target = new mxCellState();
+ target.x = pe.x;
+ target.y = pe.y;
+ }
+ else if (target != null)
+ {
+ var constraint = mxUtils.getPortConstraints(target, state, false, mxConstants.DIRECTION_MASK_NONE);
+
+ if (constraint != mxConstants.DIRECTION_MASK_NONE)
+ {
+ isTargetLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
+ }
+ else
+ {
+ var targetGeometry = graph.getCellGeometry(target.cell);
+
+ if (targetGeometry.relative)
+ {
+ isTargetLeft = targetGeometry.x <= 0.5;
+ }
+ else if (source != null)
+ {
+ isTargetLeft = source.x + source.width < target.x;
+ }
+ }
+ }
+
+ if (source != null && target != null)
+ {
+ var x0 = (isSourceLeft) ? source.x : source.x + source.width;
+ var y0 = view.getRoutingCenterY(source);
+
+ var xe = (isTargetLeft) ? target.x : target.x + target.width;
+ var ye = view.getRoutingCenterY(target);
+
+ var seg = segment;
+
+ var dx = (isSourceLeft) ? -seg : seg;
+ var dep = new mxPoint(x0 + dx, y0);
+
+ dx = (isTargetLeft) ? -seg : seg;
+ var arr = new mxPoint(xe + dx, ye);
+
+ // Adds intermediate points if both go out on same side
+ if (isSourceLeft == isTargetLeft)
+ {
+ var x = (isSourceLeft) ?
+ Math.min(x0, xe)-segment :
+ Math.max(x0, xe)+segment;
+
+ result.push(new mxPoint(x, y0));
+ result.push(new mxPoint(x, ye));
+ }
+ else if ((dep.x < arr.x) == isSourceLeft)
+ {
+ var midY = y0 + (ye - y0) / 2;
+
+ result.push(dep);
+ result.push(new mxPoint(dep.x, midY));
+ result.push(new mxPoint(arr.x, midY));
+ result.push(arr);
+ }
+ else
+ {
+ result.push(dep);
+ result.push(arr);
+ }
+ }
+ },
+
+ /**
+ * Function: Loop
+ *
+ * Implements a self-reference, aka. loop.
+ */
+ Loop: function (state, source, target, points, result)
+ {
+ if (source != null)
+ {
+ var view = state.view;
+ var graph = view.graph;
+ var pt = (points != null && points.length > 0) ? points[0] : null;
+
+ if (pt != null)
+ {
+ pt = view.transformControlPoint(state, pt);
+
+ if (mxUtils.contains(source, pt.x, pt.y))
+ {
+ pt = null;
+ }
+ }
+
+ var x = 0;
+ var dx = 0;
+ var y = 0;
+ var dy = 0;
+
+ var seg = mxUtils.getValue(state.style, mxConstants.STYLE_SEGMENT,
+ graph.gridSize) * view.scale;
+ var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION,
+ mxConstants.DIRECTION_WEST);
+
+ if (dir == mxConstants.DIRECTION_NORTH ||
+ dir == mxConstants.DIRECTION_SOUTH)
+ {
+ x = view.getRoutingCenterX(source);
+ dx = seg;
+ }
+ else
+ {
+ y = view.getRoutingCenterY(source);
+ dy = seg;
+ }
+
+ if (pt == null ||
+ pt.x < source.x ||
+ pt.x > source.x + source.width)
+ {
+ if (pt != null)
+ {
+ x = pt.x;
+ dy = Math.max(Math.abs(y - pt.y), dy);
+ }
+ else
+ {
+ if (dir == mxConstants.DIRECTION_NORTH)
+ {
+ y = source.y - 2 * dx;
+ }
+ else if (dir == mxConstants.DIRECTION_SOUTH)
+ {
+ y = source.y + source.height + 2 * dx;
+ }
+ else if (dir == mxConstants.DIRECTION_EAST)
+ {
+ x = source.x - 2 * dy;
+ }
+ else
+ {
+ x = source.x + source.width + 2 * dy;
+ }
+ }
+ }
+ else if (pt != null)
+ {
+ x = view.getRoutingCenterX(source);
+ dx = Math.max(Math.abs(x - pt.x), dy);
+ y = pt.y;
+ dy = 0;
+ }
+
+ result.push(new mxPoint(x - dx, y - dy));
+ result.push(new mxPoint(x + dx, y + dy));
+ }
+ },
+
+ /**
+ * Function: ElbowConnector
+ *
+ * Uses either <SideToSide> or <TopToBottom> depending on the horizontal
+ * flag in the cell style. <SideToSide> is used if horizontal is true or
+ * unspecified. See <EntityRelation> for a description of the
+ * parameters.
+ */
+ ElbowConnector: function (state, source, target, points, result)
+ {
+ var pt = (points != null && points.length > 0) ? points[0] : null;
+
+ var vertical = false;
+ var horizontal = false;
+
+ if (source != null && target != null)
+ {
+ if (pt != null)
+ {
+ var left = Math.min(source.x, target.x);
+ var right = Math.max(source.x + source.width,
+ target.x + target.width);
+
+ var top = Math.min(source.y, target.y);
+ var bottom = Math.max(source.y + source.height,
+ target.y + target.height);
+
+ pt = state.view.transformControlPoint(state, pt);
+
+ vertical = pt.y < top || pt.y > bottom;
+ horizontal = pt.x < left || pt.x > right;
+ }
+ else
+ {
+ var left = Math.max(source.x, target.x);
+ var right = Math.min(source.x + source.width,
+ target.x + target.width);
+
+ vertical = left == right;
+
+ if (!vertical)
+ {
+ var top = Math.max(source.y, target.y);
+ var bottom = Math.min(source.y + source.height,
+ target.y + target.height);
+
+ horizontal = top == bottom;
+ }
+ }
+ }
+
+ if (!horizontal && (vertical ||
+ state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL))
+ {
+ mxEdgeStyle.TopToBottom(state, source, target, points, result);
+ }
+ else
+ {
+ mxEdgeStyle.SideToSide(state, source, target, points, result);
+ }
+ },
+
+ /**
+ * Function: SideToSide
+ *
+ * Implements a vertical elbow edge. See <EntityRelation> for a description
+ * of the parameters.
+ */
+ SideToSide: function (state, source, target, points, result)
+ {
+ var view = state.view;
+ var pt = (points != null && points.length > 0) ? points[0] : null;
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length-1];
+
+ if (pt != null)
+ {
+ pt = view.transformControlPoint(state, pt);
+ }
+
+ if (p0 != null)
+ {
+ source = new mxCellState();
+ source.x = p0.x;
+ source.y = p0.y;
+ }
+
+ if (pe != null)
+ {
+ target = new mxCellState();
+ target.x = pe.x;
+ target.y = pe.y;
+ }
+
+ if (source != null && target != null)
+ {
+ var l = Math.max(source.x, target.x);
+ var r = Math.min(source.x + source.width,
+ target.x + target.width);
+
+ var x = (pt != null) ? pt.x : r + (l - r) / 2;
+
+ var y1 = view.getRoutingCenterY(source);
+ var y2 = view.getRoutingCenterY(target);
+
+ if (pt != null)
+ {
+ if (pt.y >= source.y && pt.y <= source.y + source.height)
+ {
+ y1 = pt.y;
+ }
+
+ if (pt.y >= target.y && pt.y <= target.y + target.height)
+ {
+ y2 = pt.y;
+ }
+ }
+
+ if (!mxUtils.contains(target, x, y1) &&
+ !mxUtils.contains(source, x, y1))
+ {
+ result.push(new mxPoint(x, y1));
+ }
+
+ if (!mxUtils.contains(target, x, y2) &&
+ !mxUtils.contains(source, x, y2))
+ {
+ result.push(new mxPoint(x, y2));
+ }
+
+ if (result.length == 1)
+ {
+ if (pt != null)
+ {
+ if (!mxUtils.contains(target, x, pt.y) &&
+ !mxUtils.contains(source, x, pt.y))
+ {
+ result.push(new mxPoint(x, pt.y));
+ }
+ }
+ else
+ {
+ var t = Math.max(source.y, target.y);
+ var b = Math.min(source.y + source.height,
+ target.y + target.height);
+
+ result.push(new mxPoint(x, t + (b - t) / 2));
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: TopToBottom
+ *
+ * Implements a horizontal elbow edge. See <EntityRelation> for a
+ * description of the parameters.
+ */
+ TopToBottom: function(state, source, target, points, result)
+ {
+ var view = state.view;
+ var pt = (points != null && points.length > 0) ? points[0] : null;
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length-1];
+
+ if (pt != null)
+ {
+ pt = view.transformControlPoint(state, pt);
+ }
+
+ if (p0 != null)
+ {
+ source = new mxCellState();
+ source.x = p0.x;
+ source.y = p0.y;
+ }
+
+ if (pe != null)
+ {
+ target = new mxCellState();
+ target.x = pe.x;
+ target.y = pe.y;
+ }
+
+ if (source != null && target != null)
+ {
+ var t = Math.max(source.y, target.y);
+ var b = Math.min(source.y + source.height,
+ target.y + target.height);
+
+ var x = view.getRoutingCenterX(source);
+
+ if (pt != null &&
+ pt.x >= source.x &&
+ pt.x <= source.x + source.width)
+ {
+ x = pt.x;
+ }
+
+ var y = (pt != null) ? pt.y : b + (t - b) / 2;
+
+ if (!mxUtils.contains(target, x, y) &&
+ !mxUtils.contains(source, x, y))
+ {
+ result.push(new mxPoint(x, y));
+ }
+
+ if (pt != null &&
+ pt.x >= target.x &&
+ pt.x <= target.x + target.width)
+ {
+ x = pt.x;
+ }
+ else
+ {
+ x = view.getRoutingCenterX(target);
+ }
+
+ if (!mxUtils.contains(target, x, y) &&
+ !mxUtils.contains(source, x, y))
+ {
+ result.push(new mxPoint(x, y));
+ }
+
+ if (result.length == 1)
+ {
+ if (pt != null && result.length == 1)
+ {
+ if (!mxUtils.contains(target, pt.x, y) &&
+ !mxUtils.contains(source, pt.x, y))
+ {
+ result.push(new mxPoint(pt.x, y));
+ }
+ }
+ else
+ {
+ var l = Math.max(source.x, target.x);
+ var r = Math.min(source.x + source.width,
+ target.x + target.width);
+
+ result.push(new mxPoint(l + (r - l) / 2, y));
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: SegmentConnector
+ *
+ * Implements an orthogonal edge style. Use <mxEdgeSegmentHandler>
+ * as an interactive handler for this style.
+ */
+ SegmentConnector: function(state, source, target, hints, result)
+ {
+ // Creates array of all way- and terminalpoints
+ var pts = state.absolutePoints;
+ var horizontal = true;
+ var hint = null;
+
+ // Adds the first point
+ var pt = pts[0];
+
+ if (pt == null && source != null)
+ {
+ pt = new mxPoint(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source));
+ }
+ else if (pt != null)
+ {
+ pt = pt.clone();
+ }
+
+ var lastInx = pts.length - 1;
+
+ // Adds the waypoints
+ if (hints != null && hints.length > 0)
+ {
+ hint = state.view.transformControlPoint(state, hints[0]);
+
+ var currentTerm = source;
+ var currentPt = pts[0];
+ var hozChan = false;
+ var vertChan = false;
+ var currentHint = hint;
+ var hintsLen = hints.length;
+
+ for (var i = 0; i < 2; i++)
+ {
+ var fixedVertAlign = currentPt != null && currentPt.x == currentHint.x;
+ var fixedHozAlign = currentPt != null && currentPt.y == currentHint.y;
+ var inHozChan = currentTerm != null && (currentHint.y >= currentTerm.y &&
+ currentHint.y <= currentTerm.y + currentTerm.height);
+ var inVertChan = currentTerm != null && (currentHint.x >= currentTerm.x &&
+ currentHint.x <= currentTerm.x + currentTerm.width);
+
+ hozChan = fixedHozAlign || (currentPt == null && inHozChan);
+ vertChan = fixedVertAlign || (currentPt == null && inVertChan);
+
+ if (currentPt != null && (!fixedHozAlign && !fixedVertAlign) && (inHozChan || inVertChan))
+ {
+ horizontal = inHozChan ? false : true;
+ break;
+ }
+
+ if (vertChan || hozChan)
+ {
+ horizontal = hozChan;
+
+ if (i == 1)
+ {
+ // Work back from target end
+ horizontal = hints.length % 2 == 0 ? hozChan : vertChan;
+ }
+
+ break;
+ }
+
+ currentTerm = target;
+ currentPt = pts[lastInx];
+ currentHint = state.view.transformControlPoint(state, hints[hintsLen - 1]);
+ }
+
+ if (horizontal && ((pts[0] != null && pts[0].y != hint.y) ||
+ (pts[0] == null && source != null &&
+ (hint.y < source.y || hint.y > source.y + source.height))))
+ {
+ result.push(new mxPoint(pt.x, hint.y));
+ }
+ else if (!horizontal && ((pts[0] != null && pts[0].x != hint.x) ||
+ (pts[0] == null && source != null &&
+ (hint.x < source.x || hint.x > source.x + source.width))))
+ {
+ result.push(new mxPoint(hint.x, pt.y));
+ }
+
+ if (horizontal)
+ {
+ pt.y = hint.y;
+ }
+ else
+ {
+ pt.x = hint.x;
+ }
+
+ for (var i = 0; i < hints.length; i++)
+ {
+ horizontal = !horizontal;
+ hint = state.view.transformControlPoint(state, hints[i]);
+
+// mxLog.show();
+// mxLog.debug('hint', i, hint.x, hint.y);
+
+ if (horizontal)
+ {
+ pt.y = hint.y;
+ }
+ else
+ {
+ pt.x = hint.x;
+ }
+
+ result.push(pt.clone());
+ }
+ }
+ else
+ {
+ hint = pt;
+ // FIXME: First click in connect preview toggles orientation
+ horizontal = true;
+ }
+
+ // Adds the last point
+ pt = pts[lastInx];
+
+ if (pt == null && target != null)
+ {
+ pt = new mxPoint(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target));
+ }
+
+ if (horizontal && ((pts[lastInx] != null && pts[lastInx].y != hint.y) ||
+ (pts[lastInx] == null && target != null &&
+ (hint.y < target.y || hint.y > target.y + target.height))))
+ {
+ result.push(new mxPoint(pt.x, hint.y));
+ }
+ else if (!horizontal && ((pts[lastInx] != null && pts[lastInx].x != hint.x) ||
+ (pts[lastInx] == null && target != null &&
+ (hint.x < target.x || hint.x > target.x + target.width))))
+ {
+ result.push(new mxPoint(hint.x, pt.y));
+ }
+
+ // Removes bends inside the source terminal for floating ports
+ if (pts[0] == null && source != null)
+ {
+ while (result.length > 1 && mxUtils.contains(source, result[1].x, result[1].y))
+ {
+ result = result.splice(1, 1);
+ }
+ }
+
+ // Removes bends inside the target terminal
+ if (pts[lastInx] == null && target != null)
+ {
+ while (result.length > 1 && mxUtils.contains(target, result[result.length - 1].x, result[result.length - 1].y))
+ {
+ result = result.splice(result.length - 1, 1);
+ }
+ }
+
+ },
+
+ orthBuffer: 10,
+
+ dirVectors: [ [ -1, 0 ],
+ [ 0, -1 ], [ 1, 0 ], [ 0, 1 ], [ -1, 0 ], [ 0, -1 ], [ 1, 0 ] ],
+
+ wayPoints1: [ [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0],
+ [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0] ],
+
+ routePatterns: [
+ [ [ 513, 2308, 2081, 2562 ], [ 513, 1090, 514, 2184, 2114, 2561 ],
+ [ 513, 1090, 514, 2564, 2184, 2562 ],
+ [ 513, 2308, 2561, 1090, 514, 2568, 2308 ] ],
+ [ [ 514, 1057, 513, 2308, 2081, 2562 ], [ 514, 2184, 2114, 2561 ],
+ [ 514, 2184, 2562, 1057, 513, 2564, 2184 ],
+ [ 514, 1057, 513, 2568, 2308, 2561 ] ],
+ [ [ 1090, 514, 1057, 513, 2308, 2081, 2562 ], [ 2114, 2561 ],
+ [ 1090, 2562, 1057, 513, 2564, 2184 ],
+ [ 1090, 514, 1057, 513, 2308, 2561, 2568 ] ],
+ [ [ 2081, 2562 ], [ 1057, 513, 1090, 514, 2184, 2114, 2561 ],
+ [ 1057, 513, 1090, 514, 2184, 2562, 2564 ],
+ [ 1057, 2561, 1090, 514, 2568, 2308 ] ] ],
+
+ inlineRoutePatterns: [
+ [ null, [ 2114, 2568 ], null, null ],
+ [ null, [ 514, 2081, 2114, 2568 ] , null, null ],
+ [ null, [ 2114, 2561 ], null, null ],
+ [ [ 2081, 2562 ], [ 1057, 2114, 2568 ],
+ [ 2184, 2562 ],
+ null ] ],
+ vertexSeperations: [],
+
+ limits: [
+ [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+ [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ],
+
+ LEFT_MASK: 32,
+
+ TOP_MASK: 64,
+
+ RIGHT_MASK: 128,
+
+ BOTTOM_MASK: 256,
+
+ LEFT: 1,
+
+ TOP: 2,
+
+ RIGHT: 4,
+
+ BOTTOM: 8,
+
+ // TODO remove magic numbers
+ SIDE_MASK: 480,
+ //mxEdgeStyle.LEFT_MASK | mxEdgeStyle.TOP_MASK | mxEdgeStyle.RIGHT_MASK
+ //| mxEdgeStyle.BOTTOM_MASK,
+
+ CENTER_MASK: 512,
+
+ SOURCE_MASK: 1024,
+
+ TARGET_MASK: 2048,
+
+ VERTEX_MASK: 3072,
+ // mxEdgeStyle.SOURCE_MASK | mxEdgeStyle.TARGET_MASK,
+
+ /**
+ * Function: OrthConnector
+ *
+ * Implements a local orthogonal router between the given
+ * cells.
+ */
+ OrthConnector: function(state, source, target, points, result)
+ {
+ var graph = state.view.graph;
+ var sourceEdge = source == null ? false : graph.getModel().isEdge(source.cell);
+ var targetEdge = target == null ? false : graph.getModel().isEdge(target.cell);
+
+ if ((points != null && points.length > 0) || (sourceEdge) || (targetEdge))
+ {
+ mxEdgeStyle.SegmentConnector(state, source, target, points, result);
+ return;
+ }
+
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length-1];
+
+ var sourceX = source != null ? source.x : p0.x;
+ var sourceY = source != null ? source.y : p0.y;
+ var sourceWidth = source != null ? source.width : 1;
+ var sourceHeight = source != null ? source.height : 1;
+
+ var targetX = target != null ? target.x : pe.x;
+ var targetY = target != null ? target.y : pe.y;
+ var targetWidth = target != null ? target.width : 1;
+ var targetHeight = target != null ? target.height : 1;
+
+ var scaledOrthBuffer = state.view.scale * mxEdgeStyle.orthBuffer;
+ // Determine the side(s) of the source and target vertices
+ // that the edge may connect to
+ // portConstraint [source, target]
+ var portConstraint = [mxConstants.DIRECTION_MASK_ALL, mxConstants.DIRECTION_MASK_ALL];
+
+ if (source != null)
+ {
+ portConstraint[0] = mxUtils.getPortConstraints(source, state, true,
+ mxConstants.DIRECTION_MASK_ALL);
+ }
+
+ if (target != null)
+ {
+ portConstraint[1] = mxUtils.getPortConstraints(target, state, false,
+ mxConstants.DIRECTION_MASK_ALL);
+ }
+
+ var dir = [0, 0] ;
+
+ // Work out which faces of the vertices present against each other
+ // in a way that would allow a 3-segment connection if port constraints
+ // permitted.
+ // geo -> [source, target] [x, y, width, height]
+ var geo = [ [sourceX, sourceY, sourceWidth, sourceHeight] ,
+ [targetX, targetY, targetWidth, targetHeight] ];
+
+ for (var i = 0; i < 2; i++)
+ {
+ mxEdgeStyle.limits[i][1] = geo[i][0] - scaledOrthBuffer;
+ mxEdgeStyle.limits[i][2] = geo[i][1] - scaledOrthBuffer;
+ mxEdgeStyle.limits[i][4] = geo[i][0] + geo[i][2] + scaledOrthBuffer;
+ mxEdgeStyle.limits[i][8] = geo[i][1] + geo[i][3] + scaledOrthBuffer;
+ }
+
+ // Work out which quad the target is in
+ var sourceCenX = geo[0][0] + geo[0][2] / 2.0;
+ var sourceCenY = geo[0][1] + geo[0][3] / 2.0;
+ var targetCenX = geo[1][0] + geo[1][2] / 2.0;
+ var targetCenY = geo[1][1] + geo[1][3] / 2.0;
+
+ var dx = sourceCenX - targetCenX;
+ var dy = sourceCenY - targetCenY;
+
+ var quad = 0;
+
+ if (dx < 0)
+ {
+ if (dy < 0)
+ {
+ quad = 2;
+ }
+ else
+ {
+ quad = 1;
+ }
+ }
+ else
+ {
+ if (dy <= 0)
+ {
+ quad = 3;
+
+ // Special case on x = 0 and negative y
+ if (dx == 0)
+ {
+ quad = 2;
+ }
+ }
+ }
+
+ // Check for connection constraints
+ var currentTerm = null;
+
+ if (source != null)
+ {
+ currentTerm = p0;
+ }
+
+ var constraint = [ [0.5, 0.5] , [0.5, 0.5] ];
+
+ for (var i = 0; i < 2; i++)
+ {
+ if (currentTerm != null)
+ {
+ constraint[i][0] = (currentTerm.x - geo[i][0]) / geo[i][2];
+
+ if (constraint[i][0] < 0.01)
+ {
+ dir[i] = mxConstants.DIRECTION_MASK_WEST;
+ }
+ else if (constraint[i][0] > 0.99)
+ {
+ dir[i] = mxConstants.DIRECTION_MASK_EAST;
+ }
+
+ constraint[i][1] = (currentTerm.y - geo[i][1]) / geo[i][3];
+
+ if (constraint[i][1] < 0.01)
+ {
+ dir[i] = mxConstants.DIRECTION_MASK_NORTH;
+ }
+ else if (constraint[i][1] > 0.99)
+ {
+ dir[i] = mxConstants.DIRECTION_MASK_SOUTH;
+ }
+ }
+
+ currentTerm = null;
+
+ if (target != null)
+ {
+ currentTerm = pe;
+ }
+ }
+
+ var sourceTopDist = geo[0][1] - (geo[1][1] + geo[1][3]);
+ var sourceLeftDist = geo[0][0] - (geo[1][0] + geo[1][2]);
+ var sourceBottomDist = geo[1][1] - (geo[0][1] + geo[0][3]);
+ var sourceRightDist = geo[1][0] - (geo[0][0] + geo[0][2]);
+
+ mxEdgeStyle.vertexSeperations[1] = Math.max(
+ sourceLeftDist - 2 * scaledOrthBuffer, 0);
+ mxEdgeStyle.vertexSeperations[2] = Math.max(sourceTopDist - 2 * scaledOrthBuffer,
+ 0);
+ mxEdgeStyle.vertexSeperations[4] = Math.max(sourceBottomDist - 2
+ * scaledOrthBuffer, 0);
+ mxEdgeStyle.vertexSeperations[3] = Math.max(sourceRightDist - 2
+ * scaledOrthBuffer, 0);
+
+ //==============================================================
+ // Start of source and target direction determination
+
+ // Work through the preferred orientations by relative positioning
+ // of the vertices and list them in preferred and available order
+
+ var dirPref = [];
+ var horPref = [];
+ var vertPref = [];
+
+ horPref[0] = (sourceLeftDist >= sourceRightDist) ? mxConstants.DIRECTION_MASK_WEST
+ : mxConstants.DIRECTION_MASK_EAST;
+ vertPref[0] = (sourceTopDist >= sourceBottomDist) ? mxConstants.DIRECTION_MASK_NORTH
+ : mxConstants.DIRECTION_MASK_SOUTH;
+
+ horPref[1] = mxUtils.reversePortConstraints(horPref[0]);
+ vertPref[1] = mxUtils.reversePortConstraints(vertPref[0]);
+
+ var preferredHorizDist = sourceLeftDist >= sourceRightDist ? sourceLeftDist
+ : sourceRightDist;
+ var preferredVertDist = sourceTopDist >= sourceBottomDist ? sourceTopDist
+ : sourceBottomDist;
+
+ var prefOrdering = [ [0, 0] , [0, 0] ];
+ var preferredOrderSet = false;
+
+ // If the preferred port isn't available, switch it
+ for (var i = 0; i < 2; i++)
+ {
+ if (dir[i] != 0x0)
+ {
+ continue;
+ }
+
+ if ((horPref[i] & portConstraint[i]) == 0)
+ {
+ horPref[i] = mxUtils.reversePortConstraints(horPref[i]);
+ }
+
+ if ((vertPref[i] & portConstraint[i]) == 0)
+ {
+ vertPref[i] = mxUtils
+ .reversePortConstraints(vertPref[i]);
+ }
+
+ prefOrdering[i][0] = vertPref[i];
+ prefOrdering[i][1] = horPref[i];
+ }
+
+ if (preferredVertDist > scaledOrthBuffer * 2
+ && preferredHorizDist > scaledOrthBuffer * 2)
+ {
+ // Possibility of two segment edge connection
+ if (((horPref[0] & portConstraint[0]) > 0)
+ && ((vertPref[1] & portConstraint[1]) > 0))
+ {
+ prefOrdering[0][0] = horPref[0];
+ prefOrdering[0][1] = vertPref[0];
+ prefOrdering[1][0] = vertPref[1];
+ prefOrdering[1][1] = horPref[1];
+ preferredOrderSet = true;
+ }
+ else if (((vertPref[0] & portConstraint[0]) > 0)
+ && ((horPref[1] & portConstraint[1]) > 0))
+ {
+ prefOrdering[0][0] = vertPref[0];
+ prefOrdering[0][1] = horPref[0];
+ prefOrdering[1][0] = horPref[1];
+ prefOrdering[1][1] = vertPref[1];
+ preferredOrderSet = true;
+ }
+ }
+ if (preferredVertDist > scaledOrthBuffer * 2 && !preferredOrderSet)
+ {
+ prefOrdering[0][0] = vertPref[0];
+ prefOrdering[0][1] = horPref[0];
+ prefOrdering[1][0] = vertPref[1];
+ prefOrdering[1][1] = horPref[1];
+ preferredOrderSet = true;
+
+ }
+ if (preferredHorizDist > scaledOrthBuffer * 2 && !preferredOrderSet)
+ {
+ prefOrdering[0][0] = horPref[0];
+ prefOrdering[0][1] = vertPref[0];
+ prefOrdering[1][0] = horPref[1];
+ prefOrdering[1][1] = vertPref[1];
+ preferredOrderSet = true;
+ }
+
+ // The source and target prefs are now an ordered list of
+ // the preferred port selections
+ // It the list can contain gaps, compact it
+
+ for (var i = 0; i < 2; i++)
+ {
+ if (dir[i] != 0x0)
+ {
+ continue;
+ }
+
+ if ((prefOrdering[i][0] & portConstraint[i]) == 0)
+ {
+ prefOrdering[i][0] = prefOrdering[i][1];
+ }
+
+ dirPref[i] = prefOrdering[i][0] & portConstraint[i];
+ dirPref[i] |= (prefOrdering[i][1] & portConstraint[i]) << 8;
+ dirPref[i] |= (prefOrdering[1 - i][i] & portConstraint[i]) << 16;
+ dirPref[i] |= (prefOrdering[1 - i][1 - i] & portConstraint[i]) << 24;
+
+ if ((dirPref[i] & 0xF) == 0)
+ {
+ dirPref[i] = dirPref[i] << 8;
+ }
+ if ((dirPref[i] & 0xF00) == 0)
+ {
+ dirPref[i] = (dirPref[i] & 0xF) | dirPref[i] >> 8;
+ }
+ if ((dirPref[i] & 0xF0000) == 0)
+ {
+ dirPref[i] = (dirPref[i] & 0xFFFF)
+ | ((dirPref[i] & 0xF000000) >> 8);
+ }
+
+ dir[i] = dirPref[i] & 0xF;
+
+ if (portConstraint[i] == mxConstants.DIRECTION_MASK_WEST
+ || portConstraint[i] == mxConstants.DIRECTION_MASK_NORTH
+ || portConstraint[i] == mxConstants.DIRECTION_MASK_EAST
+ || portConstraint[i] == mxConstants.DIRECTION_MASK_SOUTH)
+ {
+ dir[i] = portConstraint[i];
+ }
+ }
+
+ //==============================================================
+ // End of source and target direction determination
+
+ var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
+ : dir[0];
+ var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
+ : dir[1];
+
+ sourceIndex -= quad;
+ targetIndex -= quad;
+
+ if (sourceIndex < 1)
+ {
+ sourceIndex += 4;
+ }
+ if (targetIndex < 1)
+ {
+ targetIndex += 4;
+ }
+
+ var routePattern = mxEdgeStyle.routePatterns[sourceIndex - 1][targetIndex - 1];
+
+ mxEdgeStyle.wayPoints1[0][0] = geo[0][0];
+ mxEdgeStyle.wayPoints1[0][1] = geo[0][1];
+
+ switch (dir[0])
+ {
+ case mxConstants.DIRECTION_MASK_WEST:
+ mxEdgeStyle.wayPoints1[0][0] -= scaledOrthBuffer;
+ mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
+ break;
+ case mxConstants.DIRECTION_MASK_SOUTH:
+ mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
+ mxEdgeStyle.wayPoints1[0][1] += geo[0][3] + scaledOrthBuffer;
+ break;
+ case mxConstants.DIRECTION_MASK_EAST:
+ mxEdgeStyle.wayPoints1[0][0] += geo[0][2] + scaledOrthBuffer;
+ mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
+ break;
+ case mxConstants.DIRECTION_MASK_NORTH:
+ mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
+ mxEdgeStyle.wayPoints1[0][1] -= scaledOrthBuffer;
+ break;
+ }
+
+ var currentIndex = 0;
+
+ // Orientation, 0 horizontal, 1 vertical
+ var lastOrientation = (dir[0] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
+ : 1;
+ var initialOrientation = lastOrientation;
+ var currentOrientation = 0;
+
+ for (var i = 0; i < routePattern.length; i++)
+ {
+ var nextDirection = routePattern[i] & 0xF;
+
+ // Rotate the index of this direction by the quad
+ // to get the real direction
+ var directionIndex = nextDirection == mxConstants.DIRECTION_MASK_EAST ? 3
+ : nextDirection;
+
+ directionIndex += quad;
+
+ if (directionIndex > 4)
+ {
+ directionIndex -= 4;
+ }
+
+ var direction = mxEdgeStyle.dirVectors[directionIndex - 1];
+
+ currentOrientation = (directionIndex % 2 > 0) ? 0 : 1;
+ // Only update the current index if the point moved
+ // in the direction of the current segment move,
+ // otherwise the same point is moved until there is
+ // a segment direction change
+ if (currentOrientation != lastOrientation)
+ {
+ currentIndex++;
+ // Copy the previous way point into the new one
+ // We can't base the new position on index - 1
+ // because sometime elbows turn out not to exist,
+ // then we'd have to rewind.
+ mxEdgeStyle.wayPoints1[currentIndex][0] = mxEdgeStyle.wayPoints1[currentIndex - 1][0];
+ mxEdgeStyle.wayPoints1[currentIndex][1] = mxEdgeStyle.wayPoints1[currentIndex - 1][1];
+ }
+
+ var tar = (routePattern[i] & mxEdgeStyle.TARGET_MASK) > 0;
+ var sou = (routePattern[i] & mxEdgeStyle.SOURCE_MASK) > 0;
+ var side = (routePattern[i] & mxEdgeStyle.SIDE_MASK) >> 5;
+ side = side << quad;
+
+ if (side > 0xF)
+ {
+ side = side >> 4;
+ }
+
+ var center = (routePattern[i] & mxEdgeStyle.CENTER_MASK) > 0;
+
+ if ((sou || tar) && side < 9)
+ {
+ var limit = 0;
+ var souTar = sou ? 0 : 1;
+
+ if (center && currentOrientation == 0)
+ {
+ limit = geo[souTar][0] + constraint[souTar][0] * geo[souTar][2];
+ }
+ else if (center)
+ {
+ limit = geo[souTar][1] + constraint[souTar][1] * geo[souTar][3];
+ }
+ else
+ {
+ limit = mxEdgeStyle.limits[souTar][side];
+ }
+
+ if (currentOrientation == 0)
+ {
+ var lastX = mxEdgeStyle.wayPoints1[currentIndex][0];
+ var deltaX = (limit - lastX) * direction[0];
+
+ if (deltaX > 0)
+ {
+ mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
+ * deltaX;
+ }
+ }
+ else
+ {
+ var lastY = mxEdgeStyle.wayPoints1[currentIndex][1];
+ var deltaY = (limit - lastY) * direction[1];
+
+ if (deltaY > 0)
+ {
+ mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
+ * deltaY;
+ }
+ }
+ }
+
+ else if (center)
+ {
+ // Which center we're travelling to depend on the current direction
+ mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
+ * Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
+ mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
+ * Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
+ }
+
+ if (currentIndex > 0
+ && mxEdgeStyle.wayPoints1[currentIndex][currentOrientation] == mxEdgeStyle.wayPoints1[currentIndex - 1][currentOrientation])
+ {
+ currentIndex--;
+ }
+ else
+ {
+ lastOrientation = currentOrientation;
+ }
+ }
+
+ for (var i = 0; i <= currentIndex; i++)
+ {
+ if (i == currentIndex)
+ {
+ // Last point can cause last segment to be in
+ // same direction as jetty/approach. If so,
+ // check the number of points is consistent
+ // with the relative orientation of source and target
+ // jettys. Same orientation requires an even
+ // number of turns (points), different requires
+ // odd.
+ var targetOrientation = (dir[1] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
+ : 1;
+ var sameOrient = targetOrientation == initialOrientation ? 0 : 1;
+
+ // (currentIndex + 1) % 2 is 0 for even number of points,
+ // 1 for odd
+ if (sameOrient != (currentIndex + 1) % 2)
+ {
+ // The last point isn't required
+ break;
+ }
+ }
+
+ result.push(new mxPoint(mxEdgeStyle.wayPoints1[i][0], mxEdgeStyle.wayPoints1[i][1]));
+ }
+ },
+
+ getRoutePattern: function(dir, quad, dx, dy)
+ {
+ var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
+ : dir[0];
+ var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
+ : dir[1];
+
+ sourceIndex -= quad;
+ targetIndex -= quad;
+
+ if (sourceIndex < 1)
+ {
+ sourceIndex += 4;
+ }
+ if (targetIndex < 1)
+ {
+ targetIndex += 4;
+ }
+
+ var result = routePatterns[sourceIndex - 1][targetIndex - 1];
+
+ if (dx == 0 || dy == 0)
+ {
+ if (inlineRoutePatterns[sourceIndex - 1][targetIndex - 1] != null)
+ {
+ result = inlineRoutePatterns[sourceIndex - 1][targetIndex - 1];
+ }
+ }
+
+ return result;
+ }
+}; \ No newline at end of file
diff --git a/src/js/view/mxGraph.js b/src/js/view/mxGraph.js
new file mode 100644
index 0000000..7c90f9b
--- /dev/null
+++ b/src/js/view/mxGraph.js
@@ -0,0 +1,11176 @@
+/**
+ * $Id: mxGraph.js,v 1.702 2012-12-13 15:07:34 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraph
+ *
+ * Extends <mxEventSource> to implement a graph component for
+ * the browser. This is the main class of the package. To activate
+ * panning and connections use <setPanning> and <setConnectable>.
+ * For rubberband selection you must create a new instance of
+ * <mxRubberband>. The following listeners are added to
+ * <mouseListeners> by default:
+ *
+ * - <tooltipHandler>: <mxTooltipHandler> that displays tooltips
+ * - <panningHandler>: <mxPanningHandler> for panning and popup menus
+ * - <connectionHandler>: <mxConnectionHandler> for creating connections
+ * - <graphHandler>: <mxGraphHandler> for moving and cloning cells
+ *
+ * These listeners will be called in the above order if they are enabled.
+ *
+ * Background Images:
+ *
+ * To display a background image, set the image, image width and
+ * image height using <setBackgroundImage>. If one of the
+ * above values has changed then the <view>'s <mxGraphView.validate>
+ * should be invoked.
+ *
+ * Cell Images:
+ *
+ * To use images in cells, a shape must be specified in the default
+ * vertex style (or any named style). Possible shapes are
+ * <mxConstants.SHAPE_IMAGE> and <mxConstants.SHAPE_LABEL>.
+ * The code to change the shape used in the default vertex style,
+ * the following code is used:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE;
+ * (end)
+ *
+ * For the default vertex style, the image to be displayed can be
+ * specified in a cell's style using the <mxConstants.STYLE_IMAGE>
+ * key and the image URL as a value, for example:
+ *
+ * (code)
+ * image=http://www.example.com/image.gif
+ * (end)
+ *
+ * For a named style, the the stylename must be the first element
+ * of the cell style:
+ *
+ * (code)
+ * stylename;image=http://www.example.com/image.gif
+ * (end)
+ *
+ * A cell style can have any number of key=value pairs added, divided
+ * by a semicolon as follows:
+ *
+ * (code)
+ * [stylename;|key=value;]
+ * (end)
+ *
+ * Labels:
+ *
+ * The cell labels are defined by <getLabel> which uses <convertValueToString>
+ * if <labelsVisible> is true. If a label must be rendered as HTML markup, then
+ * <isHtmlLabel> should return true for the respective cell. If all labels
+ * contain HTML markup, <htmlLabels> can be set to true. NOTE: Enabling HTML
+ * labels carries a possible security risk (see the section on security in
+ * the manual).
+ *
+ * If wrapping is needed for a label, then <isHtmlLabel> and <isWrapping> must
+ * return true for the cell whose label should be wrapped. See <isWrapping> for
+ * an example.
+ *
+ * If clipping is needed to keep the rendering of a HTML label inside the
+ * bounds of its vertex, then <isClipping> should return true for the
+ * respective cell.
+ *
+ * By default, edge labels are movable and vertex labels are fixed. This can be
+ * changed by setting <edgeLabelsMovable> and <vertexLabelsMovable>, or by
+ * overriding <isLabelMovable>.
+ *
+ * In-place Editing:
+ *
+ * In-place editing is started with a doubleclick or by typing F2.
+ * Programmatically, <edit> is used to check if the cell is editable
+ * (<isCellEditable>) and call <startEditingAtCell>, which invokes
+ * <mxCellEditor.startEditing>. The editor uses the value returned
+ * by <getEditingValue> as the editing value.
+ *
+ * After in-place editing, <labelChanged> is called, which invokes
+ * <mxGraphModel.setValue>, which in turn calls
+ * <mxGraphModel.valueForCellChanged> via <mxValueChange>.
+ *
+ * The event that triggers in-place editing is passed through to the
+ * <cellEditor>, which may take special actions depending on the type of the
+ * event or mouse location, and is also passed to <getEditingValue>. The event
+ * is then passed back to the event processing functions which can perform
+ * specific actions based on the trigger event.
+ *
+ * Tooltips:
+ *
+ * Tooltips are implemented by <getTooltip>, which calls <getTooltipForCell>
+ * if a cell is under the mousepointer. The default implementation checks if
+ * the cell has a getTooltip function and calls it if it exists. Hence, in order
+ * to provide custom tooltips, the cell must provide a getTooltip function, or
+ * one of the two above functions must be overridden.
+ *
+ * Typically, for custom cell tooltips, the latter function is overridden as
+ * follows:
+ *
+ * (code)
+ * graph.getTooltipForCell = function(cell)
+ * {
+ * var label = this.convertValueToString(cell);
+ * return 'Tooltip for '+label;
+ * }
+ * (end)
+ *
+ * When using a config file, the function is overridden in the mxGraph section
+ * using the following entry:
+ *
+ * (code)
+ * <add as="getTooltipForCell"><![CDATA[
+ * function(cell)
+ * {
+ * var label = this.convertValueToString(cell);
+ * return 'Tooltip for '+label;
+ * }
+ * ]]></add>
+ * (end)
+ *
+ * "this" refers to the graph in the implementation, so for example to check if
+ * a cell is an edge, you use this.getModel().isEdge(cell)
+ *
+ * For replacing the default implementation of <getTooltipForCell> (rather than
+ * replacing the function on a specific instance), the following code should be
+ * used after loading the JavaScript files, but before creating a new mxGraph
+ * instance using <mxGraph>:
+ *
+ * (code)
+ * mxGraph.prototype.getTooltipForCell = function(cell)
+ * {
+ * var label = this.convertValueToString(cell);
+ * return 'Tooltip for '+label;
+ * }
+ * (end)
+ *
+ * Shapes & Styles:
+ *
+ * The implementation of new shapes is demonstrated in the examples. We'll assume
+ * that we have implemented a custom shape with the name BoxShape which we want
+ * to use for drawing vertices. To use this shape, it must first be registered in
+ * the cell renderer as follows:
+ *
+ * (code)
+ * graph.cellRenderer.registerShape('box', BoxShape);
+ * (end)
+ *
+ * The code registers the BoxShape constructor under the name box in the cell
+ * renderer of the graph. The shape can now be referenced using the shape-key in
+ * a style definition. (The cell renderer contains a set of additional shapes,
+ * namely one for each constant with a SHAPE-prefix in <mxConstants>.)
+ *
+ * Styles are a collection of key, value pairs and a stylesheet is a collection
+ * of named styles. The names are referenced by the cellstyle, which is stored
+ * in <mxCell.style> with the following format: [stylename;|key=value;]. The
+ * string is resolved to a collection of key, value pairs, where the keys are
+ * overridden with the values in the string.
+ *
+ * When introducing a new shape, the name under which the shape is registered
+ * must be used in the stylesheet. There are three ways of doing this:
+ *
+ * - By changing the default style, so that all vertices will use the new
+ * shape
+ * - By defining a new style, so that only vertices with the respective
+ * cellstyle will use the new shape
+ * - By using shape=box in the cellstyle's optional list of key, value pairs
+ * to be overridden
+ *
+ * In the first case, the code to fetch and modify the default style for
+ * vertices is as follows:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = 'box';
+ * (end)
+ *
+ * The code takes the default vertex style, which is used for all vertices that
+ * do not have a specific cellstyle, and modifies the value for the shape-key
+ * in-place to use the new BoxShape for drawing vertices. This is done by
+ * assigning the box value in the second line, which refers to the name of the
+ * BoxShape in the cell renderer.
+ *
+ * In the second case, a collection of key, value pairs is created and then
+ * added to the stylesheet under a new name. In order to distinguish the
+ * shapename and the stylename we'll use boxstyle for the stylename:
+ *
+ * (code)
+ * var style = new Object();
+ * style[mxConstants.STYLE_SHAPE] = 'box';
+ * style[mxConstants.STYLE_STROKECOLOR] = '#000000';
+ * style[mxConstants.STYLE_FONTCOLOR] = '#000000';
+ * graph.getStylesheet().putCellStyle('boxstyle', style);
+ * (end)
+ *
+ * The code adds a new style with the name boxstyle to the stylesheet. To use
+ * this style with a cell, it must be referenced from the cellstyle as follows:
+ *
+ * (code)
+ * var vertex = graph.insertVertex(parent, null, 'Hello, World!', 20, 20, 80, 20,
+ * 'boxstyle');
+ * (end)
+ *
+ * To summarize, each new shape must be registered in the <mxCellRenderer> with
+ * a unique name. That name is then used as the value of the shape-key in a
+ * default or custom style. If there are multiple custom shapes, then there
+ * should be a separate style for each shape.
+ *
+ * Inheriting Styles:
+ *
+ * For fill-, stroke-, gradient- and indicatorColors special keywords can be
+ * used. The inherit keyword for one of these colors will inherit the color
+ * for the same key from the parent cell. The swimlane keyword does the same,
+ * but inherits from the nearest swimlane in the ancestor hierarchy. Finally,
+ * the indicated keyword will use the color of the indicator as the color for
+ * the given key.
+ *
+ * Scrollbars:
+ *
+ * The <containers> overflow CSS property defines if scrollbars are used to
+ * display the graph. For values of 'auto' or 'scroll', the scrollbars will
+ * be shown. Note that the <resizeContainer> flag is normally not used
+ * together with scrollbars, as it will resize the container to match the
+ * size of the graph after each change.
+ *
+ * Multiplicities and Validation:
+ *
+ * To control the possible connections in mxGraph, <getEdgeValidationError> is
+ * used. The default implementation of the function uses <multiplicities>,
+ * which is an array of <mxMultiplicity>. Using this class allows to establish
+ * simple multiplicities, which are enforced by the graph.
+ *
+ * The <mxMultiplicity> uses <mxCell.is> to determine for which terminals it
+ * applies. The default implementation of <mxCell.is> works with DOM nodes (XML
+ * nodes) and checks if the given type parameter matches the nodeName of the
+ * node (case insensitive). Optionally, an attributename and value can be
+ * specified which are also checked.
+ *
+ * <getEdgeValidationError> is called whenever the connectivity of an edge
+ * changes. It returns an empty string or an error message if the edge is
+ * invalid or null if the edge is valid. If the returned string is not empty
+ * then it is displayed as an error message.
+ *
+ * <mxMultiplicity> allows to specify the multiplicity between a terminal and
+ * its possible neighbors. For example, if any rectangle may only be connected
+ * to, say, a maximum of two circles you can add the following rule to
+ * <multiplicities>:
+ *
+ * (code)
+ * graph.multiplicities.push(new mxMultiplicity(
+ * true, 'rectangle', null, null, 0, 2, ['circle'],
+ * 'Only 2 targets allowed',
+ * 'Only shape targets allowed'));
+ * (end)
+ *
+ * This will display the first error message whenever a rectangle is connected
+ * to more than two circles and the second error message if a rectangle is
+ * connected to anything but a circle.
+ *
+ * For certain multiplicities, such as a minimum of 1 connection, which cannot
+ * be enforced at cell creation time (unless the cell is created together with
+ * the connection), mxGraph offers <validate> which checks all multiplicities
+ * for all cells and displays the respective error messages in an overlay icon
+ * on the cells.
+ *
+ * If a cell is collapsed and contains validation errors, a respective warning
+ * icon is attached to the collapsed cell.
+ *
+ * Auto-Layout:
+ *
+ * For automatic layout, the <getLayout> hook is provided in <mxLayoutManager>.
+ * It can be overridden to return a layout algorithm for the children of a
+ * given cell.
+ *
+ * Unconnected edges:
+ *
+ * The default values for all switches are designed to meet the requirements of
+ * general diagram drawing applications. A very typical set of settings to
+ * avoid edges that are not connected is the following:
+ *
+ * (code)
+ * graph.setAllowDanglingEdges(false);
+ * graph.setDisconnectOnMove(false);
+ * (end)
+ *
+ * Setting the <cloneInvalidEdges> switch to true is optional. This switch
+ * controls if edges are inserted after a copy, paste or clone-drag if they are
+ * invalid. For example, edges are invalid if copied or control-dragged without
+ * having selected the corresponding terminals and allowDanglingEdges is
+ * false, in which case the edges will not be cloned if the switch is false.
+ *
+ * Output:
+ *
+ * To produce an XML representation for a diagram, the following code can be
+ * used.
+ *
+ * (code)
+ * var enc = new mxCodec(mxUtils.createXmlDocument());
+ * var node = enc.encode(graph.getModel());
+ * (end)
+ *
+ * This will produce an XML node than can be handled using the DOM API or
+ * turned into a string representation using the following code:
+ *
+ * (code)
+ * var xml = mxUtils.getXml(node);
+ * (end)
+ *
+ * To obtain a formatted string, mxUtils.getPrettyXml can be used instead.
+ *
+ * This string can now be stored in a local persistent storage (for example
+ * using Google Gears) or it can be passed to a backend using mxUtils.post as
+ * follows. The url variable is the URL of the Java servlet, PHP page or HTTP
+ * handler, depending on the server.
+ *
+ * (code)
+ * var xmlString = encodeURIComponent(mxUtils.getXml(node));
+ * mxUtils.post(url, 'xml='+xmlString, function(req)
+ * {
+ * // Process server response using req of type mxXmlRequest
+ * });
+ * (end)
+ *
+ * Input:
+ *
+ * To load an XML representation of a diagram into an existing graph object
+ * mxUtils.load can be used as follows. The url variable is the URL of the Java
+ * servlet, PHP page or HTTP handler that produces the XML string.
+ *
+ * (code)
+ * var xmlDoc = mxUtils.load(url).getXml();
+ * var node = xmlDoc.documentElement;
+ * var dec = new mxCodec(node.ownerDocument);
+ * dec.decode(node, graph.getModel());
+ * (end)
+ *
+ * For creating a page that loads the client and a diagram using a single
+ * request please refer to the deployment examples in the backends.
+ *
+ * Functional dependencies:
+ *
+ * (see images/callgraph.png)
+ *
+ * Resources:
+ *
+ * resources/graph - Language resources for mxGraph
+ *
+ * Group: Events
+ *
+ * Event: mxEvent.ROOT
+ *
+ * Fires if the root in the model has changed. This event has no properties.
+ *
+ * Event: mxEvent.ALIGN_CELLS
+ *
+ * Fires between begin- and endUpdate in <alignCells>. The <code>cells</code>
+ * and <code>align</code> properties contain the respective arguments that were
+ * passed to <alignCells>.
+ *
+ * Event: mxEvent.FLIP_EDGE
+ *
+ * Fires between begin- and endUpdate in <flipEdge>. The <code>edge</code>
+ * property contains the edge passed to <flipEdge>.
+ *
+ * Event: mxEvent.ORDER_CELLS
+ *
+ * Fires between begin- and endUpdate in <orderCells>. The <code>cells</code>
+ * and <code>back</code> properties contain the respective arguments that were
+ * passed to <orderCells>.
+ *
+ * Event: mxEvent.CELLS_ORDERED
+ *
+ * Fires between begin- and endUpdate in <cellsOrdered>. The <code>cells</code>
+ * and <code>back</code> arguments contain the respective arguments that were
+ * passed to <cellsOrdered>.
+ *
+ * Event: mxEvent.GROUP_CELLS
+ *
+ * Fires between begin- and endUpdate in <groupCells>. The <code>group</code>,
+ * <code>cells</code> and <code>border</code> arguments contain the respective
+ * arguments that were passed to <groupCells>.
+ *
+ * Event: mxEvent.UNGROUP_CELLS
+ *
+ * Fires between begin- and endUpdate in <ungroupCells>. The <code>cells</code>
+ * property contains the array of cells that was passed to <ungroupCells>.
+ *
+ * Event: mxEvent.REMOVE_CELLS_FROM_PARENT
+ *
+ * Fires between begin- and endUpdate in <removeCellsFromParent>. The
+ * <code>cells</code> property contains the array of cells that was passed to
+ * <removeCellsFromParent>.
+ *
+ * Event: mxEvent.ADD_CELLS
+ *
+ * Fires between begin- and endUpdate in <addCells>. The <code>cells</code>,
+ * <code>parent</code>, <code>index</code>, <code>source</code> and
+ * <code>target</code> properties contain the respective arguments that were
+ * passed to <addCells>.
+ *
+ * Event: mxEvent.CELLS_ADDED
+ *
+ * Fires between begin- and endUpdate in <cellsAdded>. The <code>cells</code>,
+ * <code>parent</code>, <code>index</code>, <code>source</code>,
+ * <code>target</code> and <code>absolute</code> properties contain the
+ * respective arguments that were passed to <cellsAdded>.
+ *
+ * Event: mxEvent.REMOVE_CELLS
+ *
+ * Fires between begin- and endUpdate in <removeCells>. The <code>cells</code>
+ * and <code>includeEdges</code> arguments contain the respective arguments
+ * that were passed to <removeCells>.
+ *
+ * Event: mxEvent.CELLS_REMOVED
+ *
+ * Fires between begin- and endUpdate in <cellsRemoved>. The <code>cells</code>
+ * argument contains the array of cells that was removed.
+ *
+ * Event: mxEvent.SPLIT_EDGE
+ *
+ * Fires between begin- and endUpdate in <splitEdge>. The <code>edge</code>
+ * property contains the edge to be splitted, the <code>cells</code>,
+ * <code>newEdge</code>, <code>dx</code> and <code>dy</code> properties contain
+ * the respective arguments that were passed to <splitEdge>.
+ *
+ * Event: mxEvent.TOGGLE_CELLS
+ *
+ * Fires between begin- and endUpdate in <toggleCells>. The <code>show</code>,
+ * <code>cells</code> and <code>includeEdges</code> properties contain the
+ * respective arguments that were passed to <toggleCells>.
+ *
+ * Event: mxEvent.FOLD_CELLS
+ *
+ * Fires between begin- and endUpdate in <foldCells>. The
+ * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
+ * properties contain the respective arguments that were passed to <foldCells>.
+ *
+ * Event: mxEvent.CELLS_FOLDED
+ *
+ * Fires between begin- and endUpdate in cellsFolded. The
+ * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
+ * properties contain the respective arguments that were passed to
+ * <cellsFolded>.
+ *
+ * Event: mxEvent.UPDATE_CELL_SIZE
+ *
+ * Fires between begin- and endUpdate in <updateCellSize>. The
+ * <code>cell</code> and <code>ignoreChildren</code> properties contain the
+ * respective arguments that were passed to <updateCellSize>.
+ *
+ * Event: mxEvent.RESIZE_CELLS
+ *
+ * Fires between begin- and endUpdate in <resizeCells>. The <code>cells</code>
+ * and <code>bounds</code> properties contain the respective arguments that
+ * were passed to <resizeCells>.
+ *
+ * Event: mxEvent.CELLS_RESIZED
+ *
+ * Fires between begin- and endUpdate in <cellsResized>. The <code>cells</code>
+ * and <code>bounds</code> properties contain the respective arguments that
+ * were passed to <cellsResized>.
+ *
+ * Event: mxEvent.MOVE_CELLS
+ *
+ * Fires between begin- and endUpdate in <moveCells>. The <code>cells</code>,
+ * <code>dx</code>, <code>dy</code>, <code>clone</code>, <code>target</code>
+ * and <code>event</code> properties contain the respective arguments that
+ * were passed to <moveCells>.
+ *
+ * Event: mxEvent.CELLS_MOVED
+ *
+ * Fires between begin- and endUpdate in <cellsMoved>. The <code>cells</code>,
+ * <code>dx</code>, <code>dy</code> and <code>disconnect</code> properties
+ * contain the respective arguments that were passed to <cellsMoved>.
+ *
+ * Event: mxEvent.CONNECT_CELL
+ *
+ * Fires between begin- and endUpdate in <connectCell>. The <code>edge</code>,
+ * <code>terminal</code> and <code>source</code> properties contain the
+ * respective arguments that were passed to <connectCell>.
+ *
+ * Event: mxEvent.CELL_CONNECTED
+ *
+ * Fires between begin- and endUpdate in <cellConnected>. The
+ * <code>edge</code>, <code>terminal</code> and <code>source</code> properties
+ * contain the respective arguments that were passed to <cellConnected>.
+ *
+ * Event: mxEvent.REFRESH
+ *
+ * Fires after <refresh> was executed. This event has no properties.
+ *
+ * Event: mxEvent.CLICK
+ *
+ * Fires in <click> after a click event. The <code>event</code> property
+ * contains the original mouse event and <code>cell</code> property contains
+ * the cell under the mouse or null if the background was clicked.
+ *
+ * To handle a click event, use the following code:
+ *
+ * (code)
+ * graph.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ * var e = evt.getProperty('event'); // mouse event
+ * var cell = evt.getProperty('cell'); // cell may be null
+ *
+ * if (!evt.isConsumed())
+ * {
+ * if (cell != null)
+ * {
+ * // Do something useful with cell and consume the event
+ * evt.consume();
+ * }
+ * }
+ * });
+ * (end)
+ *
+ * Event: mxEvent.DOUBLE_CLICK
+ *
+ * Fires in <dblClick> after a double click. The <code>event</code> property
+ * contains the original mouse event and the <code>cell</code> property
+ * contains the cell under the mouse or null if the background was clicked.
+ *
+ * Event: mxEvent.SIZE
+ *
+ * Fires after <sizeDidChange> was executed. The <code>bounds</code> property
+ * contains the new graph bounds.
+ *
+ * Event: mxEvent.START_EDITING
+ *
+ * Fires before the in-place editor starts in <startEditingAtCell>. The
+ * <code>cell</code> property contains the cell that is being edited and the
+ * <code>event</code> property contains the optional event argument that was
+ * passed to <startEditingAtCell>.
+ *
+ * Event: mxEvent.LABEL_CHANGED
+ *
+ * Fires between begin- and endUpdate in <cellLabelChanged>. The
+ * <code>cell</code> property contains the cell, the <code>value</code>
+ * property contains the new value for the cell and the optional
+ * <code>event</code> property contains the mouse event that started the edit.
+ *
+ * Event: mxEvent.ADD_OVERLAY
+ *
+ * Fires after an overlay is added in <addCellOverlay>. The <code>cell</code>
+ * property contains the cell and the <code>overlay</code> property contains
+ * the <mxCellOverlay> that was added.
+ *
+ * Event: mxEvent.REMOVE_OVERLAY
+ *
+ * Fires after an overlay is removed in <removeCellOverlay> and
+ * <removeCellOverlays>. The <code>cell</code> property contains the cell and
+ * the <code>overlay</code> property contains the <mxCellOverlay> that was
+ * removed.
+ *
+ * Constructor: mxGraph
+ *
+ * Constructs a new mxGraph in the specified container. Model is an optional
+ * mxGraphModel. If no model is provided, a new mxGraphModel instance is
+ * used as the model. The container must have a valid owner document prior
+ * to calling this function in Internet Explorer. RenderHint is a string to
+ * affect the display performance and rendering in IE, but not in SVG-based
+ * browsers. The parameter is mapped to <dialect>, which may
+ * be one of <mxConstants.DIALECT_SVG> for SVG-based browsers,
+ * <mxConstants.DIALECT_STRICTHTML> for fastest display mode,
+ * <mxConstants.DIALECT_PREFERHTML> for faster display mode,
+ * <mxConstants.DIALECT_MIXEDHTML> for fast and <mxConstants.DIALECT_VML>
+ * for exact display mode (slowest). The dialects are defined in mxConstants.
+ * The default values are DIALECT_SVG for SVG-based browsers and
+ * DIALECT_MIXED for IE.
+ *
+ * The possible values for the renderingHint parameter are explained below:
+ *
+ * fast - The parameter is based on the fact that the display performance is
+ * highly improved in IE if the VML is not contained within a VML group
+ * element. The lack of a group element only slightly affects the display while
+ * panning, but improves the performance by almost a factor of 2, while keeping
+ * the display sufficiently accurate. This also allows to render certain shapes as HTML
+ * if the display accuracy is not affected, which is implemented by
+ * <mxShape.isMixedModeHtml>. This is the default setting and is mapped to
+ * DIALECT_MIXEDHTML.
+ * faster - Same as fast, but more expensive shapes are avoided. This is
+ * controlled by <mxShape.preferModeHtml>. The default implementation will
+ * avoid gradients and rounded rectangles, but more significant shapes, such
+ * as rhombus, ellipse, actor and cylinder will be rendered accurately. This
+ * setting is mapped to DIALECT_PREFERHTML.
+ * fastest - Almost anything will be rendered in Html. This allows for
+ * rectangles, labels and images. This setting is mapped to
+ * DIALECT_STRICTHTML.
+ * exact - If accurate panning is required and if the diagram is small (up
+ * to 100 cells), then this value should be used. In this mode, a group is
+ * created that contains the VML. This allows for accurate panning and is
+ * mapped to DIALECT_VML.
+ *
+ * Example:
+ *
+ * To create a graph inside a DOM node with an id of graph:
+ * (code)
+ * var container = document.getElementById('graph');
+ * var graph = new mxGraph(container);
+ * (end)
+ *
+ * Parameters:
+ *
+ * container - Optional DOM node that acts as a container for the graph.
+ * If this is null then the container can be initialized later using
+ * <init>.
+ * model - Optional <mxGraphModel> that constitutes the graph data.
+ * renderHint - Optional string that specifies the display accuracy and
+ * performance. Default is mxConstants.DIALECT_MIXEDHTML (for IE).
+ * stylesheet - Optional <mxStylesheet> to be used in the graph.
+ */
+function mxGraph(container, model, renderHint, stylesheet)
+{
+ // Initializes the variable in case the prototype has been
+ // modified to hold some listeners (which is possible because
+ // the createHandlers call is executed regardless of the
+ // arguments passed into the ctor).
+ this.mouseListeners = null;
+
+ // Converts the renderHint into a dialect
+ this.renderHint = renderHint;
+
+ if (mxClient.IS_SVG)
+ {
+ this.dialect = mxConstants.DIALECT_SVG;
+ }
+ else if (renderHint == mxConstants.RENDERING_HINT_EXACT && mxClient.IS_VML)
+ {
+ this.dialect = mxConstants.DIALECT_VML;
+ }
+ else if (renderHint == mxConstants.RENDERING_HINT_FASTEST)
+ {
+ this.dialect = mxConstants.DIALECT_STRICTHTML;
+ }
+ else if (renderHint == mxConstants.RENDERING_HINT_FASTER)
+ {
+ this.dialect = mxConstants.DIALECT_PREFERHTML;
+ }
+ else // default for VML
+ {
+ this.dialect = mxConstants.DIALECT_MIXEDHTML;
+ }
+
+ // Initializes the main members that do not require a container
+ this.model = (model != null) ? model : new mxGraphModel();
+ this.multiplicities = [];
+ this.imageBundles = [];
+ this.cellRenderer = this.createCellRenderer();
+ this.setSelectionModel(this.createSelectionModel());
+ this.setStylesheet((stylesheet != null) ? stylesheet : this.createStylesheet());
+ this.view = this.createGraphView();
+
+ // Adds a graph model listener to update the view
+ this.graphModelChangeListener = mxUtils.bind(this, function(sender, evt)
+ {
+ this.graphModelChanged(evt.getProperty('edit').changes);
+ });
+
+ this.model.addListener(mxEvent.CHANGE, this.graphModelChangeListener);
+
+ // Installs basic event handlers with disabled default settings.
+ this.createHandlers();
+
+ // Initializes the display if a container was specified
+ if (container != null)
+ {
+ this.init(container);
+ }
+
+ this.view.revalidate();
+};
+
+/**
+ * Installs the required language resources at class
+ * loading time.
+ */
+if (mxLoadResources)
+{
+ mxResources.add(mxClient.basePath+'/resources/graph');
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraph.prototype = new mxEventSource();
+mxGraph.prototype.constructor = mxGraph;
+
+/**
+ * Variable: EMPTY_ARRAY
+ *
+ * Immutable empty array instance.
+ */
+mxGraph.prototype.EMPTY_ARRAY = [];
+
+/**
+ * Group: Variables
+ */
+
+/**
+ * Variable: mouseListeners
+ *
+ * Holds the mouse event listeners. See <fireMouseEvent>.
+ */
+mxGraph.prototype.mouseListeners = null;
+
+/**
+ * Variable: isMouseDown
+ *
+ * Holds the state of the mouse button.
+ */
+mxGraph.prototype.isMouseDown = false;
+
+/**
+ * Variable: model
+ *
+ * Holds the <mxGraphModel> that contains the cells to be displayed.
+ */
+mxGraph.prototype.model = null;
+
+/**
+ * Variable: view
+ *
+ * Holds the <mxGraphView> that caches the <mxCellStates> for the cells.
+ */
+mxGraph.prototype.view = null;
+
+/**
+ * Variable: stylesheet
+ *
+ * Holds the <mxStylesheet> that defines the appearance of the cells.
+ *
+ *
+ * Example:
+ *
+ * Use the following code to read a stylesheet into an existing graph.
+ *
+ * (code)
+ * var req = mxUtils.load('stylesheet.xml');
+ * var root = req.getDocumentElement();
+ * var dec = new mxCodec(root.ownerDocument);
+ * dec.decode(root, graph.stylesheet);
+ * (end)
+ */
+mxGraph.prototype.stylesheet = null;
+
+/**
+ * Variable: selectionModel
+ *
+ * Holds the <mxGraphSelectionModel> that models the current selection.
+ */
+mxGraph.prototype.selectionModel = null;
+
+/**
+ * Variable: cellEditor
+ *
+ * Holds the <mxCellEditor> that is used as the in-place editing.
+ */
+mxGraph.prototype.cellEditor = null;
+
+/**
+ * Variable: cellRenderer
+ *
+ * Holds the <mxCellRenderer> for rendering the cells in the graph.
+ */
+mxGraph.prototype.cellRenderer = null;
+
+/**
+ * Variable: multiplicities
+ *
+ * An array of <mxMultiplicities> describing the allowed
+ * connections in a graph.
+ */
+mxGraph.prototype.multiplicities = null;
+
+/**
+ * Variable: renderHint
+ *
+ * RenderHint as it was passed to the constructor.
+ */
+mxGraph.prototype.renderHint = null;
+
+/**
+ * Variable: dialect
+ *
+ * Dialect to be used for drawing the graph. Possible values are all
+ * constants in <mxConstants> with a DIALECT-prefix.
+ */
+mxGraph.prototype.dialect = null;
+
+/**
+ * Variable: gridSize
+ *
+ * Specifies the grid size. Default is 10.
+ */
+mxGraph.prototype.gridSize = 10;
+
+/**
+ * Variable: gridEnabled
+ *
+ * Specifies if the grid is enabled. This is used in <snap>. Default is
+ * true.
+ */
+mxGraph.prototype.gridEnabled = true;
+
+/**
+ * Variable: portsEnabled
+ *
+ * Specifies if ports are enabled. This is used in <cellConnected> to update
+ * the respective style. Default is true.
+ */
+mxGraph.prototype.portsEnabled = true;
+
+/**
+ * Variable: doubleTapEnabled
+ *
+ * Specifies if double taps on touch-based devices should be handled. Default
+ * is true.
+ */
+mxGraph.prototype.doubleTapEnabled = true;
+
+/**
+ * Variable: doubleTapTimeout
+ *
+ * Specifies the timeout for double taps. Default is 700 ms.
+ */
+mxGraph.prototype.doubleTapTimeout = 700;
+
+/**
+ * Variable: doubleTapTolerance
+ *
+ * Specifies the tolerance for double taps. Default is 25 pixels.
+ */
+mxGraph.prototype.doubleTapTolerance = 25;
+
+/**
+ * Variable: lastTouchX
+ *
+ * Holds the x-coordinate of the last touch event for double tap detection.
+ */
+mxGraph.prototype.lastTouchY = 0;
+
+/**
+ * Variable: lastTouchX
+ *
+ * Holds the y-coordinate of the last touch event for double tap detection.
+ */
+mxGraph.prototype.lastTouchY = 0;
+
+/**
+ * Variable: lastTouchTime
+ *
+ * Holds the time of the last touch event for double click detection.
+ */
+mxGraph.prototype.lastTouchTime = 0;
+
+/**
+ * Variable: gestureEnabled
+ *
+ * Specifies if the handleGesture method should be invoked. Default is true. This
+ * is an experimental feature for touch-based devices.
+ */
+mxGraph.prototype.gestureEnabled = true;
+
+/**
+ * Variable: tolerance
+ *
+ * Tolerance for a move to be handled as a single click.
+ * Default is 4 pixels.
+ */
+mxGraph.prototype.tolerance = 4;
+
+/**
+ * Variable: defaultOverlap
+ *
+ * Value returned by <getOverlap> if <isAllowOverlapParent> returns
+ * true for the given cell. <getOverlap> is used in <constrainChild> if
+ * <isConstrainChild> returns true. The value specifies the
+ * portion of the child which is allowed to overlap the parent.
+ */
+mxGraph.prototype.defaultOverlap = 0.5;
+
+/**
+ * Variable: defaultParent
+ *
+ * Specifies the default parent to be used to insert new cells.
+ * This is used in <getDefaultParent>. Default is null.
+ */
+mxGraph.prototype.defaultParent = null;
+
+/**
+ * Variable: alternateEdgeStyle
+ *
+ * Specifies the alternate edge style to be used if the main control point
+ * on an edge is being doubleclicked. Default is null.
+ */
+mxGraph.prototype.alternateEdgeStyle = null;
+
+/**
+ * Variable: backgroundImage
+ *
+ * Specifies the <mxImage> to be returned by <getBackgroundImage>. Default
+ * is null.
+ *
+ * Example:
+ *
+ * (code)
+ * var img = new mxImage('http://www.example.com/maps/examplemap.jpg', 1024, 768);
+ * graph.setBackgroundImage(img);
+ * graph.view.validate();
+ * (end)
+ */
+mxGraph.prototype.backgroundImage = null;
+
+/**
+ * Variable: pageVisible
+ *
+ * Specifies if the background page should be visible. Default is false.
+ * Not yet implemented.
+ */
+mxGraph.prototype.pageVisible = false;
+
+/**
+ * Variable: pageBreaksVisible
+ *
+ * Specifies if a dashed line should be drawn between multiple pages. Default
+ * is false. If you change this value while a graph is being displayed then you
+ * should call <sizeDidChange> to force an update of the display.
+ */
+mxGraph.prototype.pageBreaksVisible = false;
+
+/**
+ * Variable: pageBreakColor
+ *
+ * Specifies the color for page breaks. Default is 'gray'.
+ */
+mxGraph.prototype.pageBreakColor = 'gray';
+
+/**
+ * Variable: pageBreakDashed
+ *
+ * Specifies the page breaks should be dashed. Default is true.
+ */
+mxGraph.prototype.pageBreakDashed = true;
+
+/**
+ * Variable: minPageBreakDist
+ *
+ * Specifies the minimum distance for page breaks to be visible. Default is
+ * 20 (in pixels).
+ */
+mxGraph.prototype.minPageBreakDist = 20;
+
+/**
+ * Variable: preferPageSize
+ *
+ * Specifies if the graph size should be rounded to the next page number in
+ * <sizeDidChange>. This is only used if the graph container has scrollbars.
+ * Default is false.
+ */
+mxGraph.prototype.preferPageSize = false;
+
+/**
+ * Variable: pageFormat
+ *
+ * Specifies the page format for the background page. Default is
+ * <mxConstants.PAGE_FORMAT_A4_PORTRAIT>. This is used as the default in
+ * <mxPrintPreview> and for painting the background page if <pageVisible> is
+ * true and the pagebreaks if <pageBreaksVisible> is true.
+ */
+mxGraph.prototype.pageFormat = mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+
+/**
+ * Variable: pageScale
+ *
+ * Specifies the scale of the background page. Default is 1.5.
+ * Not yet implemented.
+ */
+mxGraph.prototype.pageScale = 1.5;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies the return value for <isEnabled>. Default is true.
+ */
+mxGraph.prototype.enabled = true;
+
+/**
+ * Variable: escapeEnabled
+ *
+ * Specifies if <mxKeyHandler> should invoke <escape> when the escape key
+ * is pressed. Default is true.
+ */
+mxGraph.prototype.escapeEnabled = true;
+
+/**
+ * Variable: invokesStopCellEditing
+ *
+ * If true, when editing is to be stopped by way of selection changing,
+ * data in diagram changing or other means stopCellEditing is invoked, and
+ * changes are saved. This is implemented in a focus handler in
+ * <mxCellEditor>. Default is true.
+ */
+mxGraph.prototype.invokesStopCellEditing = true;
+
+/**
+ * Variable: enterStopsCellEditing
+ *
+ * If true, pressing the enter key without pressing control or shift will stop
+ * editing and accept the new value. This is used in <mxCellEditor> to stop
+ * cell editing. Note: You can always use F2 and escape to stop editing.
+ * Default is false.
+ */
+mxGraph.prototype.enterStopsCellEditing = false;
+
+/**
+ * Variable: useScrollbarsForPanning
+ *
+ * Specifies if scrollbars should be used for panning in <panGraph> if
+ * any scrollbars are available. If scrollbars are enabled in CSS, but no
+ * scrollbars appear because the graph is smaller than the container size,
+ * then no panning occurs if this is true. Default is true.
+ */
+mxGraph.prototype.useScrollbarsForPanning = true;
+
+/**
+ * Variable: exportEnabled
+ *
+ * Specifies the return value for <canExportCell>. Default is true.
+ */
+mxGraph.prototype.exportEnabled = true;
+
+/**
+ * Variable: importEnabled
+ *
+ * Specifies the return value for <canImportCell>. Default is true.
+ */
+mxGraph.prototype.importEnabled = true;
+
+/**
+ * Variable: cellsLocked
+ *
+ * Specifies the return value for <isCellLocked>. Default is false.
+ */
+mxGraph.prototype.cellsLocked = false;
+
+/**
+ * Variable: cellsCloneable
+ *
+ * Specifies the return value for <isCellCloneable>. Default is true.
+ */
+mxGraph.prototype.cellsCloneable = true;
+
+/**
+ * Variable: foldingEnabled
+ *
+ * Specifies if folding (collapse and expand via an image icon in the graph
+ * should be enabled). Default is true.
+ */
+mxGraph.prototype.foldingEnabled = true;
+
+/**
+ * Variable: cellsEditable
+ *
+ * Specifies the return value for <isCellEditable>. Default is true.
+ */
+mxGraph.prototype.cellsEditable = true;
+
+/**
+ * Variable: cellsDeletable
+ *
+ * Specifies the return value for <isCellDeletable>. Default is true.
+ */
+mxGraph.prototype.cellsDeletable = true;
+
+/**
+ * Variable: cellsMovable
+ *
+ * Specifies the return value for <isCellMovable>. Default is true.
+ */
+mxGraph.prototype.cellsMovable = true;
+
+/**
+ * Variable: edgeLabelsMovable
+ *
+ * Specifies the return value for edges in <isLabelMovable>. Default is true.
+ */
+mxGraph.prototype.edgeLabelsMovable = true;
+
+/**
+ * Variable: vertexLabelsMovable
+ *
+ * Specifies the return value for vertices in <isLabelMovable>. Default is false.
+ */
+mxGraph.prototype.vertexLabelsMovable = false;
+
+/**
+ * Variable: dropEnabled
+ *
+ * Specifies the return value for <isDropEnabled>. Default is false.
+ */
+mxGraph.prototype.dropEnabled = false;
+
+/**
+ * Variable: splitEnabled
+ *
+ * Specifies if dropping onto edges should be enabled. Default is true.
+ */
+mxGraph.prototype.splitEnabled = true;
+
+/**
+ * Variable: cellsResizable
+ *
+ * Specifies the return value for <isCellResizable>. Default is true.
+ */
+mxGraph.prototype.cellsResizable = true;
+
+/**
+ * Variable: cellsBendable
+ *
+ * Specifies the return value for <isCellsBendable>. Default is true.
+ */
+mxGraph.prototype.cellsBendable = true;
+
+/**
+ * Variable: cellsSelectable
+ *
+ * Specifies the return value for <isCellSelectable>. Default is true.
+ */
+mxGraph.prototype.cellsSelectable = true;
+
+/**
+ * Variable: cellsDisconnectable
+ *
+ * Specifies the return value for <isCellDisconntable>. Default is true.
+ */
+mxGraph.prototype.cellsDisconnectable = true;
+
+/**
+ * Variable: autoSizeCells
+ *
+ * Specifies if the graph should automatically update the cell size after an
+ * edit. This is used in <isAutoSizeCell>. Default is false.
+ */
+mxGraph.prototype.autoSizeCells = false;
+
+/**
+ * Variable: autoScroll
+ *
+ * Specifies if the graph should automatically scroll if the mouse goes near
+ * the container edge while dragging. This is only taken into account if the
+ * container has scrollbars. Default is true.
+ *
+ * If you need this to work without scrollbars then set <ignoreScrollbars> to
+ * true.
+ */
+mxGraph.prototype.autoScroll = true;
+
+/**
+ * Variable: timerAutoScroll
+ *
+ * Specifies if timer-based autoscrolling should be used via mxPanningManager.
+ * Note that this disables the code in <scrollPointToVisible> and uses code in
+ * mxPanningManager instead. Note that <autoExtend> is disabled if this is
+ * true and that this should only be used with a scroll buffer or when
+ * scollbars are visible and scrollable in all directions. Default is false.
+ */
+mxGraph.prototype.timerAutoScroll = false;
+
+/**
+ * Variable: allowAutoPanning
+ *
+ * Specifies if panning via <panGraph> should be allowed to implement autoscroll
+ * if no scrollbars are available in <scrollPointToVisible>. Default is false.
+ */
+mxGraph.prototype.allowAutoPanning = false;
+
+/**
+ * Variable: ignoreScrollbars
+ *
+ * Specifies if the graph should automatically scroll regardless of the
+ * scrollbars.
+ */
+mxGraph.prototype.ignoreScrollbars = false;
+
+/**
+ * Variable: autoExtend
+ *
+ * Specifies if the size of the graph should be automatically extended if the
+ * mouse goes near the container edge while dragging. This is only taken into
+ * account if the container has scrollbars. Default is true. See <autoScroll>.
+ */
+mxGraph.prototype.autoExtend = true;
+
+/**
+ * Variable: maximumGraphBounds
+ *
+ * <mxRectangle> that specifies the area in which all cells in the diagram
+ * should be placed. Uses in <getMaximumGraphBounds>. Use a width or height of
+ * 0 if you only want to give a upper, left corner.
+ */
+mxGraph.prototype.maximumGraphBounds = null;
+
+/**
+ * Variable: minimumGraphSize
+ *
+ * <mxRectangle> that specifies the minimum size of the graph. This is ignored
+ * if the graph container has no scrollbars. Default is null.
+ */
+mxGraph.prototype.minimumGraphSize = null;
+
+/**
+ * Variable: minimumContainerSize
+ *
+ * <mxRectangle> that specifies the minimum size of the <container> if
+ * <resizeContainer> is true.
+ */
+mxGraph.prototype.minimumContainerSize = null;
+
+/**
+ * Variable: maximumContainerSize
+ *
+ * <mxRectangle> that specifies the maximum size of the container if
+ * <resizeContainer> is true.
+ */
+mxGraph.prototype.maximumContainerSize = null;
+
+/**
+ * Variable: resizeContainer
+ *
+ * Specifies if the container should be resized to the graph size when
+ * the graph size has changed. Default is false.
+ */
+mxGraph.prototype.resizeContainer = false;
+
+/**
+ * Variable: border
+ *
+ * Border to be added to the bottom and right side when the container is
+ * being resized after the graph has been changed. Default is 0.
+ */
+mxGraph.prototype.border = 0;
+
+/**
+ * Variable: ordered
+ *
+ * Specifies if the display should reflect the order of the cells in
+ * the model. Default is true. This has precendence over
+ * <keepEdgesInBackground> and <keepEdgesInForeground>.
+ */
+mxGraph.prototype.ordered = true;
+
+/**
+ * Variable: keepEdgesInForeground
+ *
+ * Specifies if edges should appear in the foreground regardless of their
+ * order in the model. This has precendence over <keepEdgeInBackground>,
+ * but not over <ordered>. Default is false.
+ */
+mxGraph.prototype.keepEdgesInForeground = false;
+
+/**
+ * Variable: keepEdgesInBackground
+ *
+ * Specifies if edges should appear in the background regardless of their
+ * order in the model. <ordered> and <keepEdgesInForeground> have
+ * precedence over this setting. Default is true.
+ */
+mxGraph.prototype.keepEdgesInBackground = true;
+
+/**
+ * Variable: allowNegativeCoordinates
+ *
+ * Specifies if negative coordinates for vertices are allowed. Default is true.
+ */
+mxGraph.prototype.allowNegativeCoordinates = true;
+
+/**
+ * Variable: constrainChildren
+ *
+ * Specifies the return value for <isConstrainChildren>. Default is
+ * true.
+ */
+mxGraph.prototype.constrainChildren = true;
+
+/**
+ * Variable: extendParents
+ *
+ * Specifies if a parent should contain the child bounds after a resize of
+ * the child. Default is true.
+ */
+mxGraph.prototype.extendParents = true;
+
+/**
+ * Variable: extendParentsOnAdd
+ *
+ * Specifies if parents should be extended according to the <extendParents>
+ * switch if cells are added. Default is true.
+ */
+mxGraph.prototype.extendParentsOnAdd = true;
+
+/**
+ * Variable: collapseToPreferredSize
+ *
+ * Specifies if the cell size should be changed to the preferred size when
+ * a cell is first collapsed. Default is true.
+ */
+mxGraph.prototype.collapseToPreferredSize = true;
+
+/**
+ * Variable: zoomFactor
+ *
+ * Specifies the factor used for <zoomIn> and <zoomOut>. Default is 1.2
+ * (120%).
+ */
+mxGraph.prototype.zoomFactor = 1.2;
+
+/**
+ * Variable: keepSelectionVisibleOnZoom
+ *
+ * Specifies if the viewport should automatically contain the selection cells
+ * after a zoom operation. Default is false.
+ */
+mxGraph.prototype.keepSelectionVisibleOnZoom = false;
+
+/**
+ * Variable: centerZoom
+ *
+ * Specifies if the zoom operations should go into the center of the actual
+ * diagram rather than going from top, left. Default is true.
+ */
+mxGraph.prototype.centerZoom = true;
+
+/**
+ * Variable: resetViewOnRootChange
+ *
+ * Specifies if the scale and translate should be reset if the root changes in
+ * the model. Default is true.
+ */
+mxGraph.prototype.resetViewOnRootChange = true;
+
+/**
+ * Variable: resetEdgesOnResize
+ *
+ * Specifies if edge control points should be reset after the resize of a
+ * connected cell. Default is false.
+ */
+mxGraph.prototype.resetEdgesOnResize = false;
+
+/**
+ * Variable: resetEdgesOnMove
+ *
+ * Specifies if edge control points should be reset after the move of a
+ * connected cell. Default is false.
+ */
+mxGraph.prototype.resetEdgesOnMove = false;
+
+/**
+ * Variable: resetEdgesOnConnect
+ *
+ * Specifies if edge control points should be reset after the the edge has been
+ * reconnected. Default is true.
+ */
+mxGraph.prototype.resetEdgesOnConnect = true;
+
+/**
+ * Variable: allowLoops
+ *
+ * Specifies if loops (aka self-references) are allowed. Default is false.
+ */
+mxGraph.prototype.allowLoops = false;
+
+/**
+ * Variable: defaultLoopStyle
+ *
+ * <mxEdgeStyle> to be used for loops. This is a fallback for loops if the
+ * <mxConstants.STYLE_LOOP> is undefined. Default is <mxEdgeStyle.Loop>.
+ */
+mxGraph.prototype.defaultLoopStyle = mxEdgeStyle.Loop;
+
+/**
+ * Variable: multigraph
+ *
+ * Specifies if multiple edges in the same direction between the same pair of
+ * vertices are allowed. Default is true.
+ */
+mxGraph.prototype.multigraph = true;
+
+/**
+ * Variable: connectableEdges
+ *
+ * Specifies if edges are connectable. Default is false. This overrides the
+ * connectable field in edges.
+ */
+mxGraph.prototype.connectableEdges = false;
+
+/**
+ * Variable: allowDanglingEdges
+ *
+ * Specifies if edges with disconnected terminals are allowed in the graph.
+ * Default is true.
+ */
+mxGraph.prototype.allowDanglingEdges = true;
+
+/**
+ * Variable: cloneInvalidEdges
+ *
+ * Specifies if edges that are cloned should be validated and only inserted
+ * if they are valid. Default is true.
+ */
+mxGraph.prototype.cloneInvalidEdges = false;
+
+/**
+ * Variable: disconnectOnMove
+ *
+ * Specifies if edges should be disconnected from their terminals when they
+ * are moved. Default is true.
+ */
+mxGraph.prototype.disconnectOnMove = true;
+
+/**
+ * Variable: labelsVisible
+ *
+ * Specifies if labels should be visible. This is used in <getLabel>. Default
+ * is true.
+ */
+mxGraph.prototype.labelsVisible = true;
+
+/**
+ * Variable: htmlLabels
+ *
+ * Specifies the return value for <isHtmlLabel>. Default is false.
+ */
+mxGraph.prototype.htmlLabels = false;
+
+/**
+ * Variable: swimlaneSelectionEnabled
+ *
+ * Specifies if swimlanes should be selectable via the content if the
+ * mouse is released. Default is true.
+ */
+mxGraph.prototype.swimlaneSelectionEnabled = true;
+
+/**
+ * Variable: swimlaneNesting
+ *
+ * Specifies if nesting of swimlanes is allowed. Default is true.
+ */
+mxGraph.prototype.swimlaneNesting = true;
+
+/**
+ * Variable: swimlaneIndicatorColorAttribute
+ *
+ * The attribute used to find the color for the indicator if the indicator
+ * color is set to 'swimlane'. Default is <mxConstants.STYLE_FILLCOLOR>.
+ */
+mxGraph.prototype.swimlaneIndicatorColorAttribute = mxConstants.STYLE_FILLCOLOR;
+
+/**
+ * Variable: imageBundles
+ *
+ * Holds the list of image bundles.
+ */
+mxGraph.prototype.imageBundles = null;
+
+/**
+ * Variable: minFitScale
+ *
+ * Specifies the minimum scale to be applied in <fit>. Default is 0.1. Set this
+ * to null to allow any value.
+ */
+mxGraph.prototype.minFitScale = 0.1;
+
+/**
+ * Variable: maxFitScale
+ *
+ * Specifies the maximum scale to be applied in <fit>. Default is 8. Set this
+ * to null to allow any value.
+ */
+mxGraph.prototype.maxFitScale = 8;
+
+/**
+ * Variable: panDx
+ *
+ * Current horizontal panning value. Default is 0.
+ */
+mxGraph.prototype.panDx = 0;
+
+/**
+ * Variable: panDy
+ *
+ * Current vertical panning value. Default is 0.
+ */
+mxGraph.prototype.panDy = 0;
+
+/**
+ * Variable: collapsedImage
+ *
+ * Specifies the <mxImage> to indicate a collapsed state.
+ * Default value is mxClient.imageBasePath + '/collapsed.gif'
+ */
+mxGraph.prototype.collapsedImage = new mxImage(mxClient.imageBasePath + '/collapsed.gif', 9, 9);
+
+/**
+ * Variable: expandedImage
+ *
+ * Specifies the <mxImage> to indicate a expanded state.
+ * Default value is mxClient.imageBasePath + '/expanded.gif'
+ */
+mxGraph.prototype.expandedImage = new mxImage(mxClient.imageBasePath + '/expanded.gif', 9, 9);
+
+/**
+ * Variable: warningImage
+ *
+ * Specifies the <mxImage> for the image to be used to display a warning
+ * overlay. See <setCellWarning>. Default value is mxClient.imageBasePath +
+ * '/warning'. The extension for the image depends on the platform. It is
+ * '.png' on the Mac and '.gif' on all other platforms.
+ */
+mxGraph.prototype.warningImage = new mxImage(mxClient.imageBasePath + '/warning'+
+ ((mxClient.IS_MAC) ? '.png' : '.gif'), 16, 16);
+
+/**
+ * Variable: alreadyConnectedResource
+ *
+ * Specifies the resource key for the error message to be displayed in
+ * non-multigraphs when two vertices are already connected. If the resource
+ * for this key does not exist then the value is used as the error message.
+ * Default is 'alreadyConnected'.
+ */
+mxGraph.prototype.alreadyConnectedResource = (mxClient.language != 'none') ? 'alreadyConnected' : '';
+
+/**
+ * Variable: containsValidationErrorsResource
+ *
+ * Specifies the resource key for the warning message to be displayed when
+ * a collapsed cell contains validation errors. If the resource for this
+ * key does not exist then the value is used as the warning message.
+ * Default is 'containsValidationErrors'.
+ */
+mxGraph.prototype.containsValidationErrorsResource = (mxClient.language != 'none') ? 'containsValidationErrors' : '';
+
+/**
+ * Variable: collapseExpandResource
+ *
+ * Specifies the resource key for the tooltip on the collapse/expand icon.
+ * If the resource for this key does not exist then the value is used as
+ * the tooltip. Default is 'collapse-expand'.
+ */
+mxGraph.prototype.collapseExpandResource = (mxClient.language != 'none') ? 'collapse-expand' : '';
+
+/**
+ * Function: init
+ *
+ * Initializes the <container> and creates the respective datastructures.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the graph display.
+ */
+ mxGraph.prototype.init = function(container)
+ {
+ this.container = container;
+
+ // Initializes the in-place editor
+ this.cellEditor = this.createCellEditor();
+
+ // Initializes the container using the view
+ this.view.init();
+
+ // Updates the size of the container for the current graph
+ this.sizeDidChange();
+
+ // Automatic deallocation of memory
+ if (mxClient.IS_IE)
+ {
+ mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+ {
+ this.destroy();
+ }));
+
+ // Disable shift-click for text
+ mxEvent.addListener(container, 'selectstart',
+ mxUtils.bind(this, function()
+ {
+ return this.isEditing();
+ })
+ );
+ }
+};
+
+/**
+ * Function: createHandlers
+ *
+ * Creates the tooltip-, panning-, connection- and graph-handler (in this
+ * order). This is called in the constructor before <init> is called.
+ */
+mxGraph.prototype.createHandlers = function(container)
+{
+ this.tooltipHandler = new mxTooltipHandler(this);
+ this.tooltipHandler.setEnabled(false);
+ this.panningHandler = new mxPanningHandler(this);
+ this.panningHandler.panningEnabled = false;
+ this.selectionCellsHandler = new mxSelectionCellsHandler(this);
+ this.connectionHandler = new mxConnectionHandler(this);
+ this.connectionHandler.setEnabled(false);
+ this.graphHandler = new mxGraphHandler(this);
+};
+
+/**
+ * Function: createSelectionModel
+ *
+ * Creates a new <mxGraphSelectionModel> to be used in this graph.
+ */
+mxGraph.prototype.createSelectionModel = function()
+{
+ return new mxGraphSelectionModel(this);
+};
+
+/**
+ * Function: createStylesheet
+ *
+ * Creates a new <mxGraphSelectionModel> to be used in this graph.
+ */
+mxGraph.prototype.createStylesheet = function()
+{
+ return new mxStylesheet();
+};
+
+/**
+ * Function: createGraphView
+ *
+ * Creates a new <mxGraphView> to be used in this graph.
+ */
+mxGraph.prototype.createGraphView = function()
+{
+ return new mxGraphView(this);
+};
+
+/**
+ * Function: createCellRenderer
+ *
+ * Creates a new <mxCellRenderer> to be used in this graph.
+ */
+mxGraph.prototype.createCellRenderer = function()
+{
+ return new mxCellRenderer();
+};
+
+/**
+ * Function: createCellEditor
+ *
+ * Creates a new <mxCellEditor> to be used in this graph.
+ */
+mxGraph.prototype.createCellEditor = function()
+{
+ return new mxCellEditor(this);
+};
+
+/**
+ * Function: getModel
+ *
+ * Returns the <mxGraphModel> that contains the cells.
+ */
+mxGraph.prototype.getModel = function()
+{
+ return this.model;
+};
+
+/**
+ * Function: getView
+ *
+ * Returns the <mxGraphView> that contains the <mxCellStates>.
+ */
+mxGraph.prototype.getView = function()
+{
+ return this.view;
+};
+
+/**
+ * Function: getStylesheet
+ *
+ * Returns the <mxStylesheet> that defines the style.
+ */
+mxGraph.prototype.getStylesheet = function()
+{
+ return this.stylesheet;
+};
+
+/**
+ * Function: setStylesheet
+ *
+ * Sets the <mxStylesheet> that defines the style.
+ */
+mxGraph.prototype.setStylesheet = function(stylesheet)
+{
+ this.stylesheet = stylesheet;
+};
+
+/**
+ * Function: getSelectionModel
+ *
+ * Returns the <mxGraphSelectionModel> that contains the selection.
+ */
+mxGraph.prototype.getSelectionModel = function()
+{
+ return this.selectionModel;
+};
+
+/**
+ * Function: setSelectionModel
+ *
+ * Sets the <mxSelectionModel> that contains the selection.
+ */
+mxGraph.prototype.setSelectionModel = function(selectionModel)
+{
+ this.selectionModel = selectionModel;
+};
+
+/**
+ * Function: getSelectionCellsForChanges
+ *
+ * Returns the cells to be selected for the given array of changes.
+ */
+mxGraph.prototype.getSelectionCellsForChanges = function(changes)
+{
+ var cells = [];
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ if (change.constructor != mxRootChange)
+ {
+ var cell = null;
+
+ if (change instanceof mxChildChange && change.previous == null)
+ {
+ cell = change.child;
+ }
+ else if (change.cell != null && change.cell instanceof mxCell)
+ {
+ cell = change.cell;
+ }
+
+ if (cell != null && mxUtils.indexOf(cells, cell) < 0)
+ {
+ cells.push(cell);
+ }
+ }
+ }
+
+ return this.getModel().getTopmostCells(cells);
+};
+
+/**
+ * Function: graphModelChanged
+ *
+ * Called when the graph model changes. Invokes <processChange> on each
+ * item of the given array to update the view accordingly.
+ *
+ * Parameters:
+ *
+ * changes - Array that contains the individual changes.
+ */
+mxGraph.prototype.graphModelChanged = function(changes)
+{
+ for (var i = 0; i < changes.length; i++)
+ {
+ this.processChange(changes[i]);
+ }
+
+ this.removeSelectionCells(this.getRemovedCellsForChanges(changes));
+
+ this.view.validate();
+ this.sizeDidChange();
+};
+
+/**
+ * Function: getRemovedCellsForChanges
+ *
+ * Returns the cells that have been removed from the model.
+ */
+mxGraph.prototype.getRemovedCellsForChanges = function(changes)
+{
+ var result = [];
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ // Resets the view settings, removes all cells and clears
+ // the selection if the root changes.
+ if (change instanceof mxRootChange)
+ {
+ break;
+ }
+ else if (change instanceof mxChildChange)
+ {
+ if (change.previous != null && change.parent == null)
+ {
+ result = result.concat(this.model.getDescendants(change.child));
+ }
+ }
+ else if (change instanceof mxVisibleChange)
+ {
+ result = result.concat(this.model.getDescendants(change.cell));
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: processChange
+ *
+ * Processes the given change and invalidates the respective cached data
+ * in <view>. This fires a <root> event if the root has changed in the
+ * model.
+ *
+ * Parameters:
+ *
+ * change - Object that represents the change on the model.
+ */
+mxGraph.prototype.processChange = function(change)
+{
+ // Resets the view settings, removes all cells and clears
+ // the selection if the root changes.
+ if (change instanceof mxRootChange)
+ {
+ this.clearSelection();
+ this.removeStateForCell(change.previous);
+
+ if (this.resetViewOnRootChange)
+ {
+ this.view.scale = 1;
+ this.view.translate.x = 0;
+ this.view.translate.y = 0;
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.ROOT));
+ }
+
+ // Adds or removes a child to the view by online invaliding
+ // the minimal required portions of the cache, namely, the
+ // old and new parent and the child.
+ else if (change instanceof mxChildChange)
+ {
+ var newParent = this.model.getParent(change.child);
+
+ if (newParent != null)
+ {
+ // Flags the cell for updating the order in the renderer
+ this.view.invalidate(change.child, true, false, change.previous != null);
+ }
+ else
+ {
+ this.removeStateForCell(change.child);
+
+ // Handles special case of current root of view being removed
+ if (this.view.currentRoot == change.child)
+ {
+ this.home();
+ }
+ }
+
+ if (newParent != change.previous)
+ {
+ // Refreshes the collapse/expand icons on the parents
+ if (newParent != null)
+ {
+ this.view.invalidate(newParent, false, false);
+ }
+
+ if (change.previous != null)
+ {
+ this.view.invalidate(change.previous, false, false);
+ }
+ }
+ }
+
+ // Handles two special cases where the shape does not need to be
+ // recreated from scratch, it only need to be invalidated.
+ else if (change instanceof mxTerminalChange ||
+ change instanceof mxGeometryChange)
+ {
+ this.view.invalidate(change.cell);
+ }
+
+ // Handles two special cases where only the shape, but no
+ // descendants need to be recreated
+ else if (change instanceof mxValueChange)
+ {
+ this.view.invalidate(change.cell, false, false);
+ }
+
+ // Requires a new mxShape in JavaScript
+ else if (change instanceof mxStyleChange)
+ {
+ this.view.invalidate(change.cell, true, true, false);
+ this.view.removeState(change.cell);
+ }
+
+ // Removes the state from the cache by default
+ else if (change.cell != null &&
+ change.cell instanceof mxCell)
+ {
+ this.removeStateForCell(change.cell);
+ }
+};
+
+/**
+ * Function: removeStateForCell
+ *
+ * Removes all cached information for the given cell and its descendants.
+ * This is called when a cell was removed from the model.
+ *
+ * Paramters:
+ *
+ * cell - <mxCell> that was removed from the model.
+ */
+mxGraph.prototype.removeStateForCell = function(cell)
+{
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.removeStateForCell(this.model.getChildAt(cell, i));
+ }
+
+ this.view.removeState(cell);
+};
+
+/**
+ * Group: Overlays
+ */
+
+/**
+ * Function: addCellOverlay
+ *
+ * Adds an <mxCellOverlay> for the specified cell. This method fires an
+ * <addoverlay> event and returns the new <mxCellOverlay>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to add the overlay for.
+ * overlay - <mxCellOverlay> to be added for the cell.
+ */
+mxGraph.prototype.addCellOverlay = function(cell, overlay)
+{
+ if (cell.overlays == null)
+ {
+ cell.overlays = [];
+ }
+
+ cell.overlays.push(overlay);
+
+ var state = this.view.getState(cell);
+
+ // Immediately updates the cell display if the state exists
+ if (state != null)
+ {
+ this.cellRenderer.redraw(state);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY,
+ 'cell', cell, 'overlay', overlay));
+
+ return overlay;
+};
+
+/**
+ * Function: getCellOverlays
+ *
+ * Returns the array of <mxCellOverlays> for the given cell or null, if
+ * no overlays are defined.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose overlays should be returned.
+ */
+mxGraph.prototype.getCellOverlays = function(cell)
+{
+ return cell.overlays;
+};
+
+/**
+ * Function: removeCellOverlay
+ *
+ * Removes and returns the given <mxCellOverlay> from the given cell. This
+ * method fires a <removeoverlay> event. If no overlay is given, then all
+ * overlays are removed using <removeOverlays>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose overlay should be removed.
+ * overlay - Optional <mxCellOverlay> to be removed.
+ */
+mxGraph.prototype.removeCellOverlay = function(cell, overlay)
+{
+ if (overlay == null)
+ {
+ this.removeCellOverlays(cell);
+ }
+ else
+ {
+ var index = mxUtils.indexOf(cell.overlays, overlay);
+
+ if (index >= 0)
+ {
+ cell.overlays.splice(index, 1);
+
+ if (cell.overlays.length == 0)
+ {
+ cell.overlays = null;
+ }
+
+ // Immediately updates the cell display if the state exists
+ var state = this.view.getState(cell);
+
+ if (state != null)
+ {
+ this.cellRenderer.redraw(state);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+ 'cell', cell, 'overlay', overlay));
+ }
+ else
+ {
+ overlay = null;
+ }
+ }
+
+ return overlay;
+};
+
+/**
+ * Function: removeCellOverlays
+ *
+ * Removes all <mxCellOverlays> from the given cell. This method
+ * fires a <removeoverlay> event for each <mxCellOverlay> and returns
+ * the array of <mxCellOverlays> that was removed from the cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose overlays should be removed
+ */
+mxGraph.prototype.removeCellOverlays = function(cell)
+{
+ var overlays = cell.overlays;
+
+ if (overlays != null)
+ {
+ cell.overlays = null;
+
+ // Immediately updates the cell display if the state exists
+ var state = this.view.getState(cell);
+
+ if (state != null)
+ {
+ this.cellRenderer.redraw(state);
+ }
+
+ for (var i = 0; i < overlays.length; i++)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+ 'cell', cell, 'overlay', overlays[i]));
+ }
+ }
+
+ return overlays;
+};
+
+/**
+ * Function: clearCellOverlays
+ *
+ * Removes all <mxCellOverlays> in the graph for the given cell and all its
+ * descendants. If no cell is specified then all overlays are removed from
+ * the graph. This implementation uses <removeCellOverlays> to remove the
+ * overlays from the individual cells.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> that represents the root of the subtree to
+ * remove the overlays from. Default is the root in the model.
+ */
+mxGraph.prototype.clearCellOverlays = function(cell)
+{
+ cell = (cell != null) ? cell : this.model.getRoot();
+ this.removeCellOverlays(cell);
+
+ // Recursively removes all overlays from the children
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.model.getChildAt(cell, i);
+ this.clearCellOverlays(child); // recurse
+ }
+};
+
+/**
+ * Function: setCellWarning
+ *
+ * Creates an overlay for the given cell using the warning and image or
+ * <warningImage> and returns the new <mxCellOverlay>. The warning is
+ * displayed as a tooltip in a red font and may contain HTML markup. If
+ * the warning is null or a zero length string, then all overlays are
+ * removed from the cell.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.setCellWarning(cell, '<b>Warning:</b>: Hello, World!');
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose warning should be set.
+ * warning - String that represents the warning to be displayed.
+ * img - Optional <mxImage> to be used for the overlay. Default is
+ * <warningImage>.
+ * isSelect - Optional boolean indicating if a click on the overlay
+ * should select the corresponding cell. Default is false.
+ */
+mxGraph.prototype.setCellWarning = function(cell, warning, img, isSelect)
+{
+ if (warning != null && warning.length > 0)
+ {
+ img = (img != null) ? img : this.warningImage;
+
+ // Creates the overlay with the image and warning
+ var overlay = new mxCellOverlay(img,
+ '<font color=red>'+warning+'</font>');
+
+ // Adds a handler for single mouseclicks to select the cell
+ if (isSelect)
+ {
+ overlay.addListener(mxEvent.CLICK,
+ mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.setSelectionCell(cell);
+ }
+ })
+ );
+ }
+
+ // Sets and returns the overlay in the graph
+ return this.addCellOverlay(cell, overlay);
+ }
+ else
+ {
+ this.removeCellOverlays(cell);
+ }
+
+ return null;
+};
+
+/**
+ * Group: In-place editing
+ */
+
+/**
+ * Function: startEditing
+ *
+ * Calls <startEditingAtCell> using the given cell or the first selection
+ * cell.
+ *
+ * Parameters:
+ *
+ * evt - Optional mouse event that triggered the editing.
+ */
+mxGraph.prototype.startEditing = function(evt)
+{
+ this.startEditingAtCell(null, evt);
+};
+
+/**
+ * Function: startEditingAtCell
+ *
+ * Fires a <startEditing> event and invokes <mxCellEditor.startEditing>
+ * on <editor>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to start the in-place editor for.
+ * evt - Optional mouse event that triggered the editing.
+ */
+mxGraph.prototype.startEditingAtCell = function(cell, evt)
+{
+ if (cell == null)
+ {
+ cell = this.getSelectionCell();
+
+ if (cell != null && !this.isCellEditable(cell))
+ {
+ cell = null;
+ }
+ }
+
+ if (cell != null)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.START_EDITING,
+ 'cell', cell, 'event', evt));
+ this.cellEditor.startEditing(cell, evt);
+ }
+};
+
+/**
+ * Function: getEditingValue
+ *
+ * Returns the initial value for in-place editing. This implementation
+ * returns <convertValueToString> for the given cell. If this function is
+ * overridden, then <mxGraphModel.valueForCellChanged> should take care
+ * of correctly storing the actual new value inside the user object.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the initial editing value should be returned.
+ * evt - Optional mouse event that triggered the editor.
+ */
+mxGraph.prototype.getEditingValue = function(cell, evt)
+{
+ return this.convertValueToString(cell);
+};
+
+/**
+ * Function: stopEditing
+ *
+ * Stops the current editing.
+ *
+ * Parameters:
+ *
+ * cancel - Boolean that specifies if the current editing value
+ * should be stored.
+ */
+mxGraph.prototype.stopEditing = function(cancel)
+{
+ this.cellEditor.stopEditing(cancel);
+};
+
+/**
+ * Function: labelChanged
+ *
+ * Sets the label of the specified cell to the given value using
+ * <cellLabelChanged> and fires <mxEvent.LABEL_CHANGED> while the
+ * transaction is in progress. Returns the cell whose label was changed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be changed.
+ * value - New label to be assigned.
+ * evt - Optional event that triggered the change.
+ */
+mxGraph.prototype.labelChanged = function(cell, value, evt)
+{
+ this.model.beginUpdate();
+ try
+ {
+ this.cellLabelChanged(cell, value, this.isAutoSizeCell(cell));
+ this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED,
+ 'cell', cell, 'value', value, 'event', evt));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cell;
+};
+
+/**
+ * Function: cellLabelChanged
+ *
+ * Sets the new label for a cell. If autoSize is true then
+ * <cellSizeUpdated> will be called.
+ *
+ * In the following example, the function is extended to map changes to
+ * attributes in an XML node, as shown in <convertValueToString>.
+ * Alternatively, the handling of this can be implemented as shown in
+ * <mxGraphModel.valueForCellChanged> without the need to clone the
+ * user object.
+ *
+ * (code)
+ * var graphCellLabelChanged = graph.cellLabelChanged;
+ * graph.cellLabelChanged = function(cell, newValue, autoSize)
+ * {
+ * // Cloned for correct undo/redo
+ * var elt = cell.value.cloneNode(true);
+ * elt.setAttribute('label', newValue);
+ *
+ * newValue = elt;
+ * graphCellLabelChanged.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be changed.
+ * value - New label to be assigned.
+ * autoSize - Boolean that specifies if <cellSizeUpdated> should be called.
+ */
+mxGraph.prototype.cellLabelChanged = function(cell, value, autoSize)
+{
+ this.model.beginUpdate();
+ try
+ {
+ this.model.setValue(cell, value);
+
+ if (autoSize)
+ {
+ this.cellSizeUpdated(cell, false);
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+};
+
+/**
+ * Group: Event processing
+ */
+
+/**
+ * Function: escape
+ *
+ * Processes an escape keystroke.
+ *
+ * Parameters:
+ *
+ * evt - Mouseevent that represents the keystroke.
+ */
+mxGraph.prototype.escape = function(evt)
+{
+ this.stopEditing(true);
+ this.connectionHandler.reset();
+ this.graphHandler.reset();
+
+ // Cancels all cell-based editing
+ var cells = this.getSelectionCells();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var state = this.view.getState(cells[i]);
+
+ if (state != null && state.handler != null)
+ {
+ state.handler.reset();
+ }
+ }
+};
+
+/**
+ * Function: click
+ *
+ * Processes a singleclick on an optional cell and fires a <click> event.
+ * The click event is fired initially. If the graph is enabled and the
+ * event has not been consumed, then the cell is selected using
+ * <selectCellForEvent> or the selection is cleared using
+ * <clearSelection>. The events consumed state is set to true if the
+ * corresponding <mxMouseEvent> has been consumed.
+ *
+ * Parameters:
+ *
+ * me - <mxMouseEvent> that represents the single click.
+ */
+mxGraph.prototype.click = function(me)
+{
+ var evt = me.getEvent();
+ var cell = me.getCell();
+ var mxe = new mxEventObject(mxEvent.CLICK, 'event', evt, 'cell', cell);
+
+ if (me.isConsumed())
+ {
+ mxe.consume();
+ }
+
+ this.fireEvent(mxe);
+
+ // Handles the event if it has not been consumed
+ if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
+ {
+ if (cell != null)
+ {
+ this.selectCellForEvent(cell, evt);
+ }
+ else
+ {
+ var swimlane = null;
+
+ if (this.isSwimlaneSelectionEnabled())
+ {
+ // Gets the swimlane at the location (includes
+ // content area of swimlanes)
+ swimlane = this.getSwimlaneAt(me.getGraphX(), me.getGraphY());
+ }
+
+ // Selects the swimlane and consumes the event
+ if (swimlane != null)
+ {
+ this.selectCellForEvent(swimlane, evt);
+ }
+
+ // Ignores the event if the control key is pressed
+ else if (!this.isToggleEvent(evt))
+ {
+ this.clearSelection();
+ }
+ }
+ }
+};
+
+/**
+ * Function: dblClick
+ *
+ * Processes a doubleclick on an optional cell and fires a <dblclick>
+ * event. The event is fired initially. If the graph is enabled and the
+ * event has not been consumed, then <edit> is called with the given
+ * cell. The event is ignored if no cell was specified.
+ *
+ * Example for overriding this method.
+ *
+ * (code)
+ * graph.dblClick = function(evt, cell)
+ * {
+ * var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
+ * this.fireEvent(mxe);
+ *
+ * if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
+ * {
+ * mxUtils.alert('Hello, World!');
+ * mxe.consume();
+ * }
+ * }
+ * (end)
+ *
+ * Example listener for this event.
+ *
+ * (code)
+ * graph.addListener(mxEvent.DOUBLE_CLICK, function(sender, evt)
+ * {
+ * var cell = evt.getProperty('cell');
+ * // do something with the cell...
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * evt - Mouseevent that represents the doubleclick.
+ * cell - Optional <mxCell> under the mousepointer.
+ */
+mxGraph.prototype.dblClick = function(evt, cell)
+{
+ var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
+ this.fireEvent(mxe);
+
+ // Handles the event if it has not been consumed
+ if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() &&
+ cell != null && this.isCellEditable(cell))
+ {
+ this.startEditingAtCell(cell, evt);
+ }
+};
+
+/**
+ * Function: scrollPointToVisible
+ *
+ * Scrolls the graph to the given point, extending the graph container if
+ * specified.
+ */
+mxGraph.prototype.scrollPointToVisible = function(x, y, extend, border)
+{
+ if (!this.timerAutoScroll && (this.ignoreScrollbars || mxUtils.hasScrollbars(this.container)))
+ {
+ var c = this.container;
+ border = (border != null) ? border : 20;
+
+ if (x >= c.scrollLeft && y >= c.scrollTop && x <= c.scrollLeft + c.clientWidth &&
+ y <= c.scrollTop + c.clientHeight)
+ {
+ var dx = c.scrollLeft + c.clientWidth - x;
+
+ if (dx < border)
+ {
+ var old = c.scrollLeft;
+ c.scrollLeft += border - dx;
+
+ // Automatically extends the canvas size to the bottom, right
+ // if the event is outside of the canvas and the edge of the
+ // canvas has been reached. Notes: Needs fix for IE.
+ if (extend && old == c.scrollLeft)
+ {
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ var root = this.view.getDrawPane().ownerSVGElement;
+ var width = this.container.scrollWidth + border - dx;
+
+ // Updates the clipping region. This is an expensive
+ // operation that should not be executed too often.
+ root.setAttribute('width', width);
+ }
+ else
+ {
+ var width = Math.max(c.clientWidth, c.scrollWidth) + border - dx;
+ var canvas = this.view.getCanvas();
+ canvas.style.width = width + 'px';
+ }
+
+ c.scrollLeft += border - dx;
+ }
+ }
+ else
+ {
+ dx = x - c.scrollLeft;
+
+ if (dx < border)
+ {
+ c.scrollLeft -= border - dx;
+ }
+ }
+
+ var dy = c.scrollTop + c.clientHeight - y;
+
+ if (dy < border)
+ {
+ var old = c.scrollTop;
+ c.scrollTop += border - dy;
+
+ if (old == c.scrollTop && extend)
+ {
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ var root = this.view.getDrawPane().ownerSVGElement;
+ var height = this.container.scrollHeight + border - dy;
+
+ // Updates the clipping region. This is an expensive
+ // operation that should not be executed too often.
+ root.setAttribute('height', height);
+ }
+ else
+ {
+ var height = Math.max(c.clientHeight, c.scrollHeight) + border - dy;
+ var canvas = this.view.getCanvas();
+ canvas.style.height = height + 'px';
+ }
+
+ c.scrollTop += border - dy;
+ }
+ }
+ else
+ {
+ dy = y - c.scrollTop;
+
+ if (dy < border)
+ {
+ c.scrollTop -= border - dy;
+ }
+ }
+ }
+ }
+ else if (this.allowAutoPanning && !this.panningHandler.active)
+ {
+ if (this.panningManager == null)
+ {
+ this.panningManager = this.createPanningManager();
+ }
+
+ this.panningManager.panTo(x + this.panDx, y + this.panDy);
+ }
+};
+
+
+/**
+ * Function: createPanningManager
+ *
+ * Creates and returns an <mxPanningManager>.
+ */
+mxGraph.prototype.createPanningManager = function()
+{
+ return new mxPanningManager(this);
+};
+
+/**
+ * Function: getBorderSizes
+ *
+ * Returns the size of the border and padding on all four sides of the
+ * container. The left, top, right and bottom borders are stored in the x, y,
+ * width and height of the returned <mxRectangle>, respectively.
+ */
+mxGraph.prototype.getBorderSizes = function()
+{
+ // Helper function to handle string values for border widths (approx)
+ function parseBorder(value)
+ {
+ var result = 0;
+
+ if (value == 'thin')
+ {
+ result = 2;
+ }
+ else if (value == 'medium')
+ {
+ result = 4;
+ }
+ else if (value == 'thick')
+ {
+ result = 6;
+ }
+ else
+ {
+ result = parseInt(value);
+ }
+
+ if (isNaN(result))
+ {
+ result = 0;
+ }
+
+ return result;
+ }
+
+ var style = mxUtils.getCurrentStyle(this.container);
+ var result = new mxRectangle();
+ result.x = parseBorder(style.borderLeftWidth) + parseInt(style.paddingLeft || 0);
+ result.y = parseBorder(style.borderTopWidth) + parseInt(style.paddingTop || 0);
+ result.width = parseBorder(style.borderRightWidth) + parseInt(style.paddingRight || 0);
+ result.height = parseBorder(style.borderBottomWidth) + parseInt(style.paddingBottom || 0);
+
+ return result;
+};
+
+
+/**
+ * Function: getPreferredPageSize
+ *
+ * Returns the preferred size of the background page if <preferPageSize> is true.
+ */
+mxGraph.prototype.getPreferredPageSize = function(bounds, width, height)
+{
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+ var fmt = this.pageFormat;
+ var ps = scale * this.pageScale;
+ var page = new mxRectangle(0, 0, fmt.width * ps, fmt.height * ps);
+
+ var hCount = (this.pageBreaksVisible) ? Math.ceil(width / page.width) : 1;
+ var vCount = (this.pageBreaksVisible) ? Math.ceil(height / page.height) : 1;
+
+ return new mxRectangle(0, 0, hCount * page.width + 2 + tr.x / scale, vCount * page.height + 2 + tr.y / scale);
+};
+
+/**
+ * Function: sizeDidChange
+ *
+ * Called when the size of the graph has changed. This implementation fires
+ * a <size> event after updating the clipping region of the SVG element in
+ * SVG-bases browsers.
+ */
+mxGraph.prototype.sizeDidChange = function()
+{
+ var bounds = this.getGraphBounds();
+
+ if (this.container != null)
+ {
+ var border = this.getBorder();
+
+ var width = Math.max(0, bounds.x + bounds.width + 1 + border);
+ var height = Math.max(0, bounds.y + bounds.height + 1 + border);
+
+ if (this.minimumContainerSize != null)
+ {
+ width = Math.max(width, this.minimumContainerSize.width);
+ height = Math.max(height, this.minimumContainerSize.height);
+ }
+
+ if (this.resizeContainer)
+ {
+ this.doResizeContainer(width, height);
+ }
+
+ if (this.preferPageSize || (!mxClient.IS_IE && this.pageVisible))
+ {
+ var size = this.getPreferredPageSize(bounds, width, height);
+
+ if (size != null)
+ {
+ width = size.width;
+ height = size.height;
+ }
+ }
+
+ if (this.minimumGraphSize != null)
+ {
+ width = Math.max(width, this.minimumGraphSize.width * this.view.scale);
+ height = Math.max(height, this.minimumGraphSize.height * this.view.scale);
+ }
+
+ width = Math.ceil(width - 1);
+ height = Math.ceil(height - 1);
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ var root = this.view.getDrawPane().ownerSVGElement;
+
+ root.style.minWidth = Math.max(1, width) + 'px';
+ root.style.minHeight = Math.max(1, height) + 'px';
+ }
+ else
+ {
+ if (mxClient.IS_QUIRKS)
+ {
+ // Quirks mode has no minWidth/minHeight support
+ this.view.updateHtmlCanvasSize(Math.max(1, width), Math.max(1, height));
+ }
+ else
+ {
+ this.view.canvas.style.minWidth = Math.max(1, width) + 'px';
+ this.view.canvas.style.minHeight = Math.max(1, height) + 'px';
+ }
+ }
+
+ this.updatePageBreaks(this.pageBreaksVisible, width - 1, height - 1);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', bounds));
+};
+
+/**
+ * Function: doResizeContainer
+ *
+ * Resizes the container for the given graph width and height.
+ */
+mxGraph.prototype.doResizeContainer = function(width, height)
+{
+ // Fixes container size for different box models
+ if (mxClient.IS_IE)
+ {
+ if (mxClient.IS_QUIRKS)
+ {
+ var borders = this.getBorderSizes();
+
+ // max(2, ...) required for native IE8 in quirks mode
+ width += Math.max(2, borders.x + borders.width + 1);
+ height += Math.max(2, borders.y + borders.height + 1);
+ }
+ else if (document.documentMode >= 9)
+ {
+ width += 3;
+ height += 5;
+ }
+ else
+ {
+ width += 1;
+ height += 1;
+ }
+ }
+ else
+ {
+ height += 1;
+ }
+
+ if (this.maximumContainerSize != null)
+ {
+ width = Math.min(this.maximumContainerSize.width, width);
+ height = Math.min(this.maximumContainerSize.height, height);
+ }
+
+ this.container.style.width = Math.ceil(width) + 'px';
+ this.container.style.height = Math.ceil(height) + 'px';
+};
+
+/**
+ * Function: redrawPageBreaks
+ *
+ * Invokes from <sizeDidChange> to redraw the page breaks.
+ *
+ * Parameters:
+ *
+ * visible - Boolean that specifies if page breaks should be shown.
+ * width - Specifies the width of the container in pixels.
+ * height - Specifies the height of the container in pixels.
+ */
+mxGraph.prototype.updatePageBreaks = function(visible, width, height)
+{
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+ var fmt = this.pageFormat;
+ var ps = scale * this.pageScale;
+ var bounds = new mxRectangle(scale * tr.x, scale * tr.y,
+ fmt.width * ps, fmt.height * ps);
+
+ // Does not show page breaks if the scale is too small
+ visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
+
+ // Draws page breaks independent of translate. To ignore
+ // the translate set bounds.x/y = 0. Note that modulo
+ // in JavaScript has a bug, so use mxUtils instead.
+ bounds.x = mxUtils.mod(bounds.x, bounds.width);
+ bounds.y = mxUtils.mod(bounds.y, bounds.height);
+
+ var horizontalCount = (visible) ? Math.ceil((width - bounds.x) / bounds.width) : 0;
+ var verticalCount = (visible) ? Math.ceil((height - bounds.y) / bounds.height) : 0;
+ var right = width;
+ var bottom = height;
+
+ if (this.horizontalPageBreaks == null && horizontalCount > 0)
+ {
+ this.horizontalPageBreaks = [];
+ }
+
+ if (this.horizontalPageBreaks != null)
+ {
+ for (var i = 0; i <= horizontalCount; i++)
+ {
+ var pts = [new mxPoint(bounds.x + i * bounds.width, 1),
+ new mxPoint(bounds.x + i * bounds.width, bottom)];
+
+ if (this.horizontalPageBreaks[i] != null)
+ {
+ this.horizontalPageBreaks[i].scale = 1;
+ this.horizontalPageBreaks[i].points = pts;
+ this.horizontalPageBreaks[i].redraw();
+ }
+ else
+ {
+ var pageBreak = new mxPolyline(pts, this.pageBreakColor, this.scale);
+ pageBreak.dialect = this.dialect;
+ pageBreak.isDashed = this.pageBreakDashed;
+ pageBreak.scale = scale;
+ pageBreak.crisp = true;
+ pageBreak.init(this.view.backgroundPane);
+ pageBreak.redraw();
+
+ this.horizontalPageBreaks[i] = pageBreak;
+ }
+ }
+
+ for (var i = horizontalCount; i < this.horizontalPageBreaks.length; i++)
+ {
+ this.horizontalPageBreaks[i].destroy();
+ }
+
+ this.horizontalPageBreaks.splice(horizontalCount, this.horizontalPageBreaks.length - horizontalCount);
+ }
+
+ if (this.verticalPageBreaks == null && verticalCount > 0)
+ {
+ this.verticalPageBreaks = [];
+ }
+
+ if (this.verticalPageBreaks != null)
+ {
+ for (var i = 0; i <= verticalCount; i++)
+ {
+ var pts = [new mxPoint(1, bounds.y + i * bounds.height),
+ new mxPoint(right, bounds.y + i * bounds.height)];
+
+ if (this.verticalPageBreaks[i] != null)
+ {
+ this.verticalPageBreaks[i].scale = 1;
+ this.verticalPageBreaks[i].points = pts;
+ this.verticalPageBreaks[i].redraw();
+ }
+ else
+ {
+ var pageBreak = new mxPolyline(pts, this.pageBreakColor, scale);
+ pageBreak.dialect = this.dialect;
+ pageBreak.isDashed = this.pageBreakDashed;
+ pageBreak.scale = scale;
+ pageBreak.crisp = true;
+ pageBreak.init(this.view.backgroundPane);
+ pageBreak.redraw();
+
+ this.verticalPageBreaks[i] = pageBreak;
+ }
+ }
+
+ for (var i = verticalCount; i < this.verticalPageBreaks.length; i++)
+ {
+ this.verticalPageBreaks[i].destroy();
+ }
+
+ this.verticalPageBreaks.splice(verticalCount, this.verticalPageBreaks.length - verticalCount);
+ }
+};
+
+/**
+ * Group: Cell styles
+ */
+
+/**
+ * Function: getCellStyle
+ *
+ * Returns an array of key, value pairs representing the cell style for the
+ * given cell. If no string is defined in the model that specifies the
+ * style, then the default style for the cell is returned or <EMPTY_ARRAY>,
+ * if not style can be found. Note: You should try and get the cell state
+ * for the given cell and use the cached style in the state before using
+ * this method.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose style should be returned as an array.
+ */
+mxGraph.prototype.getCellStyle = function(cell)
+{
+ var stylename = this.model.getStyle(cell);
+ var style = null;
+
+ // Gets the default style for the cell
+ if (this.model.isEdge(cell))
+ {
+ style = this.stylesheet.getDefaultEdgeStyle();
+ }
+ else
+ {
+ style = this.stylesheet.getDefaultVertexStyle();
+ }
+
+ // Resolves the stylename using the above as the default
+ if (stylename != null)
+ {
+ style = this.postProcessCellStyle(this.stylesheet.getCellStyle(stylename, style));
+ }
+
+ // Returns a non-null value if no style can be found
+ if (style == null)
+ {
+ style = mxGraph.prototype.EMPTY_ARRAY;
+ }
+
+ return style;
+};
+
+/**
+ * Function: postProcessCellStyle
+ *
+ * Tries to resolve the value for the image style in the image bundles and
+ * turns short data URIs as defined in mxImageBundle to data URIs as
+ * defined in RFC 2397 of the IETF.
+ */
+mxGraph.prototype.postProcessCellStyle = function(style)
+{
+ if (style != null)
+ {
+ var key = style[mxConstants.STYLE_IMAGE];
+ var image = this.getImageFromBundles(key);
+
+ if (image != null)
+ {
+ style[mxConstants.STYLE_IMAGE] = image;
+ }
+ else
+ {
+ image = key;
+ }
+
+ // Converts short data uris to normal data uris
+ if (image != null && image.substring(0, 11) == "data:image/")
+ {
+ var comma = image.indexOf(',');
+
+ if (comma > 0)
+ {
+ image = image.substring(0, comma) + ";base64,"
+ + image.substring(comma + 1);
+ }
+
+ style[mxConstants.STYLE_IMAGE] = image;
+ }
+ }
+
+ return style;
+};
+
+/**
+ * Function: setCellStyle
+ *
+ * Sets the style of the specified cells. If no cells are given, then the
+ * selection cells are changed.
+ *
+ * Parameters:
+ *
+ * style - String representing the new style of the cells.
+ * cells - Optional array of <mxCells> to set the style for. Default is the
+ * selection cells.
+ */
+mxGraph.prototype.setCellStyle = function(style, cells)
+{
+ cells = cells || this.getSelectionCells();
+
+ if (cells != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ this.model.setStyle(cells[i], style);
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: toggleCellStyle
+ *
+ * Toggles the boolean value for the given key in the style of the
+ * given cell. If no cell is specified then the selection cell is
+ * used.
+ *
+ * Parameter:
+ *
+ * key - String representing the key for the boolean value to be toggled.
+ * defaultValue - Optional boolean default value if no value is defined.
+ * Default is false.
+ * cell - Optional <mxCell> whose style should be modified. Default is
+ * the selection cell.
+ */
+mxGraph.prototype.toggleCellStyle = function(key, defaultValue, cell)
+{
+ cell = cell || this.getSelectionCell();
+
+ this.toggleCellStyles(key, defaultValue, [cell]);
+};
+
+/**
+ * Function: toggleCellStyles
+ *
+ * Toggles the boolean value for the given key in the style of the given
+ * cells. If no cells are specified, then the selection cells are used. For
+ * example, this can be used to toggle <mxConstants.STYLE_ROUNDED> or any
+ * other style with a boolean value.
+ *
+ * Parameter:
+ *
+ * key - String representing the key for the boolean value to be toggled.
+ * defaultValue - Optional boolean default value if no value is defined.
+ * Default is false.
+ * cells - Optional array of <mxCells> whose styles should be modified.
+ * Default is the selection cells.
+ */
+mxGraph.prototype.toggleCellStyles = function(key, defaultValue, cells)
+{
+ defaultValue = (defaultValue != null) ? defaultValue : false;
+ cells = cells || this.getSelectionCells();
+
+ if (cells != null && cells.length > 0)
+ {
+ var state = this.view.getState(cells[0]);
+ var style = (state != null) ? state.style : this.getCellStyle(cells[0]);
+
+ if (style != null)
+ {
+ var val = (mxUtils.getValue(style, key, defaultValue)) ? 0 : 1;
+ this.setCellStyles(key, val, cells);
+ }
+ }
+};
+
+/**
+ * Function: setCellStyles
+ *
+ * Sets the key to value in the styles of the given cells. This will modify
+ * the existing cell styles in-place and override any existing assignment
+ * for the given key. If no cells are specified, then the selection cells
+ * are changed. If no value is specified, then the respective key is
+ * removed from the styles.
+ *
+ * Parameters:
+ *
+ * key - String representing the key to be assigned.
+ * value - String representing the new value for the key.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.setCellStyles = function(key, value, cells)
+{
+ cells = cells || this.getSelectionCells();
+ mxUtils.setCellStyles(this.model, cells, key, value);
+};
+
+/**
+ * Function: toggleCellStyleFlags
+ *
+ * Toggles the given bit for the given key in the styles of the specified
+ * cells.
+ *
+ * Parameters:
+ *
+ * key - String representing the key to toggle the flag in.
+ * flag - Integer that represents the bit to be toggled.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.toggleCellStyleFlags = function(key, flag, cells)
+{
+ this.setCellStyleFlags(key, flag, null, cells);
+};
+
+/**
+ * Function: setCellStyleFlags
+ *
+ * Sets or toggles the given bit for the given key in the styles of the
+ * specified cells.
+ *
+ * Parameters:
+ *
+ * key - String representing the key to toggle the flag in.
+ * flag - Integer that represents the bit to be toggled.
+ * value - Boolean value to be used or null if the value should be toggled.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.setCellStyleFlags = function(key, flag, value, cells)
+{
+ cells = cells || this.getSelectionCells();
+
+ if (cells != null && cells.length > 0)
+ {
+ if (value == null)
+ {
+ var state = this.view.getState(cells[0]);
+ var style = (state != null) ? state.style : this.getCellStyle(cells[0]);
+
+ if (style != null)
+ {
+ var current = parseInt(style[key] || 0);
+ value = !((current & flag) == flag);
+ }
+ }
+
+ mxUtils.setCellStyleFlags(this.model, cells, key, flag, value);
+ }
+};
+
+/**
+ * Group: Cell alignment and orientation
+ */
+
+/**
+ * Function: alignCells
+ *
+ * Aligns the given cells vertically or horizontally according to the given
+ * alignment using the optional parameter as the coordinate.
+ *
+ * Parameters:
+ *
+ * align - Specifies the alignment. Possible values are all constants in
+ * mxConstants with an ALIGN prefix.
+ * cells - Array of <mxCells> to be aligned.
+ * param - Optional coordinate for the alignment.
+ */
+mxGraph.prototype.alignCells = function(align, cells, param)
+{
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+ }
+
+ if (cells != null && cells.length > 1)
+ {
+ // Finds the required coordinate for the alignment
+ if (param == null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var geo = this.getCellGeometry(cells[i]);
+
+ if (geo != null && !this.model.isEdge(cells[i]))
+ {
+ if (param == null)
+ {
+ if (align == mxConstants.ALIGN_CENTER)
+ {
+ param = geo.x + geo.width / 2;
+ break;
+ }
+ else if (align == mxConstants.ALIGN_RIGHT)
+ {
+ param = geo.x + geo.width;
+ }
+ else if (align == mxConstants.ALIGN_TOP)
+ {
+ param = geo.y;
+ }
+ else if (align == mxConstants.ALIGN_MIDDLE)
+ {
+ param = geo.y + geo.height / 2;
+ break;
+ }
+ else if (align == mxConstants.ALIGN_BOTTOM)
+ {
+ param = geo.y + geo.height;
+ }
+ else
+ {
+ param = geo.x;
+ }
+ }
+ else
+ {
+ if (align == mxConstants.ALIGN_RIGHT)
+ {
+ param = Math.max(param, geo.x + geo.width);
+ }
+ else if (align == mxConstants.ALIGN_TOP)
+ {
+ param = Math.min(param, geo.y);
+ }
+ else if (align == mxConstants.ALIGN_BOTTOM)
+ {
+ param = Math.max(param, geo.y + geo.height);
+ }
+ else
+ {
+ param = Math.min(param, geo.x);
+ }
+ }
+ }
+ }
+ }
+
+ // Aligns the cells to the coordinate
+ if (param != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var geo = this.getCellGeometry(cells[i]);
+
+ if (geo != null && !this.model.isEdge(cells[i]))
+ {
+ geo = geo.clone();
+
+ if (align == mxConstants.ALIGN_CENTER)
+ {
+ geo.x = param - geo.width / 2;
+ }
+ else if (align == mxConstants.ALIGN_RIGHT)
+ {
+ geo.x = param - geo.width;
+ }
+ else if (align == mxConstants.ALIGN_TOP)
+ {
+ geo.y = param;
+ }
+ else if (align == mxConstants.ALIGN_MIDDLE)
+ {
+ geo.y = param - geo.height / 2;
+ }
+ else if (align == mxConstants.ALIGN_BOTTOM)
+ {
+ geo.y = param - geo.height;
+ }
+ else
+ {
+ geo.x = param;
+ }
+
+ this.model.setGeometry(cells[i], geo);
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS,
+ 'align', align, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+ }
+
+ return cells;
+};
+
+/**
+ * Function: flipEdge
+ *
+ * Toggles the style of the given edge between null (or empty) and
+ * <alternateEdgeStyle>. This method fires <mxEvent.FLIP_EDGE> while the
+ * transaction is in progress. Returns the edge that was flipped.
+ *
+ * Here is an example that overrides this implementation to invert the
+ * value of <mxConstants.STYLE_ELBOW> without removing any existing styles.
+ *
+ * (code)
+ * graph.flipEdge = function(edge)
+ * {
+ * if (edge != null)
+ * {
+ * var state = this.view.getState(edge);
+ * var style = (state != null) ? state.style : this.getCellStyle(edge);
+ *
+ * if (style != null)
+ * {
+ * var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
+ * mxConstants.ELBOW_HORIZONTAL);
+ * var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?
+ * mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
+ * this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
+ * }
+ * }
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose style should be changed.
+ */
+mxGraph.prototype.flipEdge = function(edge)
+{
+ if (edge != null &&
+ this.alternateEdgeStyle != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var style = this.model.getStyle(edge);
+
+ if (style == null || style.length == 0)
+ {
+ this.model.setStyle(edge, this.alternateEdgeStyle);
+ }
+ else
+ {
+ this.model.setStyle(edge, null);
+ }
+
+ // Removes all existing control points
+ this.resetEdge(edge);
+ this.fireEvent(new mxEventObject(mxEvent.FLIP_EDGE, 'edge', edge));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+
+ return edge;
+};
+
+/**
+ * Function: addImageBundle
+ *
+ * Adds the specified <mxImageBundle>.
+ */
+mxGraph.prototype.addImageBundle = function(bundle)
+{
+ this.imageBundles.push(bundle);
+};
+
+/**
+ * Function: removeImageBundle
+ *
+ * Removes the specified <mxImageBundle>.
+ */
+mxGraph.prototype.removeImageBundle = function(bundle)
+{
+ var tmp = [];
+
+ for (var i = 0; i < this.imageBundles.length; i++)
+ {
+ if (this.imageBundles[i] != bundle)
+ {
+ tmp.push(this.imageBundles[i]);
+ }
+ }
+
+ this.imageBundles = tmp;
+};
+
+/**
+ * Function: getImageFromBundles
+ *
+ * Searches all <imageBundles> for the specified key and returns the value
+ * for the first match or null if the key is not found.
+ */
+mxGraph.prototype.getImageFromBundles = function(key)
+{
+ if (key != null)
+ {
+ for (var i = 0; i < this.imageBundles.length; i++)
+ {
+ var image = this.imageBundles[i].getImage(key);
+
+ if (image != null)
+ {
+ return image;
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Group: Order
+ */
+
+/**
+ * Function: orderCells
+ *
+ * Moves the given cells to the front or back. The change is carried out
+ * using <cellsOrdered>. This method fires <mxEvent.ORDER_CELLS> while the
+ * transaction is in progress.
+ *
+ * Parameters:
+ *
+ * back - Boolean that specifies if the cells should be moved to back.
+ * cells - Array of <mxCells> to move to the background. If null is
+ * specified then the selection cells are used.
+ */
+ mxGraph.prototype.orderCells = function(back, cells)
+ {
+ if (cells == null)
+ {
+ cells = mxUtils.sortCells(this.getSelectionCells(), true);
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsOrdered(cells, back);
+ this.fireEvent(new mxEventObject(mxEvent.ORDER_CELLS,
+ 'back', back, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+ };
+
+/**
+ * Function: cellsOrdered
+ *
+ * Moves the given cells to the front or back. This method fires
+ * <mxEvent.CELLS_ORDERED> while the transaction is in progress.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose order should be changed.
+ * back - Boolean that specifies if the cells should be moved to back.
+ */
+ mxGraph.prototype.cellsOrdered = function(cells, back)
+ {
+ if (cells != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var parent = this.model.getParent(cells[i]);
+
+ if (back)
+ {
+ this.model.add(parent, cells[i], i);
+ }
+ else
+ {
+ this.model.add(parent, cells[i],
+ this.model.getChildCount(parent) - 1);
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED,
+ 'back', back, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Group: Grouping
+ */
+
+/**
+ * Function: groupCells
+ *
+ * Adds the cells into the given group. The change is carried out using
+ * <cellsAdded>, <cellsMoved> and <cellsResized>. This method fires
+ * <mxEvent.GROUP_CELLS> while the transaction is in progress. Returns the
+ * new group. A group is only created if there is at least one entry in the
+ * given array of cells.
+ *
+ * Parameters:
+ *
+ * group - <mxCell> that represents the target group. If null is specified
+ * then a new group is created using <createGroupCell>.
+ * border - Optional integer that specifies the border between the child
+ * area and the group bounds. Default is 0.
+ * cells - Optional array of <mxCells> to be grouped. If null is specified
+ * then the selection cells are used.
+ */
+mxGraph.prototype.groupCells = function(group, border, cells)
+{
+ if (cells == null)
+ {
+ cells = mxUtils.sortCells(this.getSelectionCells(), true);
+ }
+
+ cells = this.getCellsForGroup(cells);
+
+ if (group == null)
+ {
+ group = this.createGroupCell(cells);
+ }
+
+ var bounds = this.getBoundsForGroup(group, cells, border);
+
+ if (cells.length > 0 && bounds != null)
+ {
+ // Uses parent of group or previous parent of first child
+ var parent = this.model.getParent(group);
+
+ if (parent == null)
+ {
+ parent = this.model.getParent(cells[0]);
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ // Checks if the group has a geometry and
+ // creates one if one does not exist
+ if (this.getCellGeometry(group) == null)
+ {
+ this.model.setGeometry(group, new mxGeometry());
+ }
+
+ // Adds the children into the group and moves
+ var index = this.model.getChildCount(group);
+ this.cellsAdded(cells, group, index, null, null, false, false);
+ this.cellsMoved(cells, -bounds.x, -bounds.y, false, true);
+
+ // Adds the group into the parent and resizes
+ index = this.model.getChildCount(parent);
+ this.cellsAdded([group], parent, index, null, null, false);
+ this.cellsResized([group], [bounds]);
+
+ this.fireEvent(new mxEventObject(mxEvent.GROUP_CELLS,
+ 'group', group, 'border', border, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+
+ return group;
+};
+
+/**
+ * Function: getCellsForGroup
+ *
+ * Returns the cells with the same parent as the first cell
+ * in the given array.
+ */
+mxGraph.prototype.getCellsForGroup = function(cells)
+{
+ var result = [];
+
+ if (cells != null && cells.length > 0)
+ {
+ var parent = this.model.getParent(cells[0]);
+ result.push(cells[0]);
+
+ // Filters selection cells with the same parent
+ for (var i = 1; i < cells.length; i++)
+ {
+ if (this.model.getParent(cells[i]) == parent)
+ {
+ result.push(cells[i]);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getBoundsForGroup
+ *
+ * Returns the bounds to be used for the given group and children.
+ */
+mxGraph.prototype.getBoundsForGroup = function(group, children, border)
+{
+ var result = this.getBoundingBoxFromGeometry(children);
+
+ if (result != null)
+ {
+ if (this.isSwimlane(group))
+ {
+ var size = this.getStartSize(group);
+
+ result.x -= size.width;
+ result.y -= size.height;
+ result.width += size.width;
+ result.height += size.height;
+ }
+
+ // Adds the border
+ result.x -= border;
+ result.y -= border;
+ result.width += 2 * border;
+ result.height += 2 * border;
+ }
+
+ return result;
+};
+
+/**
+ * Function: createGroupCell
+ *
+ * Hook for creating the group cell to hold the given array of <mxCells> if
+ * no group cell was given to the <group> function.
+ *
+ * The following code can be used to set the style of new group cells.
+ *
+ * (code)
+ * var graphCreateGroupCell = graph.createGroupCell;
+ * graph.createGroupCell = function(cells)
+ * {
+ * var group = graphCreateGroupCell.apply(this, arguments);
+ * group.setStyle('group');
+ *
+ * return group;
+ * };
+ */
+mxGraph.prototype.createGroupCell = function(cells)
+{
+ var group = new mxCell('');
+ group.setVertex(true);
+ group.setConnectable(false);
+
+ return group;
+};
+
+/**
+ * Function: ungroupCells
+ *
+ * Ungroups the given cells by moving the children the children to their
+ * parents parent and removing the empty groups. Returns the children that
+ * have been removed from the groups.
+ *
+ * Parameters:
+ *
+ * cells - Array of cells to be ungrouped. If null is specified then the
+ * selection cells are used.
+ */
+mxGraph.prototype.ungroupCells = function(cells)
+{
+ var result = [];
+
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+
+ // Finds the cells with children
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.model.getChildCount(cells[i]) > 0)
+ {
+ tmp.push(cells[i]);
+ }
+ }
+
+ cells = tmp;
+ }
+
+ if (cells != null && cells.length > 0)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var children = this.model.getChildren(cells[i]);
+
+ if (children != null && children.length > 0)
+ {
+ children = children.slice();
+ var parent = this.model.getParent(cells[i]);
+ var index = this.model.getChildCount(parent);
+
+ this.cellsAdded(children, parent, index, null, null, true);
+ result = result.concat(children);
+ }
+ }
+
+ this.cellsRemoved(this.addAllEdges(cells));
+ this.fireEvent(new mxEventObject(mxEvent.UNGROUP_CELLS,
+ 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: removeCellsFromParent
+ *
+ * Removes the specified cells from their parents and adds them to the
+ * default parent. Returns the cells that were removed from their parents.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be removed from their parents.
+ */
+mxGraph.prototype.removeCellsFromParent = function(cells)
+{
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ var parent = this.getDefaultParent();
+ var index = this.model.getChildCount(parent);
+
+ this.cellsAdded(cells, parent, index, null, null, true);
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT,
+ 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: updateGroupBounds
+ *
+ * Updates the bounds of the given array of groups so that it includes
+ * all child vertices.
+ *
+ * Parameters:
+ *
+ * cells - The groups whose bounds should be updated.
+ * border - Optional border to be added in the group. Default is 0.
+ * moveGroup - Optional boolean that allows the group to be moved. Default
+ * is false.
+ */
+mxGraph.prototype.updateGroupBounds = function(cells, border, moveGroup)
+{
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+ }
+
+ border = (border != null) ? border : 0;
+ moveGroup = (moveGroup != null) ? moveGroup : false;
+
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var geo = this.getCellGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var children = this.getChildCells(cells[i]);
+
+ if (children != null && children.length > 0)
+ {
+ var childBounds = this.getBoundingBoxFromGeometry(children);
+
+ if (childBounds.width > 0 && childBounds.height > 0)
+ {
+ var size = (this.isSwimlane(cells[i])) ?
+ this.getStartSize(cells[i]) : new mxRectangle();
+
+ geo = geo.clone();
+
+ if (moveGroup)
+ {
+ geo.x += childBounds.x - size.width - border;
+ geo.y += childBounds.y - size.height - border;
+ }
+
+ geo.width = childBounds.width + size.width + 2 * border;
+ geo.height = childBounds.height + size.height + 2 * border;
+
+ this.model.setGeometry(cells[i], geo);
+ this.moveCells(children, -childBounds.x + size.width + border,
+ -childBounds.y + size.height + border);
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Group: Cell cloning, insertion and removal
+ */
+
+/**
+ * Function: cloneCells
+ *
+ * Returns the clones for the given cells. If the terminal of an edge is
+ * not in the given array, then the respective end is assigned a terminal
+ * point and the terminal is removed.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be cloned.
+ * allowInvalidEdges - Optional boolean that specifies if invalid edges
+ * should be cloned. Default is true.
+ */
+mxGraph.prototype.cloneCells = function(cells, allowInvalidEdges)
+{
+ allowInvalidEdges = (allowInvalidEdges != null) ? allowInvalidEdges : true;
+ var clones = null;
+
+ if (cells != null)
+ {
+ // Creates a hashtable for cell lookups
+ var hash = new Object();
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var id = mxCellPath.create(cells[i]);
+ hash[id] = cells[i];
+ tmp.push(cells[i]);
+ }
+
+ if (tmp.length > 0)
+ {
+ var scale = this.view.scale;
+ var trans = this.view.translate;
+ clones = this.model.cloneCells(cells, true);
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!allowInvalidEdges && this.model.isEdge(clones[i]) &&
+ this.getEdgeValidationError(clones[i],
+ this.model.getTerminal(clones[i], true),
+ this.model.getTerminal(clones[i], false)) != null)
+ {
+ clones[i] = null;
+ }
+ else
+ {
+ var g = this.model.getGeometry(clones[i]);
+
+ if (g != null)
+ {
+ var state = this.view.getState(cells[i]);
+ var pstate = this.view.getState(
+ this.model.getParent(cells[i]));
+
+ if (state != null && pstate != null)
+ {
+ var dx = pstate.origin.x;
+ var dy = pstate.origin.y;
+
+ if (this.model.isEdge(clones[i]))
+ {
+ var pts = state.absolutePoints;
+
+ // Checks if the source is cloned or sets the terminal point
+ var src = this.model.getTerminal(cells[i], true);
+ var srcId = mxCellPath.create(src);
+
+ while (src != null && hash[srcId] == null)
+ {
+ src = this.model.getParent(src);
+ srcId = mxCellPath.create(src);
+ }
+
+ if (src == null)
+ {
+ g.setTerminalPoint(
+ new mxPoint(pts[0].x / scale - trans.x,
+ pts[0].y / scale - trans.y), true);
+ }
+
+ // Checks if the target is cloned or sets the terminal point
+ var trg = this.model.getTerminal(cells[i], false);
+ var trgId = mxCellPath.create(trg);
+
+ while (trg != null && hash[trgId] == null)
+ {
+ trg = this.model.getParent(trg);
+ trgId = mxCellPath.create(trg);
+ }
+
+ if (trg == null)
+ {
+ var n = pts.length - 1;
+ g.setTerminalPoint(
+ new mxPoint(pts[n].x / scale - trans.x,
+ pts[n].y / scale - trans.y), false);
+ }
+
+ // Translates the control points
+ var points = g.points;
+
+ if (points != null)
+ {
+ for (var j = 0; j < points.length; j++)
+ {
+ points[j].x += dx;
+ points[j].y += dy;
+ }
+ }
+ }
+ else
+ {
+ g.x += dx;
+ g.y += dy;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ clones = [];
+ }
+ }
+
+ return clones;
+};
+
+/**
+ * Function: insertVertex
+ *
+ * Adds a new vertex into the given parent <mxCell> using value as the user
+ * object and the given coordinates as the <mxGeometry> of the new vertex.
+ * The id and style are used for the respective properties of the new
+ * <mxCell>, which is returned.
+ *
+ * When adding new vertices from a mouse event, one should take into
+ * account the offset of the graph container and the scale and translation
+ * of the view in order to find the correct unscaled, untranslated
+ * coordinates using <mxGraph.getPointForEvent> as follows:
+ *
+ * (code)
+ * var pt = graph.getPointForEvent(evt);
+ * var parent = graph.getDefaultParent();
+ * graph.insertVertex(parent, null,
+ * 'Hello, World!', x, y, 220, 30);
+ * (end)
+ *
+ * For adding image cells, the style parameter can be assigned as
+ *
+ * (code)
+ * stylename;image=imageUrl
+ * (end)
+ *
+ * See <mxGraph> for more information on using images.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> that specifies the parent of the new vertex.
+ * id - Optional string that defines the Id of the new vertex.
+ * value - Object to be used as the user object.
+ * x - Integer that defines the x coordinate of the vertex.
+ * y - Integer that defines the y coordinate of the vertex.
+ * width - Integer that defines the width of the vertex.
+ * height - Integer that defines the height of the vertex.
+ * style - Optional string that defines the cell style.
+ * relative - Optional boolean that specifies if the geometry is relative.
+ * Default is false.
+ */
+mxGraph.prototype.insertVertex = function(parent, id, value,
+ x, y, width, height, style, relative)
+{
+ var vertex = this.createVertex(parent, id, value, x, y, width, height, style, relative);
+
+ return this.addCell(vertex, parent);
+};
+
+/**
+ * Function: createVertex
+ *
+ * Hook method that creates the new vertex for <insertVertex>.
+ */
+mxGraph.prototype.createVertex = function(parent, id, value,
+ x, y, width, height, style, relative)
+{
+ // Creates the geometry for the vertex
+ var geometry = new mxGeometry(x, y, width, height);
+ geometry.relative = (relative != null) ? relative : false;
+
+ // Creates the vertex
+ var vertex = new mxCell(value, geometry, style);
+ vertex.setId(id);
+ vertex.setVertex(true);
+ vertex.setConnectable(true);
+
+ return vertex;
+};
+
+/**
+ * Function: insertEdge
+ *
+ * Adds a new edge into the given parent <mxCell> using value as the user
+ * object and the given source and target as the terminals of the new edge.
+ * The id and style are used for the respective properties of the new
+ * <mxCell>, which is returned.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> that specifies the parent of the new edge.
+ * id - Optional string that defines the Id of the new edge.
+ * value - JavaScript object to be used as the user object.
+ * source - <mxCell> that defines the source of the edge.
+ * target - <mxCell> that defines the target of the edge.
+ * style - Optional string that defines the cell style.
+ */
+mxGraph.prototype.insertEdge = function(parent, id, value, source, target, style)
+{
+ var edge = this.createEdge(parent, id, value, source, target, style);
+
+ return this.addEdge(edge, parent, source, target);
+};
+
+/**
+ * Function: createEdge
+ *
+ * Hook method that creates the new edge for <insertEdge>. This
+ * implementation does not set the source and target of the edge, these
+ * are set when the edge is added to the model.
+ *
+ */
+mxGraph.prototype.createEdge = function(parent, id, value, source, target, style)
+{
+ // Creates the edge
+ var edge = new mxCell(value, new mxGeometry(), style);
+ edge.setId(id);
+ edge.setEdge(true);
+ edge.geometry.relative = true;
+
+ return edge;
+};
+
+/**
+ * Function: addEdge
+ *
+ * Adds the edge to the parent and connects it to the given source and
+ * target terminals. This is a shortcut method. Returns the edge that was
+ * added.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> to be inserted into the given parent.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * source - Optional <mxCell> that represents the source terminal.
+ * target - Optional <mxCell> that represents the target terminal.
+ * index - Optional index to insert the cells at. Default is to append.
+ */
+mxGraph.prototype.addEdge = function(edge, parent, source, target, index)
+{
+ return this.addCell(edge, parent, index, source, target);
+};
+
+/**
+ * Function: addCell
+ *
+ * Adds the cell to the parent and connects it to the given source and
+ * target terminals. This is a shortcut method. Returns the cell that was
+ * added.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be inserted into the given parent.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * index - Optional index to insert the cells at. Default is to append.
+ * source - Optional <mxCell> that represents the source terminal.
+ * target - Optional <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.addCell = function(cell, parent, index, source, target)
+{
+ return this.addCells([cell], parent, index, source, target)[0];
+};
+
+/**
+ * Function: addCells
+ *
+ * Adds the cells to the parent at the given index, connecting each cell to
+ * the optional source and target terminal. The change is carried out using
+ * <cellsAdded>. This method fires <mxEvent.ADD_CELLS> while the
+ * transaction is in progress. Returns the cells that were added.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be inserted.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * index - Optional index to insert the cells at. Default is to append.
+ * source - Optional source <mxCell> for all inserted cells.
+ * target - Optional target <mxCell> for all inserted cells.
+ */
+mxGraph.prototype.addCells = function(cells, parent, index, source, target)
+{
+ if (parent == null)
+ {
+ parent = this.getDefaultParent();
+ }
+
+ if (index == null)
+ {
+ index = this.model.getChildCount(parent);
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsAdded(cells, parent, index, source, target, false, true);
+ this.fireEvent(new mxEventObject(mxEvent.ADD_CELLS, 'cells', cells,
+ 'parent', parent, 'index', index, 'source', source, 'target', target));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsAdded
+ *
+ * Adds the specified cells to the given parent. This method fires
+ * <mxEvent.CELLS_ADDED> while the transaction is in progress.
+ */
+mxGraph.prototype.cellsAdded = function(cells, parent, index, source, target, absolute, constrain)
+{
+ if (cells != null && parent != null && index != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var parentState = (absolute) ? this.view.getState(parent) : null;
+ var o1 = (parentState != null) ? parentState.origin : null;
+ var zero = new mxPoint(0, 0);
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] == null)
+ {
+ index--;
+ }
+ else
+ {
+ var previous = this.model.getParent(cells[i]);
+
+ // Keeps the cell at its absolute location
+ if (o1 != null && cells[i] != parent && parent != previous)
+ {
+ var oldState = this.view.getState(previous);
+ var o2 = (oldState != null) ? oldState.origin : zero;
+ var geo = this.model.getGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var dx = o2.x - o1.x;
+ var dy = o2.y - o1.y;
+
+ // FIXME: Cells should always be inserted first before any other edit
+ // to avoid forward references in sessions.
+ geo = geo.clone();
+ geo.translate(dx, dy);
+
+ if (!geo.relative && this.model.isVertex(cells[i]) &&
+ !this.isAllowNegativeCoordinates())
+ {
+ geo.x = Math.max(0, geo.x);
+ geo.y = Math.max(0, geo.y);
+ }
+
+ this.model.setGeometry(cells[i], geo);
+ }
+ }
+
+ // Decrements all following indices
+ // if cell is already in parent
+ if (parent == previous)
+ {
+ index--;
+ }
+
+ this.model.add(parent, cells[i], index + i);
+
+ // Extends the parent
+ if (this.isExtendParentsOnAdd() && this.isExtendParent(cells[i]))
+ {
+ this.extendParent(cells[i]);
+ }
+
+ // Constrains the child
+ if (constrain == null || constrain)
+ {
+ this.constrainChild(cells[i]);
+ }
+
+ // Sets the source terminal
+ if (source != null)
+ {
+ this.cellConnected(cells[i], source, true);
+ }
+
+ // Sets the target terminal
+ if (target != null)
+ {
+ this.cellConnected(cells[i], target, false);
+ }
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, 'cells', cells,
+ 'parent', parent, 'index', index, 'source', source, 'target', target,
+ 'absolute', absolute));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: removeCells
+ *
+ * Removes the given cells from the graph including all connected edges if
+ * includeEdges is true. The change is carried out using <cellsRemoved>.
+ * This method fires <mxEvent.REMOVE_CELLS> while the transaction is in
+ * progress. The removed cells are returned as an array.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to remove. If null is specified then the
+ * selection cells which are deletable are used.
+ * includeEdges - Optional boolean which specifies if all connected edges
+ * should be removed as well. Default is true.
+ */
+mxGraph.prototype.removeCells = function(cells, includeEdges)
+{
+ includeEdges = (includeEdges != null) ? includeEdges : true;
+
+ if (cells == null)
+ {
+ cells = this.getDeletableCells(this.getSelectionCells());
+ }
+
+ // Adds all edges to the cells
+ if (includeEdges)
+ {
+ cells = this.getDeletableCells(this.addAllEdges(cells));
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsRemoved(cells);
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS,
+ 'cells', cells, 'includeEdges', includeEdges));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsRemoved
+ *
+ * Removes the given cells from the model. This method fires
+ * <mxEvent.CELLS_REMOVED> while the transaction is in progress.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to remove.
+ */
+mxGraph.prototype.cellsRemoved = function(cells)
+{
+ if (cells != null && cells.length > 0)
+ {
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+
+ this.model.beginUpdate();
+ try
+ {
+ // Creates hashtable for faster lookup
+ var hash = new Object();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var id = mxCellPath.create(cells[i]);
+ hash[id] = cells[i];
+ }
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ // Disconnects edges which are not in cells
+ var edges = this.getConnections(cells[i]);
+
+ for (var j = 0; j < edges.length; j++)
+ {
+ var id = mxCellPath.create(edges[j]);
+
+ if (hash[id] == null)
+ {
+ var geo = this.model.getGeometry(edges[j]);
+
+ if (geo != null)
+ {
+ var state = this.view.getState(edges[j]);
+
+ if (state != null)
+ {
+ geo = geo.clone();
+ var source = state.getVisibleTerminal(true) == cells[i];
+ var pts = state.absolutePoints;
+ var n = (source) ? 0 : pts.length - 1;
+
+ geo.setTerminalPoint(
+ new mxPoint(pts[n].x / scale - tr.x,
+ pts[n].y / scale - tr.y), source);
+ this.model.setTerminal(edges[j], null, source);
+ this.model.setGeometry(edges[j], geo);
+ }
+ }
+ }
+ }
+
+ this.model.remove(cells[i]);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED,
+ 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: splitEdge
+ *
+ * Splits the given edge by adding the newEdge between the previous source
+ * and the given cell and reconnecting the source of the given edge to the
+ * given cell. This method fires <mxEvent.SPLIT_EDGE> while the transaction
+ * is in progress. Returns the new edge that was inserted.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to be splitted.
+ * cells - <mxCells> that represents the cells to insert into the edge.
+ * newEdge - <mxCell> that represents the edge to be inserted.
+ * dx - Optional integer that specifies the vector to move the cells.
+ * dy - Optional integer that specifies the vector to move the cells.
+ */
+mxGraph.prototype.splitEdge = function(edge, cells, newEdge, dx, dy)
+{
+ dx = dx || 0;
+ dy = dy || 0;
+
+ if (newEdge == null)
+ {
+ newEdge = this.cloneCells([edge])[0];
+ }
+
+ var parent = this.model.getParent(edge);
+ var source = this.model.getTerminal(edge, true);
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsMoved(cells, dx, dy, false, false);
+ this.cellsAdded(cells, parent, this.model.getChildCount(parent), null, null,
+ true);
+ this.cellsAdded([newEdge], parent, this.model.getChildCount(parent),
+ source, cells[0], false);
+ this.cellConnected(edge, cells[0], true);
+ this.fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE, 'edge', edge,
+ 'cells', cells, 'newEdge', newEdge, 'dx', dx, 'dy', dy));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return newEdge;
+};
+
+/**
+ * Group: Cell visibility
+ */
+
+/**
+ * Function: toggleCells
+ *
+ * Sets the visible state of the specified cells and all connected edges
+ * if includeEdges is true. The change is carried out using <cellsToggled>.
+ * This method fires <mxEvent.TOGGLE_CELLS> while the transaction is in
+ * progress. Returns the cells whose visible state was changed.
+ *
+ * Parameters:
+ *
+ * show - Boolean that specifies the visible state to be assigned.
+ * cells - Array of <mxCells> whose visible state should be changed. If
+ * null is specified then the selection cells are used.
+ * includeEdges - Optional boolean indicating if the visible state of all
+ * connected edges should be changed as well. Default is true.
+ */
+mxGraph.prototype.toggleCells = function(show, cells, includeEdges)
+{
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+ }
+
+ // Adds all connected edges recursively
+ if (includeEdges)
+ {
+ cells = this.addAllEdges(cells);
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsToggled(cells, show);
+ this.fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS,
+ 'show', show, 'cells', cells, 'includeEdges', includeEdges));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsToggled
+ *
+ * Sets the visible state of the specified cells.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose visible state should be changed.
+ * show - Boolean that specifies the visible state to be assigned.
+ */
+mxGraph.prototype.cellsToggled = function(cells, show)
+{
+ if (cells != null && cells.length > 0)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ this.model.setVisible(cells[i], show);
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Group: Folding
+ */
+
+/**
+ * Function: foldCells
+ *
+ * Sets the collapsed state of the specified cells and all descendants
+ * if recurse is true. The change is carried out using <cellsFolded>.
+ * This method fires <mxEvent.FOLD_CELLS> while the transaction is in
+ * progress. Returns the cells whose collapsed state was changed.
+ *
+ * Parameters:
+ *
+ * collapsed - Boolean indicating the collapsed state to be assigned.
+ * recurse - Optional boolean indicating if the collapsed state of all
+ * descendants should be set. Default is false.
+ * cells - Array of <mxCells> whose collapsed state should be set. If
+ * null is specified then the foldable selection cells are used.
+ * checkFoldable - Optional boolean indicating of isCellFoldable should be
+ * checked. Default is false.
+ */
+mxGraph.prototype.foldCells = function(collapse, recurse, cells, checkFoldable)
+{
+ recurse = (recurse != null) ? recurse : false;
+
+ if (cells == null)
+ {
+ cells = this.getFoldableCells(this.getSelectionCells(), collapse);
+ }
+
+ this.stopEditing(false);
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsFolded(cells, collapse, recurse, checkFoldable);
+ this.fireEvent(new mxEventObject(mxEvent.FOLD_CELLS,
+ 'collapse', collapse, 'recurse', recurse, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsFolded
+ *
+ * Sets the collapsed state of the specified cells. This method fires
+ * <mxEvent.CELLS_FOLDED> while the transaction is in progress. Returns the
+ * cells whose collapsed state was changed.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose collapsed state should be set.
+ * collapsed - Boolean indicating the collapsed state to be assigned.
+ * recurse - Boolean indicating if the collapsed state of all descendants
+ * should be set.
+ * checkFoldable - Optional boolean indicating of isCellFoldable should be
+ * checked. Default is false.
+ */
+mxGraph.prototype.cellsFolded = function(cells, collapse, recurse, checkFoldable)
+{
+ if (cells != null && cells.length > 0)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if ((!checkFoldable || this.isCellFoldable(cells[i], collapse)) &&
+ collapse != this.isCellCollapsed(cells[i]))
+ {
+ this.model.setCollapsed(cells[i], collapse);
+ this.swapBounds(cells[i], collapse);
+
+ if (this.isExtendParent(cells[i]))
+ {
+ this.extendParent(cells[i]);
+ }
+
+ if (recurse)
+ {
+ var children = this.model.getChildren(cells[i]);
+ this.foldCells(children, collapse, recurse);
+ }
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED,
+ 'cells', cells, 'collapse', collapse, 'recurse', recurse));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: swapBounds
+ *
+ * Swaps the alternate and the actual bounds in the geometry of the given
+ * cell invoking <updateAlternateBounds> before carrying out the swap.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the bounds should be swapped.
+ * willCollapse - Boolean indicating if the cell is going to be collapsed.
+ */
+mxGraph.prototype.swapBounds = function(cell, willCollapse)
+{
+ if (cell != null)
+ {
+ var geo = this.model.getGeometry(cell);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+
+ this.updateAlternateBounds(cell, geo, willCollapse);
+ geo.swap();
+
+ this.model.setGeometry(cell, geo);
+ }
+ }
+};
+
+/**
+ * Function: updateAlternateBounds
+ *
+ * Updates or sets the alternate bounds in the given geometry for the given
+ * cell depending on whether the cell is going to be collapsed. If no
+ * alternate bounds are defined in the geometry and
+ * <collapseToPreferredSize> is true, then the preferred size is used for
+ * the alternate bounds. The top, left corner is always kept at the same
+ * location.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the geometry is being udpated.
+ * g - <mxGeometry> for which the alternate bounds should be updated.
+ * willCollapse - Boolean indicating if the cell is going to be collapsed.
+ */
+mxGraph.prototype.updateAlternateBounds = function(cell, geo, willCollapse)
+{
+ if (cell != null && geo != null)
+ {
+ if (geo.alternateBounds == null)
+ {
+ var bounds = geo;
+
+ if (this.collapseToPreferredSize)
+ {
+ var tmp = this.getPreferredSizeForCell(cell);
+
+ if (tmp != null)
+ {
+ bounds = tmp;
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ var startSize = mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE);
+
+ if (startSize > 0)
+ {
+ bounds.height = Math.max(bounds.height, startSize);
+ }
+ }
+ }
+
+ geo.alternateBounds = new mxRectangle(
+ geo.x, geo.y, bounds.width, bounds.height);
+ }
+ else
+ {
+ geo.alternateBounds.x = geo.x;
+ geo.alternateBounds.y = geo.y;
+ }
+ }
+};
+
+/**
+ * Function: addAllEdges
+ *
+ * Returns an array with the given cells and all edges that are connected
+ * to a cell or one of its descendants.
+ */
+mxGraph.prototype.addAllEdges = function(cells)
+{
+ var allCells = cells.slice(); // FIXME: Required?
+ allCells = allCells.concat(this.getAllEdges(cells));
+
+ return allCells;
+};
+
+/**
+ * Function: getAllEdges
+ *
+ * Returns all edges connected to the given cells or its descendants.
+ */
+mxGraph.prototype.getAllEdges = function(cells)
+{
+ var edges = [];
+
+ if (cells != null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var edgeCount = this.model.getEdgeCount(cells[i]);
+
+ for (var j = 0; j < edgeCount; j++)
+ {
+ edges.push(this.model.getEdgeAt(cells[i], j));
+ }
+
+ // Recurses
+ var children = this.model.getChildren(cells[i]);
+ edges = edges.concat(this.getAllEdges(children));
+ }
+ }
+
+ return edges;
+};
+
+/**
+ * Group: Cell sizing
+ */
+
+/**
+ * Function: updateCellSize
+ *
+ * Updates the size of the given cell in the model using <cellSizeUpdated>.
+ * This method fires <mxEvent.UPDATE_CELL_SIZE> while the transaction is in
+ * progress. Returns the cell whose size was updated.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose size should be updated.
+ */
+mxGraph.prototype.updateCellSize = function(cell, ignoreChildren)
+{
+ ignoreChildren = (ignoreChildren != null) ? ignoreChildren : false;
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellSizeUpdated(cell, ignoreChildren);
+ this.fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE,
+ 'cell', cell, 'ignoreChildren', ignoreChildren));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cell;
+};
+
+/**
+ * Function: cellSizeUpdated
+ *
+ * Updates the size of the given cell in the model using
+ * <getPreferredSizeForCell> to get the new size.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the size should be changed.
+ */
+mxGraph.prototype.cellSizeUpdated = function(cell, ignoreChildren)
+{
+ if (cell != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var size = this.getPreferredSizeForCell(cell);
+ var geo = this.model.getGeometry(cell);
+
+ if (size != null && geo != null)
+ {
+ var collapsed = this.isCellCollapsed(cell);
+ geo = geo.clone();
+
+ if (this.isSwimlane(cell))
+ {
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+ var cellStyle = this.model.getStyle(cell);
+
+ if (cellStyle == null)
+ {
+ cellStyle = '';
+ }
+
+ if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ cellStyle = mxUtils.setStyle(cellStyle,
+ mxConstants.STYLE_STARTSIZE, size.height + 8);
+
+ if (collapsed)
+ {
+ geo.height = size.height + 8;
+ }
+
+ geo.width = size.width;
+ }
+ else
+ {
+ cellStyle = mxUtils.setStyle(cellStyle,
+ mxConstants.STYLE_STARTSIZE, size.width + 8);
+
+ if (collapsed)
+ {
+ geo.width = size.width + 8;
+ }
+
+ geo.height = size.height;
+ }
+
+ this.model.setStyle(cell, cellStyle);
+ }
+ else
+ {
+ geo.width = size.width;
+ geo.height = size.height;
+ }
+
+ if (!ignoreChildren && !collapsed)
+ {
+ var bounds = this.view.getBounds(this.model.getChildren(cell));
+
+ if (bounds != null)
+ {
+ var tr = this.view.translate;
+ var scale = this.view.scale;
+
+ var width = (bounds.x + bounds.width) / scale - geo.x - tr.x;
+ var height = (bounds.y + bounds.height) / scale - geo.y - tr.y;
+
+ geo.width = Math.max(geo.width, width);
+ geo.height = Math.max(geo.height, height);
+ }
+ }
+
+ this.cellsResized([cell], [geo]);
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: getPreferredSizeForCell
+ *
+ * Returns the preferred width and height of the given <mxCell> as an
+ * <mxRectangle>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the preferred size should be returned.
+ */
+mxGraph.prototype.getPreferredSizeForCell = function(cell)
+{
+ var result = null;
+
+ if (cell != null)
+ {
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ if (style != null && !this.model.isEdge(cell))
+ {
+ var fontSize = style[mxConstants.STYLE_FONTSIZE] || mxConstants.DEFAULT_FONTSIZE;
+ var dx = 0;
+ var dy = 0;
+
+ // Adds dimension of image if shape is a label
+ if (this.getImage(state) != null || style[mxConstants.STYLE_IMAGE] != null)
+ {
+ if (style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_LABEL)
+ {
+ if (style[mxConstants.STYLE_VERTICAL_ALIGN] == mxConstants.ALIGN_MIDDLE)
+ {
+ dx += parseFloat(style[mxConstants.STYLE_IMAGE_WIDTH]) || mxLabel.prototype.imageSize;
+ }
+
+ if (style[mxConstants.STYLE_ALIGN] != mxConstants.ALIGN_CENTER)
+ {
+ dy += parseFloat(style[mxConstants.STYLE_IMAGE_HEIGHT]) || mxLabel.prototype.imageSize;
+ }
+ }
+ }
+
+ // Adds spacings
+ dx += 2 * (style[mxConstants.STYLE_SPACING] || 0);
+ dx += style[mxConstants.STYLE_SPACING_LEFT] || 0;
+ dx += style[mxConstants.STYLE_SPACING_RIGHT] || 0;
+
+ dy += 2 * (style[mxConstants.STYLE_SPACING] || 0);
+ dy += style[mxConstants.STYLE_SPACING_TOP] || 0;
+ dy += style[mxConstants.STYLE_SPACING_BOTTOM] || 0;
+
+ // Add spacing for collapse/expand icon
+ // LATER: Check alignment and use constants
+ // for image spacing
+ var image = this.getFoldingImage(state);
+
+ if (image != null)
+ {
+ dx += image.width + 8;
+ }
+
+ // Adds space for label
+ var value = this.getLabel(cell);
+
+ if (value != null && value.length > 0)
+ {
+ if (!this.isHtmlLabel(cell))
+ {
+ value = value.replace(/\n/g, '<br>');
+ }
+
+ var size = mxUtils.getSizeForString(value,
+ fontSize, style[mxConstants.STYLE_FONTFAMILY]);
+ var width = size.width + dx;
+ var height = size.height + dy;
+
+ if (!mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ var tmp = height;
+
+ height = width;
+ width = tmp;
+ }
+
+ if (this.gridEnabled)
+ {
+ width = this.snap(width + this.gridSize / 2);
+ height = this.snap(height + this.gridSize / 2);
+ }
+
+ result = new mxRectangle(0, 0, width, height);
+ }
+ else
+ {
+ var gs2 = 4 * this.gridSize;
+ result = new mxRectangle(0, 0, gs2, gs2);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: handleGesture
+ *
+ * Invokes if a gesture event has been detected on a cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> which was pinched.
+ * evt - Object that represents the gesture event.
+ */
+mxGraph.prototype.handleGesture = function(state, evt)
+{
+ if (Math.abs(1 - evt.scale) > 0.2)
+ {
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+
+ var w = state.width * evt.scale;
+ var h = state.height * evt.scale;
+ var x = state.x - (w - state.width) / 2;
+ var y = state.y - (h - state.height) / 2;
+
+ var bounds = new mxRectangle(this.snap(x / scale) - tr.x,
+ this.snap(y / scale) - tr.y,
+ this.snap(w / scale), this.snap(h / scale));
+ this.resizeCell(state.cell, bounds);
+ }
+};
+
+/**
+ * Function: resizeCell
+ *
+ * Sets the bounds of the given cell using <resizeCells>. Returns the
+ * cell which was passed to the function.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose bounds should be changed.
+ * bounds - <mxRectangle> that represents the new bounds.
+ */
+mxGraph.prototype.resizeCell = function(cell, bounds)
+{
+ return this.resizeCells([cell], [bounds])[0];
+};
+
+/**
+ * Function: resizeCells
+ *
+ * Sets the bounds of the given cells and fires a <mxEvent.RESIZE_CELLS>
+ * event while the transaction is in progress. Returns the cells which
+ * have been passed to the function.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounds should be changed.
+ * bounds - Array of <mxRectangles> that represent the new bounds.
+ */
+mxGraph.prototype.resizeCells = function(cells, bounds)
+{
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsResized(cells, bounds);
+ this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS,
+ 'cells', cells, 'bounds', bounds));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsResized
+ *
+ * Sets the bounds of the given cells and fires a <mxEvent.CELLS_RESIZED>
+ * event. If <extendParents> is true, then the parent is extended if a
+ * child size is changed so that it overlaps with the parent.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounds should be changed.
+ * bounds - Array of <mxRectangles> that represent the new bounds.
+ */
+mxGraph.prototype.cellsResized = function(cells, bounds)
+{
+ if (cells != null && bounds != null && cells.length == bounds.length)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var tmp = bounds[i];
+ var geo = this.model.getGeometry(cells[i]);
+
+ if (geo != null && (geo.x != tmp.x || geo.y != tmp.y ||
+ geo.width != tmp.width || geo.height != tmp.height))
+ {
+ geo = geo.clone();
+
+ if (geo.relative)
+ {
+ var offset = geo.offset;
+
+ if (offset != null)
+ {
+ offset.x += tmp.x - geo.x;
+ offset.y += tmp.y - geo.y;
+ }
+ }
+ else
+ {
+ geo.x = tmp.x;
+ geo.y = tmp.y;
+ }
+
+ geo.width = tmp.width;
+ geo.height = tmp.height;
+
+ if (!geo.relative && this.model.isVertex(cells[i]) &&
+ !this.isAllowNegativeCoordinates())
+ {
+ geo.x = Math.max(0, geo.x);
+ geo.y = Math.max(0, geo.y);
+ }
+
+ this.model.setGeometry(cells[i], geo);
+
+ if (this.isExtendParent(cells[i]))
+ {
+ this.extendParent(cells[i]);
+ }
+ }
+ }
+
+ if (this.resetEdgesOnResize)
+ {
+ this.resetEdges(cells);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED,
+ 'cells', cells, 'bounds', bounds));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: extendParent
+ *
+ * Resizes the parents recursively so that they contain the complete area
+ * of the resized child cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.extendParent = function(cell)
+{
+ if (cell != null)
+ {
+ var parent = this.model.getParent(cell);
+ var p = this.model.getGeometry(parent);
+
+ if (parent != null && p != null && !this.isCellCollapsed(parent))
+ {
+ var geo = this.model.getGeometry(cell);
+
+ if (geo != null && (p.width < geo.x + geo.width ||
+ p.height < geo.y + geo.height))
+ {
+ p = p.clone();
+
+ p.width = Math.max(p.width, geo.x + geo.width);
+ p.height = Math.max(p.height, geo.y + geo.height);
+
+ this.cellsResized([parent], [p]);
+ }
+ }
+ }
+};
+
+/**
+ * Group: Cell moving
+ */
+
+/**
+ * Function: importCells
+ *
+ * Clones and inserts the given cells into the graph using the move
+ * method and returns the inserted cells. This shortcut is used if
+ * cells are inserted via datatransfer.
+ */
+mxGraph.prototype.importCells = function(cells, dx, dy, target, evt)
+{
+ return this.moveCells(cells, dx, dy, true, target, evt);
+};
+
+/**
+ * Function: moveCells
+ *
+ * Moves or clones the specified cells and moves the cells or clones by the
+ * given amount, adding them to the optional target cell. The evt is the
+ * mouse event as the mouse was released. The change is carried out using
+ * <cellsMoved>. This method fires <mxEvent.MOVE_CELLS> while the
+ * transaction is in progress. Returns the cells that were moved.
+ *
+ * Use the following code to move all cells in the graph.
+ *
+ * (code)
+ * graph.moveCells(graph.getChildCells(null, true, true), 10, 10);
+ * (end)
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be moved, cloned or added to the target.
+ * dx - Integer that specifies the x-coordinate of the vector. Default is 0.
+ * dy - Integer that specifies the y-coordinate of the vector. Default is 0.
+ * clone - Boolean indicating if the cells should be cloned. Default is false.
+ * target - <mxCell> that represents the new parent of the cells.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.moveCells = function(cells, dx, dy, clone, target, evt)
+{
+ dx = (dx != null) ? dx : 0;
+ dy = (dy != null) ? dy : 0;
+ clone = (clone != null) ? clone : false;
+
+ if (cells != null && (dx != 0 || dy != 0 || clone || target != null))
+ {
+ this.model.beginUpdate();
+ try
+ {
+ if (clone)
+ {
+ cells = this.cloneCells(cells, this.isCloneInvalidEdges());
+
+ if (target == null)
+ {
+ target = this.getDefaultParent();
+ }
+ }
+
+ // FIXME: Cells should always be inserted first before any other edit
+ // to avoid forward references in sessions.
+ // Need to disable allowNegativeCoordinates if target not null to
+ // allow for temporary negative numbers until cellsAdded is called.
+ var previous = this.isAllowNegativeCoordinates();
+
+ if (target != null)
+ {
+ this.setAllowNegativeCoordinates(true);
+ }
+
+ this.cellsMoved(cells, dx, dy, !clone && this.isDisconnectOnMove()
+ && this.isAllowDanglingEdges(), target == null);
+
+ this.setAllowNegativeCoordinates(previous);
+
+ if (target != null)
+ {
+ var index = this.model.getChildCount(target);
+ this.cellsAdded(cells, target, index, null, null, true);
+ }
+
+ // Dispatches a move event
+ this.fireEvent(new mxEventObject(mxEvent.MOVE_CELLS, 'cells', cells,
+ 'dx', dx, 'dy', dy, 'clone', clone, 'target', target, 'event', evt));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsMoved
+ *
+ * Moves the specified cells by the given vector, disconnecting the cells
+ * using disconnectGraph is disconnect is true. This method fires
+ * <mxEvent.CELLS_MOVED> while the transaction is in progress.
+ */
+mxGraph.prototype.cellsMoved = function(cells, dx, dy, disconnect, constrain)
+{
+ if (cells != null && (dx != 0 || dy != 0))
+ {
+ this.model.beginUpdate();
+ try
+ {
+ if (disconnect)
+ {
+ this.disconnectGraph(cells);
+ }
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ this.translateCell(cells[i], dx, dy);
+
+ if (constrain)
+ {
+ this.constrainChild(cells[i]);
+ }
+ }
+
+ if (this.resetEdgesOnMove)
+ {
+ this.resetEdges(cells);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_MOVED,
+ 'cells', cells, 'dx', dy, 'dy', dy, 'disconnect', disconnect));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: translateCell
+ *
+ * Translates the geometry of the given cell and stores the new,
+ * translated geometry in the model as an atomic change.
+ */
+mxGraph.prototype.translateCell = function(cell, dx, dy)
+{
+ var geo = this.model.getGeometry(cell);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+ geo.translate(dx, dy);
+
+ if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates())
+ {
+ geo.x = Math.max(0, geo.x);
+ geo.y = Math.max(0, geo.y);
+ }
+
+ if (geo.relative && !this.model.isEdge(cell))
+ {
+ if (geo.offset == null)
+ {
+ geo.offset = new mxPoint(dx, dy);
+ }
+ else
+ {
+ geo.offset.x += dx;
+ geo.offset.y += dy;
+ }
+ }
+
+ this.model.setGeometry(cell, geo);
+ }
+};
+
+/**
+ * Function: getCellContainmentArea
+ *
+ * Returns the <mxRectangle> inside which a cell is to be kept.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the area should be returned.
+ */
+mxGraph.prototype.getCellContainmentArea = function(cell)
+{
+ if (cell != null && !this.model.isEdge(cell))
+ {
+ var parent = this.model.getParent(cell);
+
+ if (parent == this.getDefaultParent() || parent == this.getCurrentRoot())
+ {
+ return this.getMaximumGraphBounds();
+ }
+ else if (parent != null && parent != this.getDefaultParent())
+ {
+ var g = this.model.getGeometry(parent);
+
+ if (g != null)
+ {
+ var x = 0;
+ var y = 0;
+ var w = g.width;
+ var h = g.height;
+
+ if (this.isSwimlane(parent))
+ {
+ var size = this.getStartSize(parent);
+
+ x = size.width;
+ w -= size.width;
+ y = size.height;
+ h -= size.height;
+ }
+
+ return new mxRectangle(x, y, w, h);
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: getMaximumGraphBounds
+ *
+ * Returns the bounds inside which the diagram should be kept as an
+ * <mxRectangle>.
+ */
+mxGraph.prototype.getMaximumGraphBounds = function()
+{
+ return this.maximumGraphBounds;
+};
+
+/**
+ * Function: constrainChild
+ *
+ * Keeps the given cell inside the bounds returned by
+ * <getCellContainmentArea> for its parent, according to the rules defined by
+ * <getOverlap> and <isConstrainChild>. This modifies the cell's geometry
+ * in-place and does not clone it.
+ *
+ * Parameters:
+ *
+ * cells - <mxCell> which should be constrained.
+ */
+mxGraph.prototype.constrainChild = function(cell)
+{
+ if (cell != null)
+ {
+ var geo = this.model.getGeometry(cell);
+ var area = (this.isConstrainChild(cell)) ?
+ this.getCellContainmentArea(cell) :
+ this.getMaximumGraphBounds();
+
+ if (geo != null && area != null)
+ {
+ // Keeps child within the content area of the parent
+ if (!geo.relative && (geo.x < area.x || geo.y < area.y ||
+ area.width < geo.x + geo.width || area.height < geo.y + geo.height))
+ {
+ var overlap = this.getOverlap(cell);
+
+ if (area.width > 0)
+ {
+ geo.x = Math.min(geo.x, area.x + area.width -
+ (1 - overlap) * geo.width);
+ }
+
+ if (area.height > 0)
+ {
+ geo.y = Math.min(geo.y, area.y + area.height -
+ (1 - overlap) * geo.height);
+ }
+
+ geo.x = Math.max(geo.x, area.x - geo.width * overlap);
+ geo.y = Math.max(geo.y, area.y - geo.height * overlap);
+ }
+ }
+ }
+};
+
+/**
+ * Function: resetEdges
+ *
+ * Resets the control points of the edges that are connected to the given
+ * cells if not both ends of the edge are in the given cells array.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> for which the connected edges should be
+ * reset.
+ */
+mxGraph.prototype.resetEdges = function(cells)
+{
+ if (cells != null)
+ {
+ // Prepares a hashtable for faster cell lookups
+ var hash = new Object();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var id = mxCellPath.create(cells[i]);
+ hash[id] = cells[i];
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var edges = this.model.getEdges(cells[i]);
+
+ if (edges != null)
+ {
+ for (var j = 0; j < edges.length; j++)
+ {
+ var state = this.view.getState(edges[j]);
+
+ var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[j], true);
+ var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[j], false);
+
+ var sourceId = mxCellPath.create(source);
+ var targetId = mxCellPath.create(target);
+
+ // Checks if one of the terminals is not in the given array
+ if (hash[sourceId] == null || hash[targetId] == null)
+ {
+ this.resetEdge(edges[j]);
+ }
+ }
+ }
+
+ this.resetEdges(this.model.getChildren(cells[i]));
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: resetEdge
+ *
+ * Resets the control points of the given edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose points should be reset.
+ */
+mxGraph.prototype.resetEdge = function(edge)
+{
+ var geo = this.model.getGeometry(edge);
+
+ // Resets the control points
+ if (geo != null && geo.points != null && geo.points.length > 0)
+ {
+ geo = geo.clone();
+ geo.points = [];
+ this.model.setGeometry(edge, geo);
+ }
+
+ return edge;
+};
+
+/**
+ * Group: Cell connecting and connection constraints
+ */
+
+/**
+ * Function: getAllConnectionConstraints
+ *
+ * Returns an array of all <mxConnectionConstraints> for the given terminal. If
+ * the shape of the given terminal is a <mxStencilShape> then the constraints
+ * of the corresponding <mxStencil> are returned.
+ *
+ * Parameters:
+ *
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean that specifies if the terminal is the source or target.
+ */
+mxGraph.prototype.getAllConnectionConstraints = function(terminal, source)
+{
+ if (terminal != null && terminal.shape != null &&
+ terminal.shape instanceof mxStencilShape)
+ {
+ if (terminal.shape.stencil != null)
+ {
+ return terminal.shape.stencil.constraints;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: getConnectionConstraint
+ *
+ * Returns an <mxConnectionConstraint> that describes the given connection
+ * point. This result can then be passed to <getConnectionPoint>.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> that represents the edge.
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean indicating if the terminal is the source or target.
+ */
+mxGraph.prototype.getConnectionConstraint = function(edge, terminal, source)
+{
+ var point = null;
+ var x = edge.style[(source) ?
+ mxConstants.STYLE_EXIT_X :
+ mxConstants.STYLE_ENTRY_X];
+
+ if (x != null)
+ {
+ var y = edge.style[(source) ?
+ mxConstants.STYLE_EXIT_Y :
+ mxConstants.STYLE_ENTRY_Y];
+
+ if (y != null)
+ {
+ point = new mxPoint(parseFloat(x), parseFloat(y));
+ }
+ }
+
+ var perimeter = false;
+
+ if (point != null)
+ {
+ perimeter = mxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_EXIT_PERIMETER :
+ mxConstants.STYLE_ENTRY_PERIMETER, true);
+ }
+
+ return new mxConnectionConstraint(point, perimeter);
+};
+
+/**
+ * Function: setConnectionConstraint
+ *
+ * Sets the <mxConnectionConstraint> that describes the given connection point.
+ * If no constraint is given then nothing is changed. To remove an existing
+ * constraint from the given edge, use an empty constraint instead.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge.
+ * terminal - <mxCell> that represents the terminal.
+ * source - Boolean indicating if the terminal is the source or target.
+ * constraint - Optional <mxConnectionConstraint> to be used for this
+ * connection.
+ */
+mxGraph.prototype.setConnectionConstraint = function(edge, terminal, source, constraint)
+{
+ if (constraint != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ if (constraint == null || constraint.point == null)
+ {
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :
+ mxConstants.STYLE_ENTRY_X, null, [edge]);
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :
+ mxConstants.STYLE_ENTRY_Y, null, [edge]);
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+ mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
+ }
+ else if (constraint.point != null)
+ {
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :
+ mxConstants.STYLE_ENTRY_X, constraint.point.x, [edge]);
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :
+ mxConstants.STYLE_ENTRY_Y, constraint.point.y, [edge]);
+
+ // Only writes 0 since 1 is default
+ if (!constraint.perimeter)
+ {
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+ mxConstants.STYLE_ENTRY_PERIMETER, '0', [edge]);
+ }
+ else
+ {
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+ mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
+ }
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: getConnectionPoint
+ *
+ * Returns the nearest point in the list of absolute points or the center
+ * of the opposite terminal.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCellState> that represents the vertex.
+ * constraint - <mxConnectionConstraint> that represents the connection point
+ * constraint as returned by <getConnectionConstraint>.
+ */
+mxGraph.prototype.getConnectionPoint = function(vertex, constraint)
+{
+ var point = null;
+
+ if (vertex != null)
+ {
+ var bounds = this.view.getPerimeterBounds(vertex);
+ var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
+
+ var direction = vertex.style[mxConstants.STYLE_DIRECTION];
+ var r1 = 0;
+
+ // Bounds need to be rotated by 90 degrees for further computation
+ if (direction != null)
+ {
+ if (direction == 'north')
+ {
+ r1 += 270;
+ }
+ else if (direction == 'west')
+ {
+ r1 += 180;
+ }
+ else if (direction == 'south')
+ {
+ r1 += 90;
+ }
+
+ // Bounds need to be rotated by 90 degrees for further computation
+ if (direction == 'north' || direction == 'south')
+ {
+ bounds.x += bounds.width / 2 - bounds.height / 2;
+ bounds.y += bounds.height / 2 - bounds.width / 2;
+ var tmp = bounds.width;
+ bounds.width = bounds.height;
+ bounds.height = tmp;
+ }
+ }
+
+ if (constraint.point != null)
+ {
+ var sx = 1;
+ var sy = 1;
+ var dx = 0;
+ var dy = 0;
+
+ // LATER: Add flipping support for image shapes
+ if (vertex.shape instanceof mxStencilShape)
+ {
+ var flipH = vertex.style[mxConstants.STYLE_STENCIL_FLIPH];
+ var flipV = vertex.style[mxConstants.STYLE_STENCIL_FLIPV];
+
+ if (direction == 'north' || direction == 'south')
+ {
+ var tmp = flipH;
+ flipH = flipV;
+ flipV = tmp;
+ }
+
+ if (flipH)
+ {
+ sx = -1;
+ dx = -bounds.width;
+ }
+
+ if (flipV)
+ {
+ sy = -1;
+ dy = -bounds.height ;
+ }
+ }
+
+ point = new mxPoint(bounds.x + constraint.point.x * bounds.width * sx - dx,
+ bounds.y + constraint.point.y * bounds.height * sy - dy);
+ }
+
+ // Rotation for direction before projection on perimeter
+ var r2 = vertex.style[mxConstants.STYLE_ROTATION] || 0;
+
+ if (constraint.perimeter)
+ {
+ if (r1 != 0 && point != null)
+ {
+ // Only 90 degrees steps possible here so no trig needed
+ var cos = 0;
+ var sin = 0;
+
+ if (r1 == 90)
+ {
+ sin = 1;
+ }
+ else if (r1 == 180)
+ {
+ cos = -1;
+ }
+ else if (r2 == 270)
+ {
+ sin = -1;
+ }
+
+ point = mxUtils.getRotatedPoint(point, cos, sin, cx);
+ }
+
+ if (point != null && constraint.perimeter)
+ {
+ point = this.view.getPerimeterPoint(vertex, point, false);
+ }
+ }
+ else
+ {
+ r2 += r1;
+ }
+
+ // Generic rotation after projection on perimeter
+ if (r2 != 0 && point != null)
+ {
+ var rad = mxUtils.toRadians(r2);
+ var cos = Math.cos(rad);
+ var sin = Math.sin(rad);
+
+ point = mxUtils.getRotatedPoint(point, cos, sin, cx);
+ }
+ }
+
+ return point;
+};
+
+/**
+ * Function: connectCell
+ *
+ * Connects the specified end of the given edge to the given terminal
+ * using <cellConnected> and fires <mxEvent.CONNECT_CELL> while the
+ * transaction is in progress. Returns the updated edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose terminal should be updated.
+ * terminal - <mxCell> that represents the new terminal to be used.
+ * source - Boolean indicating if the new terminal is the source or target.
+ * constraint - Optional <mxConnectionConstraint> to be used for this
+ * connection.
+ */
+mxGraph.prototype.connectCell = function(edge, terminal, source, constraint)
+{
+ this.model.beginUpdate();
+ try
+ {
+ var previous = this.model.getTerminal(edge, source);
+ this.cellConnected(edge, terminal, source, constraint);
+ this.fireEvent(new mxEventObject(mxEvent.CONNECT_CELL,
+ 'edge', edge, 'terminal', terminal, 'source', source,
+ 'previous', previous));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return edge;
+};
+
+/**
+ * Function: cellConnected
+ *
+ * Sets the new terminal for the given edge and resets the edge points if
+ * <resetEdgesOnConnect> is true. This method fires
+ * <mxEvent.CELL_CONNECTED> while the transaction is in progress.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose terminal should be updated.
+ * terminal - <mxCell> that represents the new terminal to be used.
+ * source - Boolean indicating if the new terminal is the source or target.
+ * constraint - <mxConnectionConstraint> to be used for this connection.
+ */
+mxGraph.prototype.cellConnected = function(edge, terminal, source, constraint)
+{
+ if (edge != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var previous = this.model.getTerminal(edge, source);
+
+ // Updates the constraint
+ this.setConnectionConstraint(edge, terminal, source, constraint);
+
+ // Checks if the new terminal is a port, uses the ID of the port in the
+ // style and the parent of the port as the actual terminal of the edge.
+ if (this.isPortsEnabled())
+ {
+ var id = null;
+
+ if (this.isPort(terminal))
+ {
+ id = terminal.getId();
+ terminal = this.getTerminalForPort(terminal, source);
+ }
+
+ // Sets or resets all previous information for connecting to a child port
+ var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
+ mxConstants.STYLE_TARGET_PORT;
+ this.setCellStyles(key, id, [edge]);
+ }
+
+ this.model.setTerminal(edge, terminal, source);
+
+ if (this.resetEdgesOnConnect)
+ {
+ this.resetEdge(edge);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELL_CONNECTED,
+ 'edge', edge, 'terminal', terminal, 'source', source,
+ 'previous', previous));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: disconnectGraph
+ *
+ * Disconnects the given edges from the terminals which are not in the
+ * given array.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be disconnected.
+ */
+mxGraph.prototype.disconnectGraph = function(cells)
+{
+ if (cells != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+
+ // Prepares a hashtable for faster cell lookups
+ var hash = new Object();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var id = mxCellPath.create(cells[i]);
+ hash[id] = cells[i];
+ }
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.model.isEdge(cells[i]))
+ {
+ var geo = this.model.getGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var state = this.view.getState(cells[i]);
+ var pstate = this.view.getState(
+ this.model.getParent(cells[i]));
+
+ if (state != null &&
+ pstate != null)
+ {
+ geo = geo.clone();
+
+ var dx = -pstate.origin.x;
+ var dy = -pstate.origin.y;
+ var pts = state.absolutePoints;
+
+ var src = this.model.getTerminal(cells[i], true);
+
+ if (src != null && this.isCellDisconnectable(cells[i], src, true))
+ {
+ var srcId = mxCellPath.create(src);
+
+ while (src != null && hash[srcId] == null)
+ {
+ src = this.model.getParent(src);
+ srcId = mxCellPath.create(src);
+ }
+
+ if (src == null)
+ {
+ geo.setTerminalPoint(
+ new mxPoint(pts[0].x / scale - tr.x + dx,
+ pts[0].y / scale - tr.y + dy), true);
+ this.model.setTerminal(cells[i], null, true);
+ }
+ }
+
+ var trg = this.model.getTerminal(cells[i], false);
+
+ if (trg != null && this.isCellDisconnectable(cells[i], trg, false))
+ {
+ var trgId = mxCellPath.create(trg);
+
+ while (trg != null && hash[trgId] == null)
+ {
+ trg = this.model.getParent(trg);
+ trgId = mxCellPath.create(trg);
+ }
+
+ if (trg == null)
+ {
+ var n = pts.length - 1;
+ geo.setTerminalPoint(
+ new mxPoint(pts[n].x / scale - tr.x + dx,
+ pts[n].y / scale - tr.y + dy), false);
+ this.model.setTerminal(cells[i], null, false);
+ }
+ }
+
+ this.model.setGeometry(cells[i], geo);
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Group: Drilldown
+ */
+
+/**
+ * Function: getCurrentRoot
+ *
+ * Returns the current root of the displayed cell hierarchy. This is a
+ * shortcut to <mxGraphView.currentRoot> in <view>.
+ */
+ mxGraph.prototype.getCurrentRoot = function()
+ {
+ return this.view.currentRoot;
+ };
+
+ /**
+ * Function: getTranslateForRoot
+ *
+ * Returns the translation to be used if the given cell is the root cell as
+ * an <mxPoint>. This implementation returns null.
+ *
+ * Example:
+ *
+ * To keep the children at their absolute position while stepping into groups,
+ * this function can be overridden as follows.
+ *
+ * (code)
+ * var offset = new mxPoint(0, 0);
+ *
+ * while (cell != null)
+ * {
+ * var geo = this.model.getGeometry(cell);
+ *
+ * if (geo != null)
+ * {
+ * offset.x -= geo.x;
+ * offset.y -= geo.y;
+ * }
+ *
+ * cell = this.model.getParent(cell);
+ * }
+ *
+ * return offset;
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the root.
+ */
+mxGraph.prototype.getTranslateForRoot = function(cell)
+{
+ return null;
+};
+
+/**
+ * Function: isPort
+ *
+ * Returns true if the given cell is a "port", that is, when connecting to
+ * it, the cell returned by getTerminalForPort should be used as the
+ * terminal and the port should be referenced by the ID in either the
+ * mxConstants.STYLE_SOURCE_PORT or the or the
+ * mxConstants.STYLE_TARGET_PORT. Note that a port should not be movable.
+ * This implementation always returns false.
+ *
+ * A typical implementation is the following:
+ *
+ * (code)
+ * graph.isPort = function(cell)
+ * {
+ * var geo = this.getCellGeometry(cell);
+ *
+ * return (geo != null) ? geo.relative : false;
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the port.
+ */
+mxGraph.prototype.isPort = function(cell)
+{
+ return false;
+};
+
+/**
+ * Function: getTerminalForPort
+ *
+ * Returns the terminal to be used for a given port. This implementation
+ * always returns the parent cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the port.
+ * source - If the cell is the source or target port.
+ */
+mxGraph.prototype.getTerminalForPort = function(cell, source)
+{
+ return this.model.getParent(cell);
+};
+
+/**
+ * Function: getChildOffsetForCell
+ *
+ * Returns the offset to be used for the cells inside the given cell. The
+ * root and layer cells may be identified using <mxGraphModel.isRoot> and
+ * <mxGraphModel.isLayer>. For all other current roots, the
+ * <mxGraphView.currentRoot> field points to the respective cell, so that
+ * the following holds: cell == this.view.currentRoot. This implementation
+ * returns null.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose offset should be returned.
+ */
+mxGraph.prototype.getChildOffsetForCell = function(cell)
+{
+ return null;
+};
+
+/**
+ * Function: enterGroup
+ *
+ * Uses the given cell as the root of the displayed cell hierarchy. If no
+ * cell is specified then the selection cell is used. The cell is only used
+ * if <isValidRoot> returns true.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> to be used as the new root. Default is the
+ * selection cell.
+ */
+mxGraph.prototype.enterGroup = function(cell)
+{
+ cell = cell || this.getSelectionCell();
+
+ if (cell != null && this.isValidRoot(cell))
+ {
+ this.view.setCurrentRoot(cell);
+ this.clearSelection();
+ }
+};
+
+/**
+ * Function: exitGroup
+ *
+ * Changes the current root to the next valid root in the displayed cell
+ * hierarchy.
+ */
+mxGraph.prototype.exitGroup = function()
+{
+ var root = this.model.getRoot();
+ var current = this.getCurrentRoot();
+
+ if (current != null)
+ {
+ var next = this.model.getParent(current);
+
+ // Finds the next valid root in the hierarchy
+ while (next != root && !this.isValidRoot(next) &&
+ this.model.getParent(next) != root)
+ {
+ next = this.model.getParent(next);
+ }
+
+ // Clears the current root if the new root is
+ // the model's root or one of the layers.
+ if (next == root || this.model.getParent(next) == root)
+ {
+ this.view.setCurrentRoot(null);
+ }
+ else
+ {
+ this.view.setCurrentRoot(next);
+ }
+
+ var state = this.view.getState(current);
+
+ // Selects the previous root in the graph
+ if (state != null)
+ {
+ this.setSelectionCell(current);
+ }
+ }
+};
+
+/**
+ * Function: home
+ *
+ * Uses the root of the model as the root of the displayed cell hierarchy
+ * and selects the previous root.
+ */
+mxGraph.prototype.home = function()
+{
+ var current = this.getCurrentRoot();
+
+ if (current != null)
+ {
+ this.view.setCurrentRoot(null);
+ var state = this.view.getState(current);
+
+ if (state != null)
+ {
+ this.setSelectionCell(current);
+ }
+ }
+};
+
+/**
+ * Function: isValidRoot
+ *
+ * Returns true if the given cell is a valid root for the cell display
+ * hierarchy. This implementation returns true for all non-null values.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> which should be checked as a possible root.
+ */
+mxGraph.prototype.isValidRoot = function(cell)
+{
+ return (cell != null);
+};
+
+/**
+ * Group: Graph display
+ */
+
+/**
+ * Function: getGraphBounds
+ *
+ * Returns the bounds of the visible graph. Shortcut to
+ * <mxGraphView.getGraphBounds>. See also: <getBoundingBoxFromGeometry>.
+ */
+ mxGraph.prototype.getGraphBounds = function()
+ {
+ return this.view.getGraphBounds();
+ };
+
+/**
+ * Function: getCellBounds
+ *
+ * Returns the scaled, translated bounds for the given cell. See
+ * <mxGraphView.getBounds> for arrays.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose bounds should be returned.
+ * includeEdge - Optional boolean that specifies if the bounds of
+ * the connected edges should be included. Default is false.
+ * includeDescendants - Optional boolean that specifies if the bounds
+ * of all descendants should be included. Default is false.
+ */
+mxGraph.prototype.getCellBounds = function(cell, includeEdges, includeDescendants)
+{
+ var cells = [cell];
+
+ // Includes all connected edges
+ if (includeEdges)
+ {
+ cells = cells.concat(this.model.getEdges(cell));
+ }
+
+ var result = this.view.getBounds(cells);
+
+ // Recursively includes the bounds of the children
+ if (includeDescendants)
+ {
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var tmp = this.getCellBounds(this.model.getChildAt(cell, i),
+ includeEdges, true);
+
+ if (result != null)
+ {
+ result.add(tmp);
+ }
+ else
+ {
+ result = tmp;
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getBoundingBoxFromGeometry
+ *
+ * Returns the bounding box for the geometries of the vertices in the
+ * given array of cells. This can be used to find the graph bounds during
+ * a layout operation (ie. before the last endUpdate) as follows:
+ *
+ * (code)
+ * var cells = graph.getChildCells(graph.getDefaultParent(), true, true);
+ * var bounds = graph.getBoundingBoxFromGeometry(cells, true);
+ * (end)
+ *
+ * This can then be used to move cells to the origin:
+ *
+ * (code)
+ * if (bounds.x < 0 || bounds.y < 0)
+ * {
+ * graph.moveCells(cells, -Math.min(bounds.x, 0), -Math.min(bounds.y, 0))
+ * }
+ * (end)
+ *
+ * Or to translate the graph view:
+ *
+ * (code)
+ * if (bounds.x < 0 || bounds.y < 0)
+ * {
+ * graph.view.setTranslate(-Math.min(bounds.x, 0), -Math.min(bounds.y, 0));
+ * }
+ * (end)
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounds should be returned.
+ * includeEdges - Specifies if edge bounds should be included by computing
+ * the bounding box for all points its geometry. Default is false.
+ */
+mxGraph.prototype.getBoundingBoxFromGeometry = function(cells, includeEdges)
+{
+ includeEdges = (includeEdges != null) ? includeEdges : false;
+ var result = null;
+
+ if (cells != null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (includeEdges || this.model.isVertex(cells[i]))
+ {
+ // Computes the bounding box for the points in the geometry
+ var geo = this.getCellGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var pts = geo.points;
+
+ if (pts != null && pts.length > 0)
+ {
+ var tmp = new mxRectangle(pts[0].x, pts[0].y, 0, 0);
+ var addPoint = function(pt)
+ {
+ if (pt != null)
+ {
+ tmp.add(new mxRectangle(pt.x, pt.y, 0, 0));
+ }
+ };
+
+ for (var j = 1; j < pts.length; j++)
+ {
+ addPoint(pts[j]);
+ }
+
+ addPoint(geo.getTerminalPoint(true));
+ addPoint(geo.getTerminalPoint(false));
+ }
+
+ if (result == null)
+ {
+ result = new mxRectangle(geo.x, geo.y, geo.width, geo.height);
+ }
+ else
+ {
+ result.add(geo);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: refresh
+ *
+ * Clears all cell states or the states for the hierarchy starting at the
+ * given cell and validates the graph. This fires a refresh event as the
+ * last step.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> for which the cell states should be cleared.
+ */
+mxGraph.prototype.refresh = function(cell)
+{
+ this.view.clear(cell, cell == null);
+ this.view.validate();
+ this.sizeDidChange();
+ this.fireEvent(new mxEventObject(mxEvent.REFRESH));
+};
+
+/**
+ * Function: snap
+ *
+ * Snaps the given numeric value to the grid if <gridEnabled> is true.
+ *
+ * Parameters:
+ *
+ * value - Numeric value to be snapped to the grid.
+ */
+mxGraph.prototype.snap = function(value)
+{
+ if (this.gridEnabled)
+ {
+ value = Math.round(value / this.gridSize ) * this.gridSize;
+ }
+
+ return value;
+};
+
+/**
+ * Function: panGraph
+ *
+ * Shifts the graph display by the given amount. This is used to preview
+ * panning operations, use <mxGraphView.setTranslate> to set a persistent
+ * translation of the view. Fires <mxEvent.PAN>.
+ *
+ * Parameters:
+ *
+ * dx - Amount to shift the graph along the x-axis.
+ * dy - Amount to shift the graph along the y-axis.
+ */
+mxGraph.prototype.panGraph = function(dx, dy)
+{
+ if (this.useScrollbarsForPanning && mxUtils.hasScrollbars(this.container))
+ {
+ this.container.scrollLeft = -dx;
+ this.container.scrollTop = -dy;
+ }
+ else
+ {
+ var canvas = this.view.getCanvas();
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ // Puts everything inside the container in a DIV so that it
+ // can be moved without changing the state of the container
+ if (dx == 0 && dy == 0)
+ {
+ // Workaround for ignored removeAttribute on SVG element in IE9 standards
+ if (mxClient.IS_IE)
+ {
+ canvas.setAttribute('transform', 'translate('+ dx + ',' + dy + ')');
+ }
+ else
+ {
+ canvas.removeAttribute('transform');
+ }
+
+ if (this.shiftPreview1 != null)
+ {
+ var child = this.shiftPreview1.firstChild;
+
+ while (child != null)
+ {
+ var next = child.nextSibling;
+ this.container.appendChild(child);
+ child = next;
+ }
+
+ this.shiftPreview1.parentNode.removeChild(this.shiftPreview1);
+ this.shiftPreview1 = null;
+
+ this.container.appendChild(canvas.parentNode);
+
+ child = this.shiftPreview2.firstChild;
+
+ while (child != null)
+ {
+ var next = child.nextSibling;
+ this.container.appendChild(child);
+ child = next;
+ }
+
+ this.shiftPreview2.parentNode.removeChild(this.shiftPreview2);
+ this.shiftPreview2 = null;
+ }
+ }
+ else
+ {
+ canvas.setAttribute('transform', 'translate('+ dx + ',' + dy + ')');
+
+ if (this.shiftPreview1 == null)
+ {
+ // Needs two divs for stuff before and after the SVG element
+ this.shiftPreview1 = document.createElement('div');
+ this.shiftPreview1.style.position = 'absolute';
+ this.shiftPreview1.style.overflow = 'visible';
+
+ this.shiftPreview2 = document.createElement('div');
+ this.shiftPreview2.style.position = 'absolute';
+ this.shiftPreview2.style.overflow = 'visible';
+
+ var current = this.shiftPreview1;
+ var child = this.container.firstChild;
+
+ while (child != null)
+ {
+ var next = child.nextSibling;
+
+ // SVG element is moved via transform attribute
+ if (child != canvas.parentNode)
+ {
+ current.appendChild(child);
+ }
+ else
+ {
+ current = this.shiftPreview2;
+ }
+
+ child = next;
+ }
+
+ this.container.insertBefore(this.shiftPreview1, canvas.parentNode);
+ this.container.appendChild(this.shiftPreview2);
+ }
+
+ this.shiftPreview1.style.left = dx + 'px';
+ this.shiftPreview1.style.top = dy + 'px';
+ this.shiftPreview2.style.left = dx + 'px';
+ this.shiftPreview2.style.top = dy + 'px';
+ }
+ }
+ else
+ {
+ canvas.style.left = dx + 'px';
+ canvas.style.top = dy + 'px';
+ }
+
+ this.panDx = dx;
+ this.panDy = dy;
+
+ this.fireEvent(new mxEventObject(mxEvent.PAN));
+ }
+};
+
+/**
+ * Function: zoomIn
+ *
+ * Zooms into the graph by <zoomFactor>.
+ */
+mxGraph.prototype.zoomIn = function()
+{
+ this.zoom(this.zoomFactor);
+};
+
+/**
+ * Function: zoomOut
+ *
+ * Zooms out of the graph by <zoomFactor>.
+ */
+mxGraph.prototype.zoomOut = function()
+{
+ this.zoom(1 / this.zoomFactor);
+};
+
+/**
+ * Function: zoomActual
+ *
+ * Resets the zoom and panning in the view.
+ */
+mxGraph.prototype.zoomActual = function()
+{
+ if (this.view.scale == 1)
+ {
+ this.view.setTranslate(0, 0);
+ }
+ else
+ {
+ this.view.translate.x = 0;
+ this.view.translate.y = 0;
+
+ this.view.setScale(1);
+ }
+};
+
+/**
+ * Function: zoomTo
+ *
+ * Zooms the graph to the given scale with an optional boolean center
+ * argument, which is passd to <zoom>.
+ */
+mxGraph.prototype.zoomTo = function(scale, center)
+{
+ this.zoom(scale / this.view.scale, center);
+};
+
+/**
+ * Function: zoom
+ *
+ * Zooms the graph using the given factor. Center is an optional boolean
+ * argument that keeps the graph scrolled to the center. If the center argument
+ * is omitted, then <centerZoom> will be used as its value.
+ */
+mxGraph.prototype.zoom = function(factor, center)
+{
+ center = (center != null) ? center : this.centerZoom;
+ var scale = this.view.scale * factor;
+ var state = this.view.getState(this.getSelectionCell());
+
+ if (this.keepSelectionVisibleOnZoom && state != null)
+ {
+ var rect = new mxRectangle(
+ state.x * factor,
+ state.y * factor,
+ state.width * factor,
+ state.height * factor);
+
+ // Refreshes the display only once if a
+ // scroll is carried out
+ this.view.scale = scale;
+
+ if (!this.scrollRectToVisible(rect))
+ {
+ this.view.revalidate();
+
+ // Forces an event to be fired but does not revalidate again
+ this.view.setScale(scale);
+ }
+ }
+ else if (center && !mxUtils.hasScrollbars(this.container))
+ {
+ var dx = this.container.offsetWidth;
+ var dy = this.container.offsetHeight;
+
+ if (factor > 1)
+ {
+ var f = (factor -1) / (scale * 2);
+ dx *= -f;
+ dy *= -f;
+ }
+ else
+ {
+ var f = (1/factor -1) / (this.view.scale * 2);
+ dx *= f;
+ dy *= f;
+ }
+
+ this.view.scaleAndTranslate(scale,
+ this.view.translate.x + dx,
+ this.view.translate.y + dy);
+ }
+ else
+ {
+ this.view.setScale(scale);
+
+ if (mxUtils.hasScrollbars(this.container))
+ {
+ var dx = 0;
+ var dy = 0;
+
+ if (center)
+ {
+ dx = this.container.offsetWidth * (factor - 1) / 2;
+ dy = this.container.offsetHeight * (factor - 1) / 2;
+ }
+
+ this.container.scrollLeft = Math.round(this.container.scrollLeft * factor + dx);
+ this.container.scrollTop = Math.round(this.container.scrollTop * factor + dy);
+ }
+ }
+};
+
+/**
+ * Function: zoomToRect
+ *
+ * Zooms the graph to the specified rectangle. If the rectangle does not have same aspect
+ * ratio as the display container, it is increased in the smaller relative dimension only
+ * until the aspect match. The original rectangle is centralised within this expanded one.
+ *
+ * Note that the input rectangular must be un-scaled and un-translated.
+ *
+ * Parameters:
+ *
+ * rect - The un-scaled and un-translated rectangluar region that should be just visible
+ * after the operation
+ */
+mxGraph.prototype.zoomToRect = function(rect)
+{
+ var scaleX = this.container.clientWidth / rect.width;
+ var scaleY = this.container.clientHeight / rect.height;
+ var aspectFactor = scaleX / scaleY;
+
+ // Remove any overlap of the rect outside the client area
+ rect.x = Math.max(0, rect.x);
+ rect.y = Math.max(0, rect.y);
+ var rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
+ var rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
+ rect.width = rectRight - rect.x;
+ rect.height = rectBottom - rect.y;
+
+ // The selection area has to be increased to the same aspect
+ // ratio as the container, centred around the centre point of the
+ // original rect passed in.
+ if (aspectFactor < 1.0)
+ {
+ // Height needs increasing
+ var newHeight = rect.height / aspectFactor;
+ var deltaHeightBuffer = (newHeight - rect.height) / 2.0;
+ rect.height = newHeight;
+
+ // Assign up to half the buffer to the upper part of the rect, not crossing 0
+ // put the rest on the bottom
+ var upperBuffer = Math.min(rect.y , deltaHeightBuffer);
+ rect.y = rect.y - upperBuffer;
+
+ // Check if the bottom has extended too far
+ rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
+ rect.height = rectBottom - rect.y;
+ }
+ else
+ {
+ // Width needs increasing
+ var newWidth = rect.width * aspectFactor;
+ var deltaWidthBuffer = (newWidth - rect.width) / 2.0;
+ rect.width = newWidth;
+
+ // Assign up to half the buffer to the upper part of the rect, not crossing 0
+ // put the rest on the bottom
+ var leftBuffer = Math.min(rect.x , deltaWidthBuffer);
+ rect.x = rect.x - leftBuffer;
+
+ // Check if the right hand side has extended too far
+ rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
+ rect.width = rectRight - rect.x;
+ }
+
+ var scale = this.container.clientWidth / rect.width;
+
+ if (!mxUtils.hasScrollbars(this.container))
+ {
+ this.view.scaleAndTranslate(scale, -rect.x, -rect.y);
+ }
+ else
+ {
+ this.view.setScale(scale);
+ this.container.scrollLeft = Math.round(rect.x * scale);
+ this.container.scrollTop = Math.round(rect.y * scale);
+ }
+};
+
+/**
+ * Function: fit
+ *
+ * Scales the graph such that the complete diagram fits into <container> and
+ * returns the current scale in the view. To fit an initial graph prior to
+ * rendering, set <mxGraphView.rendering> to false prior to changing the model
+ * and execute the following after changing the model.
+ *
+ * (code)
+ * graph.fit();
+ * graph.view.rendering = true;
+ * graph.refresh();
+ * (end)
+ *
+ * Parameters:
+ *
+ * border - Optional number that specifies the border. Default is 0.
+ * keepOrigin - Optional boolean that specifies if the translate should be
+ * changed. Default is false.
+ */
+mxGraph.prototype.fit = function(border, keepOrigin)
+{
+ if (this.container != null)
+ {
+ border = (border != null) ? border : 0;
+ keepOrigin = (keepOrigin != null) ? keepOrigin : false;
+
+ var w1 = this.container.clientWidth;
+ var h1 = this.container.clientHeight;
+
+ var bounds = this.view.getGraphBounds();
+
+ if (keepOrigin && bounds.x != null && bounds.y != null)
+ {
+ bounds.width += bounds.x;
+ bounds.height += bounds.y;
+ bounds.x = 0;
+ bounds.y = 0;
+ }
+
+ var s = this.view.scale;
+ var w2 = bounds.width / s;
+ var h2 = bounds.height / s;
+
+ // Fits to the size of the background image if required
+ if (this.backgroundImage != null)
+ {
+ w2 = Math.max(w2, this.backgroundImage.width - bounds.x / s);
+ h2 = Math.max(h2, this.backgroundImage.height - bounds.y / s);
+ }
+
+ var b = (keepOrigin) ? border : 2 * border;
+ var s2 = Math.floor(Math.min(w1 / (w2 + b), h1 / (h2 + b)) * 100) / 100;
+
+ if (this.minFitScale != null)
+ {
+ s2 = Math.max(s2, this.minFitScale);
+ }
+
+ if (this.maxFitScale != null)
+ {
+ s2 = Math.min(s2, this.maxFitScale);
+ }
+
+ if (!keepOrigin)
+ {
+ if (!mxUtils.hasScrollbars(this.container))
+ {
+ var x0 = (bounds.x != null) ? Math.floor(this.view.translate.x - bounds.x / s + border + 1) : border;
+ var y0 = (bounds.y != null) ? Math.floor(this.view.translate.y - bounds.y / s + border + 1) : border;
+
+ this.view.scaleAndTranslate(s2, x0, y0);
+ }
+ else
+ {
+ this.view.setScale(s2);
+
+ if (bounds.x != null)
+ {
+ this.container.scrollLeft = Math.round(bounds.x / s) * s2 - border -
+ Math.max(0, (this.container.clientWidth - w2 * s2) / 2);
+ }
+
+ if (bounds.y != null)
+ {
+ this.container.scrollTop = Math.round(bounds.y / s) * s2 - border -
+ Math.max(0, (this.container.clientHeight - h2 * s2) / 2);
+ }
+ }
+ }
+ else if (this.view.scale != s2)
+ {
+ this.view.setScale(s2);
+ }
+ }
+
+ return this.view.scale;
+};
+
+/**
+ * Function: scrollCellToVisible
+ *
+ * Pans the graph so that it shows the given cell. Optionally the cell may
+ * be centered in the container.
+ *
+ * To center a given graph if the <container> has no scrollbars, use the following code.
+ *
+ * [code]
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x - (bounds.width - container.clientWidth) / 2,
+ * -bounds.y - (bounds.height - container.clientHeight) / 2);
+ * [/code]
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be made visible.
+ * center - Optional boolean flag. Default is false.
+ */
+mxGraph.prototype.scrollCellToVisible = function(cell, center)
+{
+ var x = -this.view.translate.x;
+ var y = -this.view.translate.y;
+
+ var state = this.view.getState(cell);
+
+ if (state != null)
+ {
+ var bounds = new mxRectangle(x + state.x, y + state.y, state.width,
+ state.height);
+
+ if (center && this.container != null)
+ {
+ var w = this.container.clientWidth;
+ var h = this.container.clientHeight;
+
+ bounds.x = bounds.getCenterX() - w / 2;
+ bounds.width = w;
+ bounds.y = bounds.getCenterY() - h / 2;
+ bounds.height = h;
+ }
+
+ if (this.scrollRectToVisible(bounds))
+ {
+ // Triggers an update via the view's event source
+ this.view.setTranslate(this.view.translate.x, this.view.translate.y);
+ }
+ }
+};
+
+/**
+ * Function: scrollRectToVisible
+ *
+ * Pans the graph so that it shows the given rectangle.
+ *
+ * Parameters:
+ *
+ * rect - <mxRectangle> to be made visible.
+ */
+mxGraph.prototype.scrollRectToVisible = function(rect)
+{
+ var isChanged = false;
+
+ if (rect != null)
+ {
+ var w = this.container.offsetWidth;
+ var h = this.container.offsetHeight;
+
+ var widthLimit = Math.min(w, rect.width);
+ var heightLimit = Math.min(h, rect.height);
+
+ if (mxUtils.hasScrollbars(this.container))
+ {
+ var c = this.container;
+ rect.x += this.view.translate.x;
+ rect.y += this.view.translate.y;
+ var dx = c.scrollLeft - rect.x;
+ var ddx = Math.max(dx - c.scrollLeft, 0);
+
+ if (dx > 0)
+ {
+ c.scrollLeft -= dx + 2;
+ }
+ else
+ {
+ dx = rect.x + widthLimit - c.scrollLeft - c.clientWidth;
+
+ if (dx > 0)
+ {
+ c.scrollLeft += dx + 2;
+ }
+ }
+
+ var dy = c.scrollTop - rect.y;
+ var ddy = Math.max(0, dy - c.scrollTop);
+
+ if (dy > 0)
+ {
+ c.scrollTop -= dy + 2;
+ }
+ else
+ {
+ dy = rect.y + heightLimit - c.scrollTop - c.clientHeight;
+
+ if (dy > 0)
+ {
+ c.scrollTop += dy + 2;
+ }
+ }
+
+ if (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0))
+ {
+ this.view.setTranslate(ddx, ddy);
+ }
+ }
+ else
+ {
+ var x = -this.view.translate.x;
+ var y = -this.view.translate.y;
+
+ var s = this.view.scale;
+
+ if (rect.x + widthLimit > x + w)
+ {
+ this.view.translate.x -= (rect.x + widthLimit - w - x) / s;
+ isChanged = true;
+ }
+
+ if (rect.y + heightLimit > y + h)
+ {
+ this.view.translate.y -= (rect.y + heightLimit - h - y) / s;
+ isChanged = true;
+ }
+
+ if (rect.x < x)
+ {
+ this.view.translate.x += (x - rect.x) / s;
+ isChanged = true;
+ }
+
+ if (rect.y < y)
+ {
+ this.view.translate.y += (y - rect.y) / s;
+ isChanged = true;
+ }
+
+ if (isChanged)
+ {
+ this.view.refresh();
+
+ // Repaints selection marker (ticket 18)
+ if (this.selectionCellsHandler != null)
+ {
+ this.selectionCellsHandler.refresh();
+ }
+ }
+ }
+ }
+
+ return isChanged;
+};
+
+/**
+ * Function: getCellGeometry
+ *
+ * Returns the <mxGeometry> for the given cell. This implementation uses
+ * <mxGraphModel.getGeometry>. Subclasses can override this to implement
+ * specific geometries for cells in only one graph, that is, it can return
+ * geometries that depend on the current state of the view.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose geometry should be returned.
+ */
+mxGraph.prototype.getCellGeometry = function(cell)
+{
+ return this.model.getGeometry(cell);
+};
+
+/**
+ * Function: isCellVisible
+ *
+ * Returns true if the given cell is visible in this graph. This
+ * implementation uses <mxGraphModel.isVisible>. Subclassers can override
+ * this to implement specific visibility for cells in only one graph, that
+ * is, without affecting the visible state of the cell.
+ *
+ * When using dynamic filter expressions for cell visibility, then the
+ * graph should be revalidated after the filter expression has changed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose visible state should be returned.
+ */
+mxGraph.prototype.isCellVisible = function(cell)
+{
+ return this.model.isVisible(cell);
+};
+
+/**
+ * Function: isCellCollapsed
+ *
+ * Returns true if the given cell is collapsed in this graph. This
+ * implementation uses <mxGraphModel.isCollapsed>. Subclassers can override
+ * this to implement specific collapsed states for cells in only one graph,
+ * that is, without affecting the collapsed state of the cell.
+ *
+ * When using dynamic filter expressions for the collapsed state, then the
+ * graph should be revalidated after the filter expression has changed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose collapsed state should be returned.
+ */
+mxGraph.prototype.isCellCollapsed = function(cell)
+{
+ return this.model.isCollapsed(cell);
+};
+
+/**
+ * Function: isCellConnectable
+ *
+ * Returns true if the given cell is connectable in this graph. This
+ * implementation uses <mxGraphModel.isConnectable>. Subclassers can override
+ * this to implement specific connectable states for cells in only one graph,
+ * that is, without affecting the connectable state of the cell in the model.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose connectable state should be returned.
+ */
+mxGraph.prototype.isCellConnectable = function(cell)
+{
+ return this.model.isConnectable(cell);
+};
+
+/**
+ * Function: isOrthogonal
+ *
+ * Returns true if perimeter points should be computed such that the
+ * resulting edge has only horizontal or vertical segments.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> that represents the edge.
+ */
+mxGraph.prototype.isOrthogonal = function(edge)
+{
+ var orthogonal = edge.style[mxConstants.STYLE_ORTHOGONAL];
+
+ if (orthogonal != null)
+ {
+ return orthogonal;
+ }
+
+ var tmp = this.view.getEdgeStyle(edge);
+
+ return tmp == mxEdgeStyle.SegmentConnector ||
+ tmp == mxEdgeStyle.ElbowConnector ||
+ tmp == mxEdgeStyle.SideToSide ||
+ tmp == mxEdgeStyle.TopToBottom ||
+ tmp == mxEdgeStyle.EntityRelation ||
+ tmp == mxEdgeStyle.OrthConnector;
+};
+
+/**
+ * Function: isLoop
+ *
+ * Returns true if the given cell state is a loop.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents a potential loop.
+ */
+mxGraph.prototype.isLoop = function(state)
+{
+ var src = state.getVisibleTerminalState(true);
+ var trg = state.getVisibleTerminalState(false);
+
+ return (src != null && src == trg);
+};
+
+/**
+ * Function: isCloneEvent
+ *
+ * Returns true if the given event is a clone event. This implementation
+ * returns true if control is pressed.
+ */
+mxGraph.prototype.isCloneEvent = function(evt)
+{
+ return mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: isToggleEvent
+ *
+ * Returns true if the given event is a toggle event. This implementation
+ * returns true if the meta key (Cmd) is pressed on Macs or if control is
+ * pressed on any other platform.
+ */
+mxGraph.prototype.isToggleEvent = function(evt)
+{
+ return (mxClient.IS_MAC) ? mxEvent.isMetaDown(evt) : mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: isGridEnabledEvent
+ *
+ * Returns true if the given mouse event should be aligned to the grid.
+ */
+mxGraph.prototype.isGridEnabledEvent = function(evt)
+{
+ return evt != null && !mxEvent.isAltDown(evt);
+};
+
+/**
+ * Function: isConstrainedEvent
+ *
+ * Returns true if the given mouse event should be aligned to the grid.
+ */
+mxGraph.prototype.isConstrainedEvent = function(evt)
+{
+ return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: isForceMarqueeEvent
+ *
+ * Returns true if the given event forces marquee selection. This implementation
+ * returns true if alt is pressed.
+ */
+mxGraph.prototype.isForceMarqueeEvent = function(evt)
+{
+ return mxEvent.isAltDown(evt);
+};
+
+/**
+ * Group: Validation
+ */
+
+/**
+ * Function: validationAlert
+ *
+ * Displays the given validation error in a dialog. This implementation uses
+ * mxUtils.alert.
+ */
+mxGraph.prototype.validationAlert = function(message)
+{
+ mxUtils.alert(message);
+};
+
+/**
+ * Function: isEdgeValid
+ *
+ * Checks if the return value of <getEdgeValidationError> for the given
+ * arguments is null.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.isEdgeValid = function(edge, source, target)
+{
+ return this.getEdgeValidationError(edge, source, target) == null;
+};
+
+/**
+ * Function: getEdgeValidationError
+ *
+ * Returns the validation error message to be displayed when inserting or
+ * changing an edges' connectivity. A return value of null means the edge
+ * is valid, a return value of '' means it's not valid, but do not display
+ * an error message. Any other (non-empty) string returned from this method
+ * is displayed as an error message when trying to connect an edge to a
+ * source and target. This implementation uses the <multiplicities>, and
+ * checks <multigraph>, <allowDanglingEdges> and <allowLoops> to generate
+ * validation errors.
+ *
+ * For extending this method with specific checks for source/target cells,
+ * the method can be extended as follows. Returning an empty string means
+ * the edge is invalid with no error message, a non-null string specifies
+ * the error message, and null means the edge is valid.
+ *
+ * (code)
+ * graph.getEdgeValidationError = function(edge, source, target)
+ * {
+ * if (source != null && target != null &&
+ * this.model.getValue(source) != null &&
+ * this.model.getValue(target) != null)
+ * {
+ * if (target is not valid for source)
+ * {
+ * return 'Invalid Target';
+ * }
+ * }
+ *
+ * // "Supercall"
+ * return mxGraph.prototype.getEdgeValidationError.apply(this, arguments);
+ * }
+ * (end)
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.getEdgeValidationError = function(edge, source, target)
+{
+ if (edge != null && !this.isAllowDanglingEdges() && (source == null || target == null))
+ {
+ return '';
+ }
+
+ if (edge != null && this.model.getTerminal(edge, true) == null &&
+ this.model.getTerminal(edge, false) == null)
+ {
+ return null;
+ }
+
+ // Checks if we're dealing with a loop
+ if (!this.allowLoops && source == target && source != null)
+ {
+ return '';
+ }
+
+ // Checks if the connection is generally allowed
+ if (!this.isValidConnection(source, target))
+ {
+ return '';
+ }
+
+ if (source != null && target != null)
+ {
+ var error = '';
+
+ // Checks if the cells are already connected
+ // and adds an error message if required
+ if (!this.multigraph)
+ {
+ var tmp = this.model.getEdgesBetween(source, target, true);
+
+ // Checks if the source and target are not connected by another edge
+ if (tmp.length > 1 || (tmp.length == 1 && tmp[0] != edge))
+ {
+ error += (mxResources.get(this.alreadyConnectedResource) ||
+ this.alreadyConnectedResource)+'\n';
+ }
+ }
+
+ // Gets the number of outgoing edges from the source
+ // and the number of incoming edges from the target
+ // without counting the edge being currently changed.
+ var sourceOut = this.model.getDirectedEdgeCount(source, true, edge);
+ var targetIn = this.model.getDirectedEdgeCount(target, false, edge);
+
+ // Checks the change against each multiplicity rule
+ if (this.multiplicities != null)
+ {
+ for (var i = 0; i < this.multiplicities.length; i++)
+ {
+ var err = this.multiplicities[i].check(this, edge, source,
+ target, sourceOut, targetIn);
+
+ if (err != null)
+ {
+ error += err;
+ }
+ }
+ }
+
+ // Validates the source and target terminals independently
+ var err = this.validateEdge(edge, source, target);
+
+ if (err != null)
+ {
+ error += err;
+ }
+
+ return (error.length > 0) ? error : null;
+ }
+
+ return (this.allowDanglingEdges) ? null : '';
+};
+
+/**
+ * Function: validateEdge
+ *
+ * Hook method for subclassers to return an error message for the given
+ * edge and terminals. This implementation returns null.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.validateEdge = function(edge, source, target)
+{
+ return null;
+};
+
+/**
+ * Function: validateGraph
+ *
+ * Validates the graph by validating each descendant of the given cell or
+ * the root of the model. Context is an object that contains the validation
+ * state for the complete validation run. The validation errors are
+ * attached to their cells using <setCellWarning>. This function returns true
+ * if no validation errors exist in the graph.
+ *
+ * Paramters:
+ *
+ * cell - Optional <mxCell> to start the validation recursion. Default is
+ * the graph root.
+ * context - Object that represents the global validation state.
+ */
+mxGraph.prototype.validateGraph = function(cell, context)
+{
+ cell = (cell != null) ? cell : this.model.getRoot();
+ context = (context != null) ? context : new Object();
+
+ var isValid = true;
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var tmp = this.model.getChildAt(cell, i);
+ var ctx = context;
+
+ if (this.isValidRoot(tmp))
+ {
+ ctx = new Object();
+ }
+
+ var warn = this.validateGraph(tmp, ctx);
+
+ if (warn != null)
+ {
+ this.setCellWarning(tmp, warn.replace(/\n/g, '<br>'));
+ }
+ else
+ {
+ this.setCellWarning(tmp, null);
+ }
+
+ isValid = isValid && warn == null;
+ }
+
+ var warning = '';
+
+ // Adds error for invalid children if collapsed (children invisible)
+ if (this.isCellCollapsed(cell) && !isValid)
+ {
+ warning += (mxResources.get(this.containsValidationErrorsResource) ||
+ this.containsValidationErrorsResource)+'\n';
+ }
+
+ // Checks edges and cells using the defined multiplicities
+ if (this.model.isEdge(cell))
+ {
+ warning += this.getEdgeValidationError(cell,
+ this.model.getTerminal(cell, true),
+ this.model.getTerminal(cell, false)) || '';
+ }
+ else
+ {
+ warning += this.getCellValidationError(cell) || '';
+ }
+
+ // Checks custom validation rules
+ var err = this.validateCell(cell, context);
+
+ if (err != null)
+ {
+ warning += err;
+ }
+
+ // Updates the display with the warning icons
+ // before any potential alerts are displayed.
+ // LATER: Move this into addCellOverlay. Redraw
+ // should check if overlay was added or removed.
+ if (this.model.getParent(cell) == null)
+ {
+ this.view.validate();
+ }
+
+ return (warning.length > 0 || !isValid) ? warning : null;
+};
+
+/**
+ * Function: getCellValidationError
+ *
+ * Checks all <multiplicities> that cannot be enforced while the graph is
+ * being modified, namely, all multiplicities that require a minimum of
+ * 1 edge.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the multiplicities should be checked.
+ */
+mxGraph.prototype.getCellValidationError = function(cell)
+{
+ var outCount = this.model.getDirectedEdgeCount(cell, true);
+ var inCount = this.model.getDirectedEdgeCount(cell, false);
+ var value = this.model.getValue(cell);
+ var error = '';
+
+ if (this.multiplicities != null)
+ {
+ for (var i = 0; i < this.multiplicities.length; i++)
+ {
+ var rule = this.multiplicities[i];
+
+ if (rule.source && mxUtils.isNode(value, rule.type,
+ rule.attr, rule.value) && ((rule.max == 0 && outCount > 0) ||
+ (rule.min == 1 && outCount == 0) || (rule.max == 1 && outCount > 1)))
+ {
+ error += rule.countError + '\n';
+ }
+ else if (!rule.source && mxUtils.isNode(value, rule.type,
+ rule.attr, rule.value) && ((rule.max == 0 && inCount > 0) ||
+ (rule.min == 1 && inCount == 0) || (rule.max == 1 && inCount > 1)))
+ {
+ error += rule.countError + '\n';
+ }
+ }
+ }
+
+ return (error.length > 0) ? error : null;
+};
+
+/**
+ * Function: validateCell
+ *
+ * Hook method for subclassers to return an error message for the given
+ * cell and validation context. This implementation returns null. Any HTML
+ * breaks will be converted to linefeeds in the calling method.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the cell to validate.
+ * context - Object that represents the global validation state.
+ */
+mxGraph.prototype.validateCell = function(cell, context)
+{
+ return null;
+};
+
+/**
+ * Group: Graph appearance
+ */
+
+/**
+ * Function: getBackgroundImage
+ *
+ * Returns the <backgroundImage> as an <mxImage>.
+ */
+mxGraph.prototype.getBackgroundImage = function()
+{
+ return this.backgroundImage;
+};
+
+/**
+ * Function: setBackgroundImage
+ *
+ * Sets the new <backgroundImage>.
+ *
+ * Parameters:
+ *
+ * image - New <mxImage> to be used for the background.
+ */
+mxGraph.prototype.setBackgroundImage = function(image)
+{
+ this.backgroundImage = image;
+};
+
+/**
+ * Function: getFoldingImage
+ *
+ * Returns the <mxImage> used to display the collapsed state of
+ * the specified cell state. This returns null for all edges.
+ */
+mxGraph.prototype.getFoldingImage = function(state)
+{
+ if (state != null && this.foldingEnabled && !this.getModel().isEdge(state.cell))
+ {
+ var tmp = this.isCellCollapsed(state.cell);
+
+ if (this.isCellFoldable(state.cell, !tmp))
+ {
+ return (tmp) ? this.collapsedImage : this.expandedImage;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: convertValueToString
+ *
+ * Returns the textual representation for the given cell. This
+ * implementation returns the nodename or string-representation of the user
+ * object.
+ *
+ * Example:
+ *
+ * The following returns the label attribute from the cells user
+ * object if it is an XML node.
+ *
+ * (code)
+ * graph.convertValueToString = function(cell)
+ * {
+ * return cell.getAttribute('label');
+ * }
+ * (end)
+ *
+ * See also: <cellLabelChanged>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose textual representation should be returned.
+ */
+mxGraph.prototype.convertValueToString = function(cell)
+{
+ var value = this.model.getValue(cell);
+
+ if (value != null)
+ {
+ if (mxUtils.isNode(value))
+ {
+ return value.nodeName;
+ }
+ else if (typeof(value.toString) == 'function')
+ {
+ return value.toString();
+ }
+ }
+
+ return '';
+};
+
+/**
+ * Function: getLabel
+ *
+ * Returns a string or DOM node that represents the label for the given
+ * cell. This implementation uses <convertValueToString> if <labelsVisible>
+ * is true. Otherwise it returns an empty string.
+ *
+ * To truncate label to match the size of the cell, the following code
+ * can be used.
+ *
+ * (code)
+ * graph.getLabel = function(cell)
+ * {
+ * var label = mxGraph.prototype.getLabel.apply(this, arguments);
+ *
+ * if (label != null && this.model.isVertex(cell))
+ * {
+ * var geo = this.getCellGeometry(cell);
+ *
+ * if (geo != null)
+ * {
+ * var max = parseInt(geo.width / 8);
+ *
+ * if (label.length > max)
+ * {
+ * label = label.substring(0, max)+'...';
+ * }
+ * }
+ * }
+ * return mxUtils.htmlEntities(label);
+ * }
+ * (end)
+ *
+ * A resize listener is needed in the graph to force a repaint of the label
+ * after a resize.
+ *
+ * (code)
+ * graph.addListener(mxEvent.RESIZE_CELLS, function(sender, evt)
+ * {
+ * var cells = evt.getProperty('cells');
+ *
+ * for (var i = 0; i < cells.length; i++)
+ * {
+ * this.view.removeState(cells[i]);
+ * }
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be returned.
+ */
+mxGraph.prototype.getLabel = function(cell)
+{
+ var result = '';
+
+ if (this.labelsVisible && cell != null)
+ {
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ if (!mxUtils.getValue(style, mxConstants.STYLE_NOLABEL, false))
+ {
+ result = this.convertValueToString(cell);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: isHtmlLabel
+ *
+ * Returns true if the label must be rendered as HTML markup. The default
+ * implementation returns <htmlLabels>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be displayed as HTML markup.
+ */
+mxGraph.prototype.isHtmlLabel = function(cell)
+{
+ return this.isHtmlLabels();
+};
+
+/**
+ * Function: isHtmlLabels
+ *
+ * Returns <htmlLabels>.
+ */
+mxGraph.prototype.isHtmlLabels = function()
+{
+ return this.htmlLabels;
+};
+
+/**
+ * Function: setHtmlLabels
+ *
+ * Sets <htmlLabels>.
+ */
+mxGraph.prototype.setHtmlLabels = function(value)
+{
+ this.htmlLabels = value;
+};
+
+/**
+ * Function: isWrapping
+ *
+ * This enables wrapping for HTML labels.
+ *
+ * Returns true if no white-space CSS style directive should be used for
+ * displaying the given cells label. This implementation returns true if
+ * <mxConstants.STYLE_WHITE_SPACE> in the style of the given cell is 'wrap'.
+ *
+ * This is used as a workaround for IE ignoring the white-space directive
+ * of child elements if the directive appears in a parent element. It
+ * should be overridden to return true if a white-space directive is used
+ * in the HTML markup that represents the given cells label. In order for
+ * HTML markup to work in labels, <isHtmlLabel> must also return true
+ * for the given cell.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.getLabel = function(cell)
+ * {
+ * var tmp = mxGraph.prototype.getLabel.apply(this, arguments); // "supercall"
+ *
+ * if (this.model.isEdge(cell))
+ * {
+ * tmp = '<div style="width: 150px; white-space:normal;">'+tmp+'</div>';
+ * }
+ *
+ * return tmp;
+ * }
+ *
+ * graph.isWrapping = function(state)
+ * {
+ * return this.model.isEdge(state.cell);
+ * }
+ * (end)
+ *
+ * Makes sure no edge label is wider than 150 pixels, otherwise the content
+ * is wrapped. Note: No width must be specified for wrapped vertex labels as
+ * the vertex defines the width in its geometry.
+ *
+ * Parameters:
+ *
+ * state - <mxCell> whose label should be wrapped.
+ */
+mxGraph.prototype.isWrapping = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return (style != null) ? style[mxConstants.STYLE_WHITE_SPACE] == 'wrap' : false;
+};
+
+/**
+ * Function: isLabelClipped
+ *
+ * Returns true if the overflow portion of labels should be hidden. If this
+ * returns true then vertex labels will be clipped to the size of the vertices.
+ * This implementation returns true if <mxConstants.STYLE_OVERFLOW> in the
+ * style of the given cell is 'hidden'.
+ *
+ * Parameters:
+ *
+ * state - <mxCell> whose label should be clipped.
+ */
+mxGraph.prototype.isLabelClipped = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return (style != null) ? style[mxConstants.STYLE_OVERFLOW] == 'hidden' : false;
+};
+
+/**
+ * Function: getTooltip
+ *
+ * Returns the string or DOM node that represents the tooltip for the given
+ * state, node and coordinate pair. This implementation checks if the given
+ * node is a folding icon or overlay and returns the respective tooltip. If
+ * this does not result in a tooltip, the handler for the cell is retrieved
+ * from <selectionCellsHandler> and the optional getTooltipForNode method is
+ * called. If no special tooltip exists here then <getTooltipForCell> is used
+ * with the cell in the given state as the argument to return a tooltip for the
+ * given state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose tooltip should be returned.
+ * node - DOM node that is currently under the mouse.
+ * x - X-coordinate of the mouse.
+ * y - Y-coordinate of the mouse.
+ */
+mxGraph.prototype.getTooltip = function(state, node, x, y)
+{
+ var tip = null;
+
+ if (state != null)
+ {
+ // Checks if the mouse is over the folding icon
+ if (state.control != null && (node == state.control.node ||
+ node.parentNode == state.control.node))
+ {
+ tip = this.collapseExpandResource;
+ tip = mxResources.get(tip) || tip;
+ }
+
+ if (tip == null && state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ // LATER: Exit loop if tip is not null
+ if (tip == null && (node == shape.node || node.parentNode == shape.node))
+ {
+ tip = shape.overlay.toString();
+ }
+ });
+ }
+
+ if (tip == null)
+ {
+ var handler = this.selectionCellsHandler.getHandler(state.cell);
+
+ if (handler != null && typeof(handler.getTooltipForNode) == 'function')
+ {
+ tip = handler.getTooltipForNode(node);
+ }
+ }
+
+ if (tip == null)
+ {
+ tip = this.getTooltipForCell(state.cell);
+ }
+ }
+
+ return tip;
+};
+
+/**
+ * Function: getTooltipForCell
+ *
+ * Returns the string or DOM node to be used as the tooltip for the given
+ * cell. This implementation uses the cells getTooltip function if it
+ * exists, or else it returns <convertValueToString> for the cell.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.getTooltipForCell = function(cell)
+ * {
+ * return 'Hello, World!';
+ * }
+ * (end)
+ *
+ * Replaces all tooltips with the string Hello, World!
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose tooltip should be returned.
+ */
+mxGraph.prototype.getTooltipForCell = function(cell)
+{
+ var tip = null;
+
+ if (cell != null && cell.getTooltip != null)
+ {
+ tip = cell.getTooltip();
+ }
+ else
+ {
+ tip = this.convertValueToString(cell);
+ }
+
+ return tip;
+};
+
+/**
+ * Function: getCursorForCell
+ *
+ * Returns the cursor value to be used for the CSS of the shape for the
+ * given cell. This implementation returns null.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose cursor should be returned.
+ */
+mxGraph.prototype.getCursorForCell = function(cell)
+{
+ return null;
+};
+
+/**
+ * Function: getStartSize
+ *
+ * Returns the start size of the given swimlane, that is, the width or
+ * height of the part that contains the title, depending on the
+ * horizontal style. The return value is an <mxRectangle> with either
+ * width or height set as appropriate.
+ *
+ * Parameters:
+ *
+ * swimlane - <mxCell> whose start size should be returned.
+ */
+mxGraph.prototype.getStartSize = function(swimlane)
+{
+ var result = new mxRectangle();
+ var state = this.view.getState(swimlane);
+ var style = (state != null) ? state.style : this.getCellStyle(swimlane);
+
+ if (style != null)
+ {
+ var size = parseInt(mxUtils.getValue(style,
+ mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+
+ if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ result.height = size;
+ }
+ else
+ {
+ result.width = size;
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getImage
+ *
+ * Returns the image URL for the given cell state. This implementation
+ * returns the value stored under <mxConstants.STYLE_IMAGE> in the cell
+ * style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose image URL should be returned.
+ */
+mxGraph.prototype.getImage = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_IMAGE] : null;
+};
+
+/**
+ * Function: getVerticalAlign
+ *
+ * Returns the vertical alignment for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_VERTICAL_ALIGN> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose vertical alignment should be
+ * returned.
+ */
+mxGraph.prototype.getVerticalAlign = function(state)
+{
+ return (state != null && state.style != null) ?
+ (state.style[mxConstants.STYLE_VERTICAL_ALIGN] ||
+ mxConstants.ALIGN_MIDDLE ):
+ null;
+};
+
+/**
+ * Function: getIndicatorColor
+ *
+ * Returns the indicator color for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_COLOR> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose indicator color should be
+ * returned.
+ */
+mxGraph.prototype.getIndicatorColor = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_INDICATOR_COLOR] : null;
+};
+
+/**
+ * Function: getIndicatorGradientColor
+ *
+ * Returns the indicator gradient color for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_GRADIENTCOLOR> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose indicator gradient color should be
+ * returned.
+ */
+mxGraph.prototype.getIndicatorGradientColor = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR] : null;
+};
+
+/**
+ * Function: getIndicatorShape
+ *
+ * Returns the indicator shape for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_SHAPE> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose indicator shape should be returned.
+ */
+mxGraph.prototype.getIndicatorShape = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_INDICATOR_SHAPE] : null;
+};
+
+/**
+ * Function: getIndicatorImage
+ *
+ * Returns the indicator image for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_IMAGE> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose indicator image should be returned.
+ */
+mxGraph.prototype.getIndicatorImage = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_INDICATOR_IMAGE] : null;
+};
+
+/**
+ * Function: getBorder
+ *
+ * Returns the value of <border>.
+ */
+mxGraph.prototype.getBorder = function()
+{
+ return this.border;
+};
+
+/**
+ * Function: setBorder
+ *
+ * Sets the value of <border>.
+ *
+ * Parameters:
+ *
+ * value - Positive integer that represents the border to be used.
+ */
+mxGraph.prototype.setBorder = function(value)
+{
+ this.border = value;
+};
+
+/**
+ * Function: isSwimlane
+ *
+ * Returns true if the given cell is a swimlane in the graph. A swimlane is
+ * a container cell with some specific behaviour. This implementation
+ * checks if the shape associated with the given cell is a <mxSwimlane>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be checked.
+ */
+mxGraph.prototype.isSwimlane = function (cell)
+{
+ if (cell != null)
+ {
+ if (this.model.getParent(cell) != this.model.getRoot())
+ {
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ if (style != null && !this.model.isEdge(cell))
+ {
+ return style[mxConstants.STYLE_SHAPE] ==
+ mxConstants.SHAPE_SWIMLANE;
+ }
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Group: Graph behaviour
+ */
+
+/**
+ * Function: isResizeContainer
+ *
+ * Returns <resizeContainer>.
+ */
+mxGraph.prototype.isResizeContainer = function()
+{
+ return this.resizeContainer;
+};
+
+/**
+ * Function: setResizeContainer
+ *
+ * Sets <resizeContainer>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the container should be resized.
+ */
+mxGraph.prototype.setResizeContainer = function(value)
+{
+ this.resizeContainer = value;
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if the graph is <enabled>.
+ */
+mxGraph.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Specifies if the graph should allow any interactions. This
+ * implementation updates <enabled>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should be enabled.
+ */
+mxGraph.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isEscapeEnabled
+ *
+ * Returns <escapeEnabled>.
+ */
+mxGraph.prototype.isEscapeEnabled = function()
+{
+ return this.escapeEnabled;
+};
+
+/**
+ * Function: setEscapeEnabled
+ *
+ * Sets <escapeEnabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean indicating if escape should be enabled.
+ */
+mxGraph.prototype.setEscapeEnabled = function(value)
+{
+ this.escapeEnabled = value;
+};
+
+/**
+ * Function: isInvokesStopCellEditing
+ *
+ * Returns <invokesStopCellEditing>.
+ */
+mxGraph.prototype.isInvokesStopCellEditing = function()
+{
+ return this.invokesStopCellEditing;
+};
+
+/**
+ * Function: setInvokesStopCellEditing
+ *
+ * Sets <invokesStopCellEditing>.
+ */
+mxGraph.prototype.setInvokesStopCellEditing = function(value)
+{
+ this.invokesStopCellEditing = value;
+};
+
+/**
+ * Function: isEnterStopsCellEditing
+ *
+ * Returns <enterStopsCellEditing>.
+ */
+mxGraph.prototype.isEnterStopsCellEditing = function()
+{
+ return this.enterStopsCellEditing;
+};
+
+/**
+ * Function: setEnterStopsCellEditing
+ *
+ * Sets <enterStopsCellEditing>.
+ */
+mxGraph.prototype.setEnterStopsCellEditing = function(value)
+{
+ this.enterStopsCellEditing = value;
+};
+
+/**
+ * Function: isCellLocked
+ *
+ * Returns true if the given cell may not be moved, sized, bended,
+ * disconnected, edited or selected. This implementation returns true for
+ * all vertices with a relative geometry if <locked> is false.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose locked state should be returned.
+ */
+mxGraph.prototype.isCellLocked = function(cell)
+{
+ var geometry = this.model.getGeometry(cell);
+
+ return this.isCellsLocked() || (geometry != null &&
+ this.model.isVertex(cell) && geometry.relative);
+};
+
+/**
+ * Function: isCellsLocked
+ *
+ * Returns true if the given cell may not be moved, sized, bended,
+ * disconnected, edited or selected. This implementation returns true for
+ * all vertices with a relative geometry if <locked> is false.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose locked state should be returned.
+ */
+mxGraph.prototype.isCellsLocked = function()
+{
+ return this.cellsLocked;
+};
+
+/**
+ * Function: setLocked
+ *
+ * Sets if any cell may be moved, sized, bended, disconnected, edited or
+ * selected.
+ *
+ * Parameters:
+ *
+ * value - Boolean that defines the new value for <cellsLocked>.
+ */
+mxGraph.prototype.setCellsLocked = function(value)
+{
+ this.cellsLocked = value;
+};
+
+/**
+ * Function: getCloneableCells
+ *
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getCloneableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.isCellCloneable(cell);
+ }));
+};
+
+/**
+ * Function: isCellCloneable
+ *
+ * Returns true if the given cell is cloneable. This implementation returns
+ * <isCellsCloneable> for all cells unless a cell style specifies
+ * <mxConstants.STYLE_CLONEABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> whose cloneable state should be returned.
+ */
+mxGraph.prototype.isCellCloneable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsCloneable() && style[mxConstants.STYLE_CLONEABLE] != 0;
+};
+
+/**
+ * Function: isCellsCloneable
+ *
+ * Returns <cellsCloneable>, that is, if the graph allows cloning of cells
+ * by using control-drag.
+ */
+mxGraph.prototype.isCellsCloneable = function()
+{
+ return this.cellsCloneable;
+};
+
+/**
+ * Function: setCellsCloneable
+ *
+ * Specifies if the graph should allow cloning of cells by holding down the
+ * control key while cells are being moved. This implementation updates
+ * <cellsCloneable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should be cloneable.
+ */
+mxGraph.prototype.setCellsCloneable = function(value)
+{
+ this.cellsCloneable = value;
+};
+
+/**
+ * Function: getExportableCells
+ *
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getExportableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.canExportCell(cell);
+ }));
+};
+
+/**
+ * Function: canExportCell
+ *
+ * Returns true if the given cell may be exported to the clipboard. This
+ * implementation returns <exportEnabled> for all cells.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the cell to be exported.
+ */
+mxGraph.prototype.canExportCell = function(cell)
+{
+ return this.exportEnabled;
+};
+
+/**
+ * Function: getImportableCells
+ *
+ * Returns the cells which may be imported in the given array of cells.
+ */
+mxGraph.prototype.getImportableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.canImportCell(cell);
+ }));
+};
+
+/**
+ * Function: canImportCell
+ *
+ * Returns true if the given cell may be imported from the clipboard.
+ * This implementation returns <importEnabled> for all cells.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the cell to be imported.
+ */
+mxGraph.prototype.canImportCell = function(cell)
+{
+ return this.importEnabled;
+};
+
+/**
+ * Function: isCellSelectable
+ *
+ * Returns true if the given cell is selectable. This implementation
+ * returns <cellsSelectable>.
+ *
+ * To add a new style for making cells (un)selectable, use the following code.
+ *
+ * (code)
+ * mxGraph.prototype.isCellSelectable = function(cell)
+ * {
+ * var state = this.view.getState(cell);
+ * var style = (state != null) ? state.style : this.getCellStyle(cell);
+ *
+ * return this.isCellsSelectable() && !this.isCellLocked(cell) && style['selectable'] != 0;
+ * };
+ * (end)
+ *
+ * You can then use the new style as shown in this example.
+ *
+ * (code)
+ * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'selectable=0');
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose selectable state should be returned.
+ */
+mxGraph.prototype.isCellSelectable = function(cell)
+{
+ return this.isCellsSelectable();
+};
+
+/**
+ * Function: isCellsSelectable
+ *
+ * Returns <cellsSelectable>.
+ */
+mxGraph.prototype.isCellsSelectable = function()
+{
+ return this.cellsSelectable;
+};
+
+/**
+ * Function: setCellsSelectable
+ *
+ * Sets <cellsSelectable>.
+ */
+mxGraph.prototype.setCellsSelectable = function(value)
+{
+ this.cellsSelectable = value;
+};
+
+/**
+ * Function: getDeletableCells
+ *
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getDeletableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.isCellDeletable(cell);
+ }));
+};
+
+/**
+ * Function: isCellDeletable
+ *
+ * Returns true if the given cell is moveable. This returns
+ * <cellsDeletable> for all given cells if a cells style does not specify
+ * <mxConstants.STYLE_DELETABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose deletable state should be returned.
+ */
+mxGraph.prototype.isCellDeletable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsDeletable() && style[mxConstants.STYLE_DELETABLE] != 0;
+};
+
+/**
+ * Function: isCellsDeletable
+ *
+ * Returns <cellsDeletable>.
+ */
+mxGraph.prototype.isCellsDeletable = function()
+{
+ return this.cellsDeletable;
+};
+
+/**
+ * Function: setCellsDeletable
+ *
+ * Sets <cellsDeletable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow deletion of cells.
+ */
+mxGraph.prototype.setCellsDeletable = function(value)
+{
+ this.cellsDeletable = value;
+};
+
+/**
+ * Function: isLabelMovable
+ *
+ * Returns true if the given edges's label is moveable. This returns
+ * <movable> for all given cells if <isLocked> does not return true
+ * for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be moved.
+ */
+mxGraph.prototype.isLabelMovable = function(cell)
+{
+ return !this.isCellLocked(cell) &&
+ ((this.model.isEdge(cell) && this.edgeLabelsMovable) ||
+ (this.model.isVertex(cell) && this.vertexLabelsMovable));
+};
+
+/**
+ * Function: getMovableCells
+ *
+ * Returns the cells which are movable in the given array of cells.
+ */
+mxGraph.prototype.getMovableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.isCellMovable(cell);
+ }));
+};
+
+/**
+ * Function: isCellMovable
+ *
+ * Returns true if the given cell is moveable. This returns <cellsMovable>
+ * for all given cells if <isCellLocked> does not return true for the given
+ * cell and its style does not specify <mxConstants.STYLE_MOVABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose movable state should be returned.
+ */
+mxGraph.prototype.isCellMovable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsMovable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_MOVABLE] != 0;
+};
+
+/**
+ * Function: isCellsMovable
+ *
+ * Returns <cellsMovable>.
+ */
+mxGraph.prototype.isCellsMovable = function()
+{
+ return this.cellsMovable;
+};
+
+/**
+ * Function: setCellsMovable
+ *
+ * Specifies if the graph should allow moving of cells. This implementation
+ * updates <cellsMsovable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow moving of cells.
+ */
+mxGraph.prototype.setCellsMovable = function(value)
+{
+ this.cellsMovable = value;
+};
+
+/**
+ * Function: isGridEnabled
+ *
+ * Returns <gridEnabled> as a boolean.
+ */
+mxGraph.prototype.isGridEnabled = function()
+{
+ return this.gridEnabled;
+};
+
+/**
+ * Function: setGridEnabled
+ *
+ * Specifies if the grid should be enabled.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the grid should be enabled.
+ */
+mxGraph.prototype.setGridEnabled = function(value)
+{
+ this.gridEnabled = value;
+};
+
+/**
+ * Function: isPortsEnabled
+ *
+ * Returns <portsEnabled> as a boolean.
+ */
+mxGraph.prototype.isPortsEnabled = function()
+{
+ return this.portsEnabled;
+};
+
+/**
+ * Function: setPortsEnabled
+ *
+ * Specifies if the ports should be enabled.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the ports should be enabled.
+ */
+mxGraph.prototype.setPortsEnabled = function(value)
+{
+ this.portsEnabled = value;
+};
+
+/**
+ * Function: getGridSize
+ *
+ * Returns <gridSize>.
+ */
+mxGraph.prototype.getGridSize = function()
+{
+ return this.gridSize;
+};
+
+/**
+ * Function: setGridSize
+ *
+ * Sets <gridSize>.
+ */
+mxGraph.prototype.setGridSize = function(value)
+{
+ this.gridSize = value;
+};
+
+/**
+ * Function: getTolerance
+ *
+ * Returns <tolerance>.
+ */
+mxGraph.prototype.getTolerance = function()
+{
+ return this.tolerance;
+};
+
+/**
+ * Function: setTolerance
+ *
+ * Sets <tolerance>.
+ */
+mxGraph.prototype.setTolerance = function(value)
+{
+ this.tolerance = value;
+};
+
+/**
+ * Function: isVertexLabelsMovable
+ *
+ * Returns <vertexLabelsMovable>.
+ */
+mxGraph.prototype.isVertexLabelsMovable = function()
+{
+ return this.vertexLabelsMovable;
+};
+
+/**
+ * Function: setVertexLabelsMovable
+ *
+ * Sets <vertexLabelsMovable>.
+ */
+mxGraph.prototype.setVertexLabelsMovable = function(value)
+{
+ this.vertexLabelsMovable = value;
+};
+
+/**
+ * Function: isEdgeLabelsMovable
+ *
+ * Returns <edgeLabelsMovable>.
+ */
+mxGraph.prototype.isEdgeLabelsMovable = function()
+{
+ return this.edgeLabelsMovable;
+};
+
+/**
+ * Function: isEdgeLabelsMovable
+ *
+ * Sets <edgeLabelsMovable>.
+ */
+mxGraph.prototype.setEdgeLabelsMovable = function(value)
+{
+ this.edgeLabelsMovable = value;
+};
+
+/**
+ * Function: isSwimlaneNesting
+ *
+ * Returns <swimlaneNesting> as a boolean.
+ */
+mxGraph.prototype.isSwimlaneNesting = function()
+{
+ return this.swimlaneNesting;
+};
+
+/**
+ * Function: setSwimlaneNesting
+ *
+ * Specifies if swimlanes can be nested by drag and drop. This is only
+ * taken into account if dropEnabled is true.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if swimlanes can be nested.
+ */
+mxGraph.prototype.setSwimlaneNesting = function(value)
+{
+ this.swimlaneNesting = value;
+};
+
+/**
+ * Function: isSwimlaneSelectionEnabled
+ *
+ * Returns <swimlaneSelectionEnabled> as a boolean.
+ */
+mxGraph.prototype.isSwimlaneSelectionEnabled = function()
+{
+ return this.swimlaneSelectionEnabled;
+};
+
+/**
+ * Function: setSwimlaneSelectionEnabled
+ *
+ * Specifies if swimlanes should be selected if the mouse is released
+ * over their content area.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if swimlanes content areas
+ * should be selected when the mouse is released over them.
+ */
+mxGraph.prototype.setSwimlaneSelectionEnabled = function(value)
+{
+ this.swimlaneSelectionEnabled = value;
+};
+
+/**
+ * Function: isMultigraph
+ *
+ * Returns <multigraph> as a boolean.
+ */
+mxGraph.prototype.isMultigraph = function()
+{
+ return this.multigraph;
+};
+
+/**
+ * Function: setMultigraph
+ *
+ * Specifies if the graph should allow multiple connections between the
+ * same pair of vertices.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph allows multiple connections
+ * between the same pair of vertices.
+ */
+mxGraph.prototype.setMultigraph = function(value)
+{
+ this.multigraph = value;
+};
+
+/**
+ * Function: isAllowLoops
+ *
+ * Returns <allowLoops> as a boolean.
+ */
+mxGraph.prototype.isAllowLoops = function()
+{
+ return this.allowLoops;
+};
+
+/**
+ * Function: setAllowDanglingEdges
+ *
+ * Specifies if dangling edges are allowed, that is, if edges are allowed
+ * that do not have a source and/or target terminal defined.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if dangling edges are allowed.
+ */
+mxGraph.prototype.setAllowDanglingEdges = function(value)
+{
+ this.allowDanglingEdges = value;
+};
+
+/**
+ * Function: isAllowDanglingEdges
+ *
+ * Returns <allowDanglingEdges> as a boolean.
+ */
+mxGraph.prototype.isAllowDanglingEdges = function()
+{
+ return this.allowDanglingEdges;
+};
+
+/**
+ * Function: setConnectableEdges
+ *
+ * Specifies if edges should be connectable.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if edges should be connectable.
+ */
+mxGraph.prototype.setConnectableEdges = function(value)
+{
+ this.connectableEdges = value;
+};
+
+/**
+ * Function: isConnectableEdges
+ *
+ * Returns <connectableEdges> as a boolean.
+ */
+mxGraph.prototype.isConnectableEdges = function()
+{
+ return this.connectableEdges;
+};
+
+/**
+ * Function: setCloneInvalidEdges
+ *
+ * Specifies if edges should be inserted when cloned but not valid wrt.
+ * <getEdgeValidationError>. If false such edges will be silently ignored.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if cloned invalid edges should be
+ * inserted into the graph or ignored.
+ */
+mxGraph.prototype.setCloneInvalidEdges = function(value)
+{
+ this.cloneInvalidEdges = value;
+};
+
+/**
+ * Function: isCloneInvalidEdges
+ *
+ * Returns <cloneInvalidEdges> as a boolean.
+ */
+mxGraph.prototype.isCloneInvalidEdges = function()
+{
+ return this.cloneInvalidEdges;
+};
+
+/**
+ * Function: setAllowLoops
+ *
+ * Specifies if loops are allowed.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if loops are allowed.
+ */
+mxGraph.prototype.setAllowLoops = function(value)
+{
+ this.allowLoops = value;
+};
+
+/**
+ * Function: isDisconnectOnMove
+ *
+ * Returns <disconnectOnMove> as a boolean.
+ */
+mxGraph.prototype.isDisconnectOnMove = function()
+{
+ return this.disconnectOnMove;
+};
+
+/**
+ * Function: setDisconnectOnMove
+ *
+ * Specifies if edges should be disconnected when moved. (Note: Cloned
+ * edges are always disconnected.)
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if edges should be disconnected
+ * when moved.
+ */
+mxGraph.prototype.setDisconnectOnMove = function(value)
+{
+ this.disconnectOnMove = value;
+};
+
+/**
+ * Function: isDropEnabled
+ *
+ * Returns <dropEnabled> as a boolean.
+ */
+mxGraph.prototype.isDropEnabled = function()
+{
+ return this.dropEnabled;
+};
+
+/**
+ * Function: setDropEnabled
+ *
+ * Specifies if the graph should allow dropping of cells onto or into other
+ * cells.
+ *
+ * Parameters:
+ *
+ * dropEnabled - Boolean indicating if the graph should allow dropping
+ * of cells into other cells.
+ */
+mxGraph.prototype.setDropEnabled = function(value)
+{
+ this.dropEnabled = value;
+};
+
+/**
+ * Function: isSplitEnabled
+ *
+ * Returns <splitEnabled> as a boolean.
+ */
+mxGraph.prototype.isSplitEnabled = function()
+{
+ return this.splitEnabled;
+};
+
+/**
+ * Function: setSplitEnabled
+ *
+ * Specifies if the graph should allow dropping of cells onto or into other
+ * cells.
+ *
+ * Parameters:
+ *
+ * dropEnabled - Boolean indicating if the graph should allow dropping
+ * of cells into other cells.
+ */
+mxGraph.prototype.setSplitEnabled = function(value)
+{
+ this.splitEnabled = value;
+};
+
+/**
+ * Function: isCellResizable
+ *
+ * Returns true if the given cell is resizable. This returns
+ * <cellsResizable> for all given cells if <isCellLocked> does not return
+ * true for the given cell and its style does not specify
+ * <mxConstants.STYLE_RESIZABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose resizable state should be returned.
+ */
+mxGraph.prototype.isCellResizable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsResizable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_RESIZABLE] != 0;
+};
+
+/**
+ * Function: isCellsResizable
+ *
+ * Returns <cellsResizable>.
+ */
+mxGraph.prototype.isCellsResizable = function()
+{
+ return this.cellsResizable;
+};
+
+/**
+ * Function: setCellsResizable
+ *
+ * Specifies if the graph should allow resizing of cells. This
+ * implementation updates <cellsResizable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow resizing of
+ * cells.
+ */
+mxGraph.prototype.setCellsResizable = function(value)
+{
+ this.cellsResizable = value;
+};
+
+/**
+ * Function: isTerminalPointMovable
+ *
+ * Returns true if the given terminal point is movable. This is independent
+ * from <isCellConnectable> and <isCellDisconnectable> and controls if terminal
+ * points can be moved in the graph if the edge is not connected. Note that it
+ * is required for this to return true to connect unconnected edges. This
+ * implementation returns true.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose terminal point should be moved.
+ * source - Boolean indicating if the source or target terminal should be moved.
+ */
+mxGraph.prototype.isTerminalPointMovable = function(cell, source)
+{
+ return true;
+};
+
+/**
+ * Function: isCellBendable
+ *
+ * Returns true if the given cell is bendable. This returns <cellsBendable>
+ * for all given cells if <isLocked> does not return true for the given
+ * cell and its style does not specify <mxConstants.STYLE_BENDABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose bendable state should be returned.
+ */
+mxGraph.prototype.isCellBendable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsBendable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_BENDABLE] != 0;
+};
+
+/**
+ * Function: isCellsBendable
+ *
+ * Returns <cellsBenadable>.
+ */
+mxGraph.prototype.isCellsBendable = function()
+{
+ return this.cellsBendable;
+};
+
+/**
+ * Function: setCellsBendable
+ *
+ * Specifies if the graph should allow bending of edges. This
+ * implementation updates <bendable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow bending of
+ * edges.
+ */
+mxGraph.prototype.setCellsBendable = function(value)
+{
+ this.cellsBendable = value;
+};
+
+/**
+ * Function: isCellEditable
+ *
+ * Returns true if the given cell is editable. This returns <cellsEditable> for
+ * all given cells if <isCellLocked> does not return true for the given cell
+ * and its style does not specify <mxConstants.STYLE_EDITABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose editable state should be returned.
+ */
+mxGraph.prototype.isCellEditable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsEditable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_EDITABLE] != 0;
+};
+
+/**
+ * Function: isCellsEditable
+ *
+ * Returns <cellsEditable>.
+ */
+mxGraph.prototype.isCellsEditable = function()
+{
+ return this.cellsEditable;
+};
+
+/**
+ * Function: setCellsEditable
+ *
+ * Specifies if the graph should allow in-place editing for cell labels.
+ * This implementation updates <cellsEditable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow in-place
+ * editing.
+ */
+mxGraph.prototype.setCellsEditable = function(value)
+{
+ this.cellsEditable = value;
+};
+
+/**
+ * Function: isCellDisconnectable
+ *
+ * Returns true if the given cell is disconnectable from the source or
+ * target terminal. This returns <isCellsDisconnectable> for all given
+ * cells if <isCellLocked> does not return true for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose disconnectable state should be returned.
+ * terminal - <mxCell> that represents the source or target terminal.
+ * source - Boolean indicating if the source or target terminal is to be
+ * disconnected.
+ */
+mxGraph.prototype.isCellDisconnectable = function(cell, terminal, source)
+{
+ return this.isCellsDisconnectable() && !this.isCellLocked(cell);
+};
+
+/**
+ * Function: isCellsDisconnectable
+ *
+ * Returns <cellsDisconnectable>.
+ */
+mxGraph.prototype.isCellsDisconnectable = function()
+{
+ return this.cellsDisconnectable;
+};
+
+/**
+ * Function: setCellsDisconnectable
+ *
+ * Sets <cellsDisconnectable>.
+ */
+mxGraph.prototype.setCellsDisconnectable = function(value)
+{
+ this.cellsDisconnectable = value;
+};
+
+/**
+ * Function: isValidSource
+ *
+ * Returns true if the given cell is a valid source for new connections.
+ * This implementation returns true for all non-null values and is
+ * called by is called by <isValidConnection>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents a possible source or null.
+ */
+mxGraph.prototype.isValidSource = function(cell)
+{
+ return (cell == null && this.allowDanglingEdges) ||
+ (cell != null && (!this.model.isEdge(cell) ||
+ this.connectableEdges) && this.isCellConnectable(cell));
+};
+
+/**
+ * Function: isValidTarget
+ *
+ * Returns <isValidSource> for the given cell. This is called by
+ * <isValidConnection>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents a possible target or null.
+ */
+mxGraph.prototype.isValidTarget = function(cell)
+{
+ return this.isValidSource(cell);
+};
+
+/**
+ * Function: isValidConnection
+ *
+ * Returns true if the given target cell is a valid target for source.
+ * This is a boolean implementation for not allowing connections between
+ * certain pairs of vertices and is called by <getEdgeValidationError>.
+ * This implementation returns true if <isValidSource> returns true for
+ * the source and <isValidTarget> returns true for the target.
+ *
+ * Parameters:
+ *
+ * source - <mxCell> that represents the source cell.
+ * target - <mxCell> that represents the target cell.
+ */
+mxGraph.prototype.isValidConnection = function(source, target)
+{
+ return this.isValidSource(source) && this.isValidTarget(target);
+};
+
+/**
+ * Function: setConnectable
+ *
+ * Specifies if the graph should allow new connections. This implementation
+ * updates <mxConnectionHandler.enabled> in <connectionHandler>.
+ *
+ * Parameters:
+ *
+ * connectable - Boolean indicating if new connections should be allowed.
+ */
+mxGraph.prototype.setConnectable = function(connectable)
+{
+ this.connectionHandler.setEnabled(connectable);
+};
+
+/**
+ * Function: isConnectable
+ *
+ * Returns true if the <connectionHandler> is enabled.
+ */
+mxGraph.prototype.isConnectable = function(connectable)
+{
+ return this.connectionHandler.isEnabled();
+};
+
+/**
+ * Function: setTooltips
+ *
+ * Specifies if tooltips should be enabled. This implementation updates
+ * <mxTooltipHandler.enabled> in <tooltipHandler>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean indicating if tooltips should be enabled.
+ */
+mxGraph.prototype.setTooltips = function (enabled)
+{
+ this.tooltipHandler.setEnabled(enabled);
+};
+
+/**
+ * Function: setPanning
+ *
+ * Specifies if panning should be enabled. This implementation updates
+ * <mxPanningHandler.panningEnabled> in <panningHandler>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean indicating if panning should be enabled.
+ */
+mxGraph.prototype.setPanning = function(enabled)
+{
+ this.panningHandler.panningEnabled = enabled;
+};
+
+/**
+ * Function: isEditing
+ *
+ * Returns true if the given cell is currently being edited.
+ * If no cell is specified then this returns true if any
+ * cell is currently being edited.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that should be checked.
+ */
+mxGraph.prototype.isEditing = function(cell)
+{
+ if (this.cellEditor != null)
+ {
+ var editingCell = this.cellEditor.getEditingCell();
+
+ return (cell == null) ?
+ editingCell != null :
+ cell == editingCell;
+ }
+
+ return false;
+};
+
+/**
+ * Function: isAutoSizeCell
+ *
+ * Returns true if the size of the given cell should automatically be
+ * updated after a change of the label. This implementation returns
+ * <autoSizeCells> or checks if the cell style does specify
+ * <mxConstants.STYLE_AUTOSIZE> to be 1.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that should be resized.
+ */
+mxGraph.prototype.isAutoSizeCell = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isAutoSizeCells() || style[mxConstants.STYLE_AUTOSIZE] == 1;
+};
+
+/**
+ * Function: isAutoSizeCells
+ *
+ * Returns <autoSizeCells>.
+ */
+mxGraph.prototype.isAutoSizeCells = function()
+{
+ return this.autoSizeCells;
+};
+
+/**
+ * Function: setAutoSizeCells
+ *
+ * Specifies if cell sizes should be automatically updated after a label
+ * change. This implementation sets <autoSizeCells> to the given parameter.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if cells should be resized
+ * automatically.
+ */
+mxGraph.prototype.setAutoSizeCells = function(value)
+{
+ this.autoSizeCells = value;
+};
+
+/**
+ * Function: isExtendParent
+ *
+ * Returns true if the parent of the given cell should be extended if the
+ * child has been resized so that it overlaps the parent. This
+ * implementation returns <isExtendParents> if the cell is not an edge.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.isExtendParent = function(cell)
+{
+ return !this.getModel().isEdge(cell) && this.isExtendParents();
+};
+
+/**
+ * Function: isExtendParents
+ *
+ * Returns <extendParents>.
+ */
+mxGraph.prototype.isExtendParents = function()
+{
+ return this.extendParents;
+};
+
+/**
+ * Function: setExtendParents
+ *
+ * Sets <extendParents>.
+ *
+ * Parameters:
+ *
+ * value - New boolean value for <extendParents>.
+ */
+mxGraph.prototype.setExtendParents = function(value)
+{
+ this.extendParents = value;
+};
+
+/**
+ * Function: isExtendParentsOnAdd
+ *
+ * Returns <extendParentsOnAdd>.
+ */
+mxGraph.prototype.isExtendParentsOnAdd = function()
+{
+ return this.extendParentsOnAdd;
+};
+
+/**
+ * Function: setExtendParentsOnAdd
+ *
+ * Sets <extendParentsOnAdd>.
+ *
+ * Parameters:
+ *
+ * value - New boolean value for <extendParentsOnAdd>.
+ */
+mxGraph.prototype.setExtendParentsOnAdd = function(value)
+{
+ this.extendParentsOnAdd = value;
+};
+
+/**
+ * Function: isConstrainChild
+ *
+ * Returns true if the given cell should be kept inside the bounds of its
+ * parent according to the rules defined by <getOverlap> and
+ * <isAllowOverlapParent>. This implementation returns false for all children
+ * of edges and <isConstrainChildren> otherwise.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that should be constrained.
+ */
+mxGraph.prototype.isConstrainChild = function(cell)
+{
+ return this.isConstrainChildren() && !this.getModel().isEdge(this.getModel().getParent(cell));
+
+};
+
+/**
+ * Function: isConstrainChildren
+ *
+ * Returns <constrainChildren>.
+ */
+mxGraph.prototype.isConstrainChildren = function()
+{
+ return this.constrainChildren;
+};
+
+/**
+ * Function: setConstrainChildren
+ *
+ * Sets <constrainChildren>.
+ */
+mxGraph.prototype.setConstrainChildren = function(value)
+{
+ this.constrainChildren = value;
+};
+
+/**
+ * Function: isConstrainChildren
+ *
+ * Returns <allowNegativeCoordinates>.
+ */
+mxGraph.prototype.isAllowNegativeCoordinates = function()
+{
+ return this.allowNegativeCoordinates;
+};
+
+/**
+ * Function: setConstrainChildren
+ *
+ * Sets <allowNegativeCoordinates>.
+ */
+mxGraph.prototype.setAllowNegativeCoordinates = function(value)
+{
+ this.allowNegativeCoordinates = value;
+};
+
+/**
+ * Function: getOverlap
+ *
+ * Returns a decimal number representing the amount of the width and height
+ * of the given cell that is allowed to overlap its parent. A value of 0
+ * means all children must stay inside the parent, 1 means the child is
+ * allowed to be placed outside of the parent such that it touches one of
+ * the parents sides. If <isAllowOverlapParent> returns false for the given
+ * cell, then this method returns 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the overlap ratio should be returned.
+ */
+mxGraph.prototype.getOverlap = function(cell)
+{
+ return (this.isAllowOverlapParent(cell)) ? this.defaultOverlap : 0;
+};
+
+/**
+ * Function: isAllowOverlapParent
+ *
+ * Returns true if the given cell is allowed to be placed outside of the
+ * parents area.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the child to be checked.
+ */
+mxGraph.prototype.isAllowOverlapParent = function(cell)
+{
+ return false;
+};
+
+/**
+ * Function: getFoldableCells
+ *
+ * Returns the cells which are movable in the given array of cells.
+ */
+mxGraph.prototype.getFoldableCells = function(cells, collapse)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.isCellFoldable(cell, collapse);
+ }));
+};
+
+/**
+ * Function: isCellFoldable
+ *
+ * Returns true if the given cell is foldable. This implementation
+ * returns true if the cell has at least one child and its style
+ * does not specify <mxConstants.STYLE_FOLDABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose foldable state should be returned.
+ */
+mxGraph.prototype.isCellFoldable = function(cell, collapse)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.model.getChildCount(cell) > 0 && style[mxConstants.STYLE_FOLDABLE] != 0;
+};
+
+/**
+ * Function: isValidDropTarget
+ *
+ * Returns true if the given cell is a valid drop target for the specified
+ * cells. If the given cell is an edge, then <isSplitDropTarget> is used,
+ * else <isParentDropTarget> is used to compute the return value.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the possible drop target.
+ * cells - <mxCells> that should be dropped into the target.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.isValidDropTarget = function(cell, cells, evt)
+{
+ return cell != null && ((this.isSplitEnabled() &&
+ this.isSplitTarget(cell, cells, evt)) || (!this.model.isEdge(cell) &&
+ (this.isSwimlane(cell) || (this.model.getChildCount(cell) > 0 &&
+ !this.isCellCollapsed(cell)))));
+};
+
+/**
+ * Function: isSplitTarget
+ *
+ * Returns true if the given edge may be splitted into two edges with the
+ * given cell as a new terminal between the two.
+ *
+ * Parameters:
+ *
+ * target - <mxCell> that represents the edge to be splitted.
+ * cells - <mxCells> that should split the edge.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.isSplitTarget = function(target, cells, evt)
+{
+ if (this.model.isEdge(target) && cells != null && cells.length == 1 &&
+ this.isCellConnectable(cells[0]) && this.getEdgeValidationError(target,
+ this.model.getTerminal(target, true), cells[0]) == null)
+ {
+ var src = this.model.getTerminal(target, true);
+ var trg = this.model.getTerminal(target, false);
+
+ return (!this.model.isAncestor(cells[0], src) &&
+ !this.model.isAncestor(cells[0], trg));
+ }
+
+ return false;
+};
+
+/**
+ * Function: getDropTarget
+ *
+ * Returns the given cell if it is a drop target for the given cells or the
+ * nearest ancestor that may be used as a drop target for the given cells.
+ * If the given array contains a swimlane and <swimlaneNesting> is false
+ * then this always returns null. If no cell is given, then the bottommost
+ * swimlane at the location of the given event is returned.
+ *
+ * This function should only be used if <isDropEnabled> returns true.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> which are to be dropped onto the target.
+ * evt - Mouseevent for the drag and drop.
+ * cell - <mxCell> that is under the mousepointer.
+ */
+mxGraph.prototype.getDropTarget = function(cells, evt, cell)
+{
+ if (!this.isSwimlaneNesting())
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.isSwimlane(cells[i]))
+ {
+ return null;
+ }
+ }
+ }
+
+ var pt = mxUtils.convertPoint(this.container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+ pt.x -= this.panDx;
+ pt.y -= this.panDy;
+ var swimlane = this.getSwimlaneAt(pt.x, pt.y);
+
+ if (cell == null)
+ {
+ cell = swimlane;
+ }
+ else if (swimlane != null)
+ {
+ // Checks if the cell is an ancestor of the swimlane
+ // under the mouse and uses the swimlane in that case
+ var tmp = this.model.getParent(swimlane);
+
+ while (tmp != null && this.isSwimlane(tmp) && tmp != cell)
+ {
+ tmp = this.model.getParent(tmp);
+ }
+
+ if (tmp == cell)
+ {
+ cell = swimlane;
+ }
+ }
+
+ while (cell != null && !this.isValidDropTarget(cell, cells, evt) &&
+ !this.model.isLayer(cell))
+ {
+ cell = this.model.getParent(cell);
+ }
+
+ return (!this.model.isLayer(cell) && mxUtils.indexOf(cells, cell) < 0) ? cell : null;
+};
+
+/**
+ * Group: Cell retrieval
+ */
+
+/**
+ * Function: getDefaultParent
+ *
+ * Returns <defaultParent> or <mxGraphView.currentRoot> or the first child
+ * child of <mxGraphModel.root> if both are null. The value returned by
+ * this function should be used as the parent for new cells (aka default
+ * layer).
+ */
+mxGraph.prototype.getDefaultParent = function()
+{
+ var parent = this.defaultParent;
+
+ if (parent == null)
+ {
+ parent = this.getCurrentRoot();
+
+ if (parent == null)
+ {
+ var root = this.model.getRoot();
+ parent = this.model.getChildAt(root, 0);
+ }
+ }
+
+ return parent;
+};
+
+/**
+ * Function: setDefaultParent
+ *
+ * Sets the <defaultParent> to the given cell. Set this to null to return
+ * the first child of the root in getDefaultParent.
+ */
+mxGraph.prototype.setDefaultParent = function(cell)
+{
+ this.defaultParent = cell;
+};
+
+/**
+ * Function: getSwimlane
+ *
+ * Returns the nearest ancestor of the given cell which is a swimlane, or
+ * the given cell, if it is itself a swimlane.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the ancestor swimlane should be returned.
+ */
+mxGraph.prototype.getSwimlane = function(cell)
+{
+ while (cell != null && !this.isSwimlane(cell))
+ {
+ cell = this.model.getParent(cell);
+ }
+
+ return cell;
+};
+
+/**
+ * Function: getSwimlaneAt
+ *
+ * Returns the bottom-most swimlane that intersects the given point (x, y)
+ * in the cell hierarchy that starts at the given parent.
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.getSwimlaneAt = function (x, y, parent)
+{
+ parent = parent || this.getDefaultParent();
+
+ if (parent != null)
+ {
+ var childCount = this.model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.model.getChildAt(parent, i);
+ var result = this.getSwimlaneAt(x, y, child);
+
+ if (result != null)
+ {
+ return result;
+ }
+ else if (this.isSwimlane(child))
+ {
+ var state = this.view.getState(child);
+
+ if (this.intersects(state, x, y))
+ {
+ return child;
+ }
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: getCellAt
+ *
+ * Returns the bottom-most cell that intersects the given point (x, y) in
+ * the cell hierarchy starting at the given parent. This will also return
+ * swimlanes if the given location intersects the content area of the
+ * swimlane. If this is not desired, then the <hitsSwimlaneContent> may be
+ * used if the returned cell is a swimlane to determine if the location
+ * is inside the content area or on the actual title of the swimlane.
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is <defaultParent>.
+ * vertices - Optional boolean indicating if vertices should be returned.
+ * Default is true.
+ * edges - Optional boolean indicating if edges should be returned. Default
+ * is true.
+ */
+mxGraph.prototype.getCellAt = function(x, y, parent, vertices, edges)
+{
+ vertices = (vertices != null) ? vertices : true;
+ edges = (edges != null) ? edges : true;
+ parent = (parent != null) ? parent : this.getDefaultParent();
+
+ if (parent != null)
+ {
+ var childCount = this.model.getChildCount(parent);
+
+ for (var i = childCount - 1; i >= 0; i--)
+ {
+ var cell = this.model.getChildAt(parent, i);
+ var result = this.getCellAt(x, y, cell, vertices, edges);
+
+ if (result != null)
+ {
+ return result;
+ }
+ else if (this.isCellVisible(cell) && (edges && this.model.isEdge(cell) ||
+ vertices && this.model.isVertex(cell)))
+ {
+ var state = this.view.getState(cell);
+
+ if (this.intersects(state, x, y))
+ {
+ return cell;
+ }
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: intersects
+ *
+ * Returns the bottom-most cell that intersects the given point (x, y) in
+ * the cell hierarchy that starts at the given parent.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the cell state.
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ */
+mxGraph.prototype.intersects = function(state, x, y)
+{
+ if (state != null)
+ {
+ var pts = state.absolutePoints;
+
+ if (pts != null)
+ {
+ var t2 = this.tolerance * this.tolerance;
+
+ var pt = pts[0];
+
+ for (var i = 1; i<pts.length; i++)
+ {
+ var next = pts[i];
+ var dist = mxUtils.ptSegDistSq(
+ pt.x, pt.y, next.x, next.y, x, y);
+
+ if (dist <= t2)
+ {
+ return true;
+ }
+
+ pt = next;
+ }
+ }
+ else if (mxUtils.contains(state, x, y))
+ {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Function: hitsSwimlaneContent
+ *
+ * Returns true if the given coordinate pair is inside the content
+ * are of the given swimlane.
+ *
+ * Parameters:
+ *
+ * swimlane - <mxCell> that specifies the swimlane.
+ * x - X-coordinate of the mouse event.
+ * y - Y-coordinate of the mouse event.
+ */
+mxGraph.prototype.hitsSwimlaneContent = function(swimlane, x, y)
+{
+ var state = this.getView().getState(swimlane);
+ var size = this.getStartSize(swimlane);
+
+ if (state != null)
+ {
+ var scale = this.getView().getScale();
+ x -= state.x;
+ y -= state.y;
+
+ if (size.width > 0 && x > 0 && x > size.width * scale)
+ {
+ return true;
+ }
+ else if (size.height > 0 && y > 0 && y > size.height * scale)
+ {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Function: getChildVertices
+ *
+ * Returns the visible child vertices of the given parent.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be returned.
+ */
+mxGraph.prototype.getChildVertices = function(parent)
+{
+ return this.getChildCells(parent, true, false);
+};
+
+/**
+ * Function: getChildEdges
+ *
+ * Returns the visible child edges of the given parent.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose child vertices should be returned.
+ */
+mxGraph.prototype.getChildEdges = function(parent)
+{
+ return this.getChildCells(parent, false, true);
+};
+
+/**
+ * Function: getChildCells
+ *
+ * Returns the visible child vertices or edges in the given parent. If
+ * vertices and edges is false, then all children are returned.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be returned.
+ * vertices - Optional boolean that specifies if child vertices should
+ * be returned. Default is false.
+ * edges - Optional boolean that specifies if child edges should
+ * be returned. Default is false.
+ */
+mxGraph.prototype.getChildCells = function(parent, vertices, edges)
+{
+ parent = (parent != null) ? parent : this.getDefaultParent();
+ vertices = (vertices != null) ? vertices : false;
+ edges = (edges != null) ? edges : false;
+
+ var cells = this.model.getChildCells(parent, vertices, edges);
+ var result = [];
+
+ // Filters out the non-visible child cells
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.isCellVisible(cells[i]))
+ {
+ result.push(cells[i]);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getConnections
+ *
+ * Returns all visible edges connected to the given cell without loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose connections should be returned.
+ * parent - Optional parent of the opposite end for a connection to be
+ * returned.
+ */
+mxGraph.prototype.getConnections = function(cell, parent)
+{
+ return this.getEdges(cell, parent, true, true, false);
+};
+
+/**
+ * Function: getIncomingEdges
+ *
+ * Returns the visible incoming edges for the given cell. If the optional
+ * parent argument is specified, then only child edges of the given parent
+ * are returned.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose incoming edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ */
+mxGraph.prototype.getIncomingEdges = function(cell, parent)
+{
+ return this.getEdges(cell, parent, true, false, false);
+};
+
+/**
+ * Function: getOutgoingEdges
+ *
+ * Returns the visible outgoing edges for the given cell. If the optional
+ * parent argument is specified, then only child edges of the given parent
+ * are returned.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose outgoing edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ */
+mxGraph.prototype.getOutgoingEdges = function(cell, parent)
+{
+ return this.getEdges(cell, parent, false, true, false);
+};
+
+/**
+ * Function: getEdges
+ *
+ * Returns the incoming and/or outgoing edges for the given cell.
+ * If the optional parent argument is specified, then only edges are returned
+ * where the opposite is in the given parent cell. If at least one of incoming
+ * or outgoing is true, then loops are ignored, if both are false, then all
+ * edges connected to the given cell are returned including loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ * incoming - Optional boolean that specifies if incoming edges should
+ * be included in the result. Default is true.
+ * outgoing - Optional boolean that specifies if outgoing edges should
+ * be included in the result. Default is true.
+ * includeLoops - Optional boolean that specifies if loops should be
+ * included in the result. Default is true.
+ * recurse - Optional boolean the specifies if the parent specified only
+ * need be an ancestral parent, true, or the direct parent, false.
+ * Default is false
+ */
+mxGraph.prototype.getEdges = function(cell, parent, incoming, outgoing, includeLoops, recurse)
+{
+ incoming = (incoming != null) ? incoming : true;
+ outgoing = (outgoing != null) ? outgoing : true;
+ includeLoops = (includeLoops != null) ? includeLoops : true;
+ recurse = (recurse != null) ? recurse : false;
+
+ var edges = [];
+ var isCollapsed = this.isCellCollapsed(cell);
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.model.getChildAt(cell, i);
+
+ if (isCollapsed || !this.isCellVisible(child))
+ {
+ edges = edges.concat(this.model.getEdges(child, incoming, outgoing));
+ }
+ }
+
+ edges = edges.concat(this.model.getEdges(cell, incoming, outgoing));
+ var result = [];
+
+ for (var i = 0; i < edges.length; i++)
+ {
+ var state = this.view.getState(edges[i]);
+
+ var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+ var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+ if ((includeLoops && source == target) || ((source != target) && ((incoming &&
+ target == cell && (parent == null || this.isValidAncestor(source, parent, recurse))) ||
+ (outgoing && source == cell && (parent == null ||
+ this.isValidAncestor(target, parent, recurse))))))
+ {
+ result.push(edges[i]);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: isValidAncestor
+ *
+ * Returns whether or not the specified parent is a valid
+ * ancestor of the specified cell, either direct or indirectly
+ * based on whether ancestor recursion is enabled.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> the possible child cell
+ * parent - <mxCell> the possible parent cell
+ * recurse - boolean whether or not to recurse the child ancestors
+ */
+mxGraph.prototype.isValidAncestor = function(cell, parent, recurse)
+{
+ return (recurse ? this.model.isAncestor(parent, cell) : this.model
+ .getParent(cell) == parent);
+};
+
+/**
+ * Function: getOpposites
+ *
+ * Returns all distinct visible opposite cells for the specified terminal
+ * on the given edges.
+ *
+ * Parameters:
+ *
+ * edges - Array of <mxCells> that contains the edges whose opposite
+ * terminals should be returned.
+ * terminal - Terminal that specifies the end whose opposite should be
+ * returned.
+ * source - Optional boolean that specifies if source terminals should be
+ * included in the result. Default is true.
+ * targets - Optional boolean that specifies if targer terminals should be
+ * included in the result. Default is true.
+ */
+mxGraph.prototype.getOpposites = function(edges, terminal, sources, targets)
+{
+ sources = (sources != null) ? sources : true;
+ targets = (targets != null) ? targets : true;
+
+ var terminals = [];
+
+ // Implements set semantic on the terminals array using a string
+ // representation of each cell in an associative array lookup
+ var hash = new Object();
+
+ if (edges != null)
+ {
+ for (var i = 0; i < edges.length; i++)
+ {
+ var state = this.view.getState(edges[i]);
+
+ var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+ var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+ // Checks if the terminal is the source of the edge and if the
+ // target should be stored in the result
+ if (source == terminal && target != null &&
+ target != terminal && targets)
+ {
+ var id = mxCellPath.create(target);
+
+ if (hash[id] == null)
+ {
+ hash[id] = target;
+ terminals.push(target);
+ }
+ }
+
+ // Checks if the terminal is the taget of the edge and if the
+ // source should be stored in the result
+ else if (target == terminal && source != null &&
+ source != terminal && sources)
+ {
+ var id = mxCellPath.create(source);
+
+ if (hash[id] == null)
+ {
+ hash[id] = source;
+ terminals.push(source);
+ }
+ }
+ }
+ }
+
+ return terminals;
+};
+
+/**
+ * Function: getEdgesBetween
+ *
+ * Returns the edges between the given source and target. This takes into
+ * account collapsed and invisible cells and returns the connected edges
+ * as displayed on the screen.
+ *
+ * Parameters:
+ *
+ * source -
+ * target -
+ * directed -
+ */
+mxGraph.prototype.getEdgesBetween = function(source, target, directed)
+{
+ directed = (directed != null) ? directed : false;
+ var edges = this.getEdges(source);
+ var result = [];
+
+ // Checks if the edge is connected to the correct
+ // cell and returns the first match
+ for (var i = 0; i < edges.length; i++)
+ {
+ var state = this.view.getState(edges[i]);
+
+ var src = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+ var trg = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+ if ((src == source && trg == target) || (!directed && src == target && trg == source))
+ {
+ result.push(edges[i]);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getPointForEvent
+ *
+ * Returns an <mxPoint> representing the given event in the unscaled,
+ * non-translated coordinate space of <container> and applies the grid.
+ *
+ * Parameters:
+ *
+ * evt - Mousevent that contains the mouse pointer location.
+ * addOffset - Optional boolean that specifies if the position should be
+ * offset by half of the <gridSize>. Default is true.
+ */
+ mxGraph.prototype.getPointForEvent = function(evt, addOffset)
+ {
+ var p = mxUtils.convertPoint(this.container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+
+ var s = this.view.scale;
+ var tr = this.view.translate;
+ var off = (addOffset != false) ? this.gridSize / 2 : 0;
+
+ p.x = this.snap(p.x / s - tr.x - off);
+ p.y = this.snap(p.y / s - tr.y - off);
+
+ return p;
+ };
+
+/**
+ * Function: getCells
+ *
+ * Returns the children of the given parent that are contained in the given
+ * rectangle (x, y, width, height). The result is added to the optional
+ * result array, which is returned from the function. If no result array
+ * is specified then a new array is created and returned.
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the rectangle.
+ * y - Y-coordinate of the rectangle.
+ * width - Width of the rectangle.
+ * height - Height of the rectangle.
+ * parent - <mxCell> whose children should be checked. Default is
+ * <defaultParent>.
+ * result - Optional array to store the result in.
+ */
+mxGraph.prototype.getCells = function(x, y, width, height, parent, result)
+{
+ result = (result != null) ? result : [];
+
+ if (width > 0 || height > 0)
+ {
+ var right = x + width;
+ var bottom = y + height;
+
+ parent = parent || this.getDefaultParent();
+
+ if (parent != null)
+ {
+ var childCount = this.model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var cell = this.model.getChildAt(parent, i);
+ var state = this.view.getState(cell);
+
+ if (this.isCellVisible(cell) && state != null)
+ {
+ if (state.x >= x && state.y >= y &&
+ state.x + state.width <= right &&
+ state.y + state.height <= bottom)
+ {
+ result.push(cell);
+ }
+ else
+ {
+ this.getCells(x, y, width, height, cell, result);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getCellsBeyond
+ *
+ * Returns the children of the given parent that are contained in the
+ * halfpane from the given point (x0, y0) rightwards and/or downwards
+ * depending on rightHalfpane and bottomHalfpane.
+ *
+ * Parameters:
+ *
+ * x0 - X-coordinate of the origin.
+ * y0 - Y-coordinate of the origin.
+ * parent - Optional <mxCell> whose children should be checked. Default is
+ * <defaultParent>.
+ * rightHalfpane - Boolean indicating if the cells in the right halfpane
+ * from the origin should be returned.
+ * bottomHalfpane - Boolean indicating if the cells in the bottom halfpane
+ * from the origin should be returned.
+ */
+mxGraph.prototype.getCellsBeyond = function(x0, y0, parent, rightHalfpane, bottomHalfpane)
+{
+ var result = [];
+
+ if (rightHalfpane || bottomHalfpane)
+ {
+ if (parent == null)
+ {
+ parent = this.getDefaultParent();
+ }
+
+ if (parent != null)
+ {
+ var childCount = this.model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.model.getChildAt(parent, i);
+ var state = this.view.getState(child);
+
+ if (this.isCellVisible(child) && state != null)
+ {
+ if ((!rightHalfpane ||
+ state.x >= x0) &&
+ (!bottomHalfpane ||
+ state.y >= y0))
+ {
+ result.push(child);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: findTreeRoots
+ *
+ * Returns all children in the given parent which do not have incoming
+ * edges. If the result is empty then the with the greatest difference
+ * between incoming and outgoing edges is returned.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be checked.
+ * isolate - Optional boolean that specifies if edges should be ignored if
+ * the opposite end is not a child of the given parent cell. Default is
+ * false.
+ * invert - Optional boolean that specifies if outgoing or incoming edges
+ * should be counted for a tree root. If false then outgoing edges will be
+ * counted. Default is false.
+ */
+mxGraph.prototype.findTreeRoots = function(parent, isolate, invert)
+{
+ isolate = (isolate != null) ? isolate : false;
+ invert = (invert != null) ? invert : false;
+ var roots = [];
+
+ if (parent != null)
+ {
+ var model = this.getModel();
+ var childCount = model.getChildCount(parent);
+ var best = null;
+ var maxDiff = 0;
+
+ for (var i=0; i<childCount; i++)
+ {
+ var cell = model.getChildAt(parent, i);
+
+ if (this.model.isVertex(cell) && this.isCellVisible(cell))
+ {
+ var conns = this.getConnections(cell, (isolate) ? parent : null);
+ var fanOut = 0;
+ var fanIn = 0;
+
+ for (var j = 0; j < conns.length; j++)
+ {
+ var src = this.view.getVisibleTerminal(conns[j], true);
+
+ if (src == cell)
+ {
+ fanOut++;
+ }
+ else
+ {
+ fanIn++;
+ }
+ }
+
+ if ((invert && fanOut == 0 && fanIn > 0) ||
+ (!invert && fanIn == 0 && fanOut > 0))
+ {
+ roots.push(cell);
+ }
+
+ var diff = (invert) ? fanIn - fanOut : fanOut - fanIn;
+
+ if (diff > maxDiff)
+ {
+ maxDiff = diff;
+ best = cell;
+ }
+ }
+ }
+
+ if (roots.length == 0 && best != null)
+ {
+ roots.push(best);
+ }
+ }
+
+ return roots;
+};
+
+/**
+ * Function: traverse
+ *
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ *
+ * Example:
+ *
+ * (code)
+ * mxLog.show();
+ * var cell = graph.getSelectionCell();
+ * graph.traverse(cell, false, function(vertex, edge)
+ * {
+ * mxLog.debug(graph.getLabel(vertex));
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - Optional boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * func - Visitor function that takes the current vertex and the incoming
+ * edge as arguments. The traversal stops if the function returns false.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * visited - Optional array of cell paths for the visited cells.
+ */
+mxGraph.prototype.traverse = function(vertex, directed, func, edge, visited)
+{
+ if (func != null && vertex != null)
+ {
+ directed = (directed != null) ? directed : true;
+ visited = visited || [];
+ var id = mxCellPath.create(vertex);
+
+ if (visited[id] == null)
+ {
+ visited[id] = vertex;
+ var result = func(vertex, edge);
+
+ if (result == null || result)
+ {
+ var edgeCount = this.model.getEdgeCount(vertex);
+
+ if (edgeCount > 0)
+ {
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var e = this.model.getEdgeAt(vertex, i);
+ var isSource = this.model.getTerminal(e, true) == vertex;
+
+ if (!directed || isSource)
+ {
+ var next = this.model.getTerminal(e, !isSource);
+ this.traverse(next, directed, func, e, visited);
+ }
+ }
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Group: Selection
+ */
+
+/**
+ * Function: isCellSelected
+ *
+ * Returns true if the given cell is selected.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the selection state should be returned.
+ */
+mxGraph.prototype.isCellSelected = function(cell)
+{
+ return this.getSelectionModel().isSelected(cell);
+};
+
+/**
+ * Function: isSelectionEmpty
+ *
+ * Returns true if the selection is empty.
+ */
+mxGraph.prototype.isSelectionEmpty = function()
+{
+ return this.getSelectionModel().isEmpty();
+};
+
+/**
+ * Function: clearSelection
+ *
+ * Clears the selection using <mxGraphSelectionModel.clear>.
+ */
+mxGraph.prototype.clearSelection = function()
+{
+ return this.getSelectionModel().clear();
+};
+
+/**
+ * Function: getSelectionCount
+ *
+ * Returns the number of selected cells.
+ */
+mxGraph.prototype.getSelectionCount = function()
+{
+ return this.getSelectionModel().cells.length;
+};
+
+/**
+ * Function: getSelectionCell
+ *
+ * Returns the first cell from the array of selected <mxCells>.
+ */
+mxGraph.prototype.getSelectionCell = function()
+{
+ return this.getSelectionModel().cells[0];
+};
+
+/**
+ * Function: getSelectionCells
+ *
+ * Returns the array of selected <mxCells>.
+ */
+mxGraph.prototype.getSelectionCells = function()
+{
+ return this.getSelectionModel().cells.slice();
+};
+
+/**
+ * Function: setSelectionCell
+ *
+ * Sets the selection cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be selected.
+ */
+mxGraph.prototype.setSelectionCell = function(cell)
+{
+ this.getSelectionModel().setCell(cell);
+};
+
+/**
+ * Function: setSelectionCells
+ *
+ * Sets the selection cell.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be selected.
+ */
+mxGraph.prototype.setSelectionCells = function(cells)
+{
+ this.getSelectionModel().setCells(cells);
+};
+
+/**
+ * Function: addSelectionCell
+ *
+ * Adds the given cell to the selection.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be add to the selection.
+ */
+mxGraph.prototype.addSelectionCell = function(cell)
+{
+ this.getSelectionModel().addCell(cell);
+};
+
+/**
+ * Function: addSelectionCells
+ *
+ * Adds the given cells to the selection.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be added to the selection.
+ */
+mxGraph.prototype.addSelectionCells = function(cells)
+{
+ this.getSelectionModel().addCells(cells);
+};
+
+/**
+ * Function: removeSelectionCell
+ *
+ * Removes the given cell from the selection.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be removed from the selection.
+ */
+mxGraph.prototype.removeSelectionCell = function(cell)
+{
+ this.getSelectionModel().removeCell(cell);
+};
+
+/**
+ * Function: removeSelectionCells
+ *
+ * Removes the given cells from the selection.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be removed from the selection.
+ */
+mxGraph.prototype.removeSelectionCells = function(cells)
+{
+ this.getSelectionModel().removeCells(cells);
+};
+
+/**
+ * Function: selectRegion
+ *
+ * Selects and returns the cells inside the given rectangle for the
+ * specified event.
+ *
+ * Parameters:
+ *
+ * rect - <mxRectangle> that represents the region to be selected.
+ * evt - Mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectRegion = function(rect, evt)
+{
+ var cells = this.getCells(rect.x, rect.y, rect.width, rect.height);
+ this.selectCellsForEvent(cells, evt);
+
+ return cells;
+};
+
+/**
+ * Function: selectNextCell
+ *
+ * Selects the next cell.
+ */
+mxGraph.prototype.selectNextCell = function()
+{
+ this.selectCell(true);
+};
+
+/**
+ * Function: selectPreviousCell
+ *
+ * Selects the previous cell.
+ */
+mxGraph.prototype.selectPreviousCell = function()
+{
+ this.selectCell();
+};
+
+/**
+ * Function: selectParentCell
+ *
+ * Selects the parent cell.
+ */
+mxGraph.prototype.selectParentCell = function()
+{
+ this.selectCell(false, true);
+};
+
+/**
+ * Function: selectChildCell
+ *
+ * Selects the first child cell.
+ */
+mxGraph.prototype.selectChildCell = function()
+{
+ this.selectCell(false, false, true);
+};
+
+/**
+ * Function: selectCell
+ *
+ * Selects the next, parent, first child or previous cell, if all arguments
+ * are false.
+ *
+ * Parameters:
+ *
+ * isNext - Boolean indicating if the next cell should be selected.
+ * isParent - Boolean indicating if the parent cell should be selected.
+ * isChild - Boolean indicating if the first child cell should be selected.
+ */
+mxGraph.prototype.selectCell = function(isNext, isParent, isChild)
+{
+ var sel = this.selectionModel;
+ var cell = (sel.cells.length > 0) ? sel.cells[0] : null;
+
+ if (sel.cells.length > 1)
+ {
+ sel.clear();
+ }
+
+ var parent = (cell != null) ?
+ this.model.getParent(cell) :
+ this.getDefaultParent();
+
+ var childCount = this.model.getChildCount(parent);
+
+ if (cell == null && childCount > 0)
+ {
+ var child = this.model.getChildAt(parent, 0);
+ this.setSelectionCell(child);
+ }
+ else if ((cell == null || isParent) &&
+ this.view.getState(parent) != null &&
+ this.model.getGeometry(parent) != null)
+ {
+ if (this.getCurrentRoot() != parent)
+ {
+ this.setSelectionCell(parent);
+ }
+ }
+ else if (cell != null && isChild)
+ {
+ var tmp = this.model.getChildCount(cell);
+
+ if (tmp > 0)
+ {
+ var child = this.model.getChildAt(cell, 0);
+ this.setSelectionCell(child);
+ }
+ }
+ else if (childCount > 0)
+ {
+ var i = parent.getIndex(cell);
+
+ if (isNext)
+ {
+ i++;
+ var child = this.model.getChildAt(parent, i % childCount);
+ this.setSelectionCell(child);
+ }
+ else
+ {
+ i--;
+ var index = (i < 0) ? childCount - 1 : i;
+ var child = this.model.getChildAt(parent, index);
+ this.setSelectionCell(child);
+ }
+ }
+};
+
+/**
+ * Function: selectAll
+ *
+ * Selects all children of the given parent cell or the children of the
+ * default parent if no parent is specified. To select leaf vertices and/or
+ * edges use <selectCells>.
+ *
+ * Parameters:
+ *
+ * parent - Optional <mxCell> whose children should be selected.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.selectAll = function(parent)
+{
+ parent = parent || this.getDefaultParent();
+
+ var children = this.model.getChildren(parent);
+
+ if (children != null)
+ {
+ this.setSelectionCells(children);
+ }
+};
+
+/**
+ * Function: selectVertices
+ *
+ * Select all vertices inside the given parent or the default parent.
+ */
+mxGraph.prototype.selectVertices = function(parent)
+{
+ this.selectCells(true, false, parent);
+};
+
+/**
+ * Function: selectVertices
+ *
+ * Select all vertices inside the given parent or the default parent.
+ */
+mxGraph.prototype.selectEdges = function(parent)
+{
+ this.selectCells(false, true, parent);
+};
+
+/**
+ * Function: selectCells
+ *
+ * Selects all vertices and/or edges depending on the given boolean
+ * arguments recursively, starting at the given parent or the default
+ * parent if no parent is specified. Use <selectAll> to select all cells.
+ *
+ * Parameters:
+ *
+ * vertices - Boolean indicating if vertices should be selected.
+ * edges - Boolean indicating if edges should be selected.
+ * parent - Optional <mxCell> that acts as the root of the recursion.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.selectCells = function(vertices, edges, parent)
+{
+ parent = parent || this.getDefaultParent();
+
+ var filter = mxUtils.bind(this, function(cell)
+ {
+ return this.view.getState(cell) != null &&
+ this.model.getChildCount(cell) == 0 &&
+ ((this.model.isVertex(cell) && vertices) ||
+ (this.model.isEdge(cell) && edges));
+ });
+
+ var cells = this.model.filterDescendants(filter, parent);
+ this.setSelectionCells(cells);
+};
+
+/**
+ * Function: selectCellForEvent
+ *
+ * Selects the given cell by either adding it to the selection or
+ * replacing the selection depending on whether the given mouse event is a
+ * toggle event.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be selected.
+ * evt - Optional mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectCellForEvent = function(cell, evt)
+{
+ var isSelected = this.isCellSelected(cell);
+
+ if (this.isToggleEvent(evt))
+ {
+ if (isSelected)
+ {
+ this.removeSelectionCell(cell);
+ }
+ else
+ {
+ this.addSelectionCell(cell);
+ }
+ }
+ else if (!isSelected || this.getSelectionCount() != 1)
+ {
+ this.setSelectionCell(cell);
+ }
+};
+
+/**
+ * Function: selectCellsForEvent
+ *
+ * Selects the given cells by either adding them to the selection or
+ * replacing the selection depending on whether the given mouse event is a
+ * toggle event.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be selected.
+ * evt - Optional mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectCellsForEvent = function(cells, evt)
+{
+ if (this.isToggleEvent(evt))
+ {
+ this.addSelectionCells(cells);
+ }
+ else
+ {
+ this.setSelectionCells(cells);
+ }
+};
+
+/**
+ * Group: Selection state
+ */
+
+/**
+ * Function: createHandler
+ *
+ * Creates a new handler for the given cell state. This implementation
+ * returns a new <mxEdgeHandler> of the corresponding cell is an edge,
+ * otherwise it returns an <mxVertexHandler>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose handler should be created.
+ */
+mxGraph.prototype.createHandler = function(state)
+{
+ var result = null;
+
+ if (state != null)
+ {
+ if (this.model.isEdge(state.cell))
+ {
+ var style = this.view.getEdgeStyle(state);
+
+ if (this.isLoop(state) ||
+ style == mxEdgeStyle.ElbowConnector ||
+ style == mxEdgeStyle.SideToSide ||
+ style == mxEdgeStyle.TopToBottom)
+ {
+ result = new mxElbowEdgeHandler(state);
+ }
+ else if (style == mxEdgeStyle.SegmentConnector ||
+ style == mxEdgeStyle.OrthConnector)
+ {
+ result = new mxEdgeSegmentHandler(state);
+ }
+ else
+ {
+ result = new mxEdgeHandler(state);
+ }
+ }
+ else
+ {
+ result = new mxVertexHandler(state);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Group: Graph events
+ */
+
+/**
+ * Function: addMouseListener
+ *
+ * Adds a listener to the graph event dispatch loop. The listener
+ * must implement the mouseDown, mouseMove and mouseUp methods
+ * as shown in the <mxMouseEvent> class.
+ *
+ * Parameters:
+ *
+ * listener - Listener to be added to the graph event listeners.
+ */
+mxGraph.prototype.addMouseListener = function(listener)
+{
+ if (this.mouseListeners == null)
+ {
+ this.mouseListeners = [];
+ }
+
+ this.mouseListeners.push(listener);
+};
+
+/**
+ * Function: removeMouseListener
+ *
+ * Removes the specified graph listener.
+ *
+ * Parameters:
+ *
+ * listener - Listener to be removed from the graph event listeners.
+ */
+mxGraph.prototype.removeMouseListener = function(listener)
+{
+ if (this.mouseListeners != null)
+ {
+ for (var i = 0; i < this.mouseListeners.length; i++)
+ {
+ if (this.mouseListeners[i] == listener)
+ {
+ this.mouseListeners.splice(i, 1);
+ break;
+ }
+ }
+ }
+};
+
+/**
+ * Function: updateMouseEvent
+ *
+ * Sets the graphX and graphY properties if the given <mxMouseEvent> if
+ * required.
+ */
+mxGraph.prototype.updateMouseEvent = function(me)
+{
+ if (me.graphX == null || me.graphY == null)
+ {
+ var pt = mxUtils.convertPoint(this.container, me.getX(), me.getY());
+
+ me.graphX = pt.x - this.panDx;
+ me.graphY = pt.y - this.panDy;
+ }
+};
+
+/**
+ * Function: fireMouseEvent
+ *
+ * Dispatches the given event in the graph event dispatch loop. Possible
+ * event names are <mxEvent.MOUSE_DOWN>, <mxEvent.MOUSE_MOVE> and
+ * <mxEvent.MOUSE_UP>. All listeners are invoked for all events regardless
+ * of the consumed state of the event.
+ *
+ * Parameters:
+ *
+ * evtName - String that specifies the type of event to be dispatched.
+ * me - <mxMouseEvent> to be fired.
+ * sender - Optional sender argument. Default is this.
+ */
+mxGraph.prototype.fireMouseEvent = function(evtName, me, sender)
+{
+ if (sender == null)
+ {
+ sender = this;
+ }
+
+ // Updates the graph coordinates in the event
+ this.updateMouseEvent(me);
+
+ // Makes sure we have a uniform event-sequence across all
+ // browsers for a double click. Since evt.detail == 2 is only
+ // available on Firefox we use the fact that each mousedown
+ // must be followed by a mouseup, all out-of-sync downs
+ // will be dropped silently.
+ if (evtName == mxEvent.MOUSE_DOWN)
+ {
+ this.isMouseDown = true;
+ }
+
+ // Detects and processes double taps for touch-based devices
+ // which do not have native double click events
+ if (mxClient.IS_TOUCH && this.doubleTapEnabled && evtName == mxEvent.MOUSE_DOWN)
+ {
+ var currentTime = new Date().getTime();
+
+ if (currentTime - this.lastTouchTime < this.doubleTapTimeout &&
+ Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&
+ Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance)
+ {
+ // FIXME: The actual editing should start on MOUSE_UP event but
+ // the detection of the double click should use the mouse_down event
+ // to make it consistent with behaviour in browser with mouse.
+ this.lastTouchTime = 0;
+ this.dblClick(me.getEvent(), me.getCell());
+
+ // Stop bubbling but do not consume to make sure the device
+ // can bring up the virtual keyboard for editing
+ me.getEvent().cancelBubble = true;
+ }
+ else
+ {
+ this.lastTouchX = me.getX();
+ this.lastTouchY = me.getY();
+ this.lastTouchTime = currentTime;
+ }
+ }
+
+ // Workaround for IE9 standards mode ignoring tolerance for double clicks
+ var noDoubleClick = me.getEvent().detail/*clickCount*/ != 2;
+
+ if (mxClient.IS_IE && document.compatMode == 'CSS1Compat')
+ {
+ if ((this.lastMouseX != null && Math.abs(this.lastMouseX - me.getX()) > this.doubleTapTolerance) ||
+ (this.lastMouseY != null && Math.abs(this.lastMouseY - me.getY()) > this.doubleTapTolerance))
+ {
+ noDoubleClick = true;
+ }
+
+ if (evtName == mxEvent.MOUSE_UP)
+ {
+ this.lastMouseX = me.getX();
+ this.lastMouseY = me.getY();
+ }
+ }
+
+ // Filters too many mouse ups when the mouse is down
+ if ((evtName != mxEvent.MOUSE_UP || this.isMouseDown) && noDoubleClick)
+ {
+ if (evtName == mxEvent.MOUSE_UP)
+ {
+ this.isMouseDown = false;
+ }
+
+ if (!this.isEditing() && (mxClient.IS_OP || mxClient.IS_SF || mxClient.IS_GC ||
+ (mxClient.IS_IE && mxClient.IS_SVG) || me.getEvent().target != this.container))
+ {
+ if (evtName == mxEvent.MOUSE_MOVE && this.isMouseDown && this.autoScroll)
+ {
+ this.scrollPointToVisible(me.getGraphX(), me.getGraphY(), this.autoExtend);
+ }
+
+ if (this.mouseListeners != null)
+ {
+ var args = [sender, me];
+
+ // Does not change returnValue in Opera
+ me.getEvent().returnValue = true;
+
+ for (var i = 0; i < this.mouseListeners.length; i++)
+ {
+ var l = this.mouseListeners[i];
+
+ if (evtName == mxEvent.MOUSE_DOWN)
+ {
+ l.mouseDown.apply(l, args);
+ }
+ else if (evtName == mxEvent.MOUSE_MOVE)
+ {
+ l.mouseMove.apply(l, args);
+ }
+ else if (evtName == mxEvent.MOUSE_UP)
+ {
+ l.mouseUp.apply(l, args);
+ }
+ }
+ }
+
+ // Invokes the click handler
+ if (evtName == mxEvent.MOUSE_UP)
+ {
+ this.click(me);
+ }
+ }
+ }
+ else if (evtName == mxEvent.MOUSE_UP)
+ {
+ this.isMouseDown = false;
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the graph and all its resources.
+ */
+mxGraph.prototype.destroy = function()
+{
+ if (!this.destroyed)
+ {
+ this.destroyed = true;
+
+ if (this.tooltipHandler != null)
+ {
+ this.tooltipHandler.destroy();
+ }
+
+ if (this.selectionCellsHandler != null)
+ {
+ this.selectionCellsHandler.destroy();
+ }
+
+ if (this.panningHandler != null)
+ {
+ this.panningHandler.destroy();
+ }
+
+ if (this.connectionHandler != null)
+ {
+ this.connectionHandler.destroy();
+ }
+
+ if (this.graphHandler != null)
+ {
+ this.graphHandler.destroy();
+ }
+
+ if (this.cellEditor != null)
+ {
+ this.cellEditor.destroy();
+ }
+
+ if (this.view != null)
+ {
+ this.view.destroy();
+ }
+
+ if (this.model != null && this.graphModelChangeListener != null)
+ {
+ this.model.removeListener(this.graphModelChangeListener);
+ this.graphModelChangeListener = null;
+ }
+
+ this.container = null;
+ }
+};
diff --git a/src/js/view/mxGraphSelectionModel.js b/src/js/view/mxGraphSelectionModel.js
new file mode 100644
index 0000000..5cd16a8
--- /dev/null
+++ b/src/js/view/mxGraphSelectionModel.js
@@ -0,0 +1,435 @@
+/**
+ * $Id: mxGraphSelectionModel.js,v 1.14 2011-11-25 10:16:08 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphSelectionModel
+ *
+ * Implements the selection model for a graph. Here is a listener that handles
+ * all removed selection cells.
+ *
+ * (code)
+ * graph.getSelectionModel().addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ * var cells = evt.getProperty('added');
+ *
+ * for (var i = 0; i < cells.length; i++)
+ * {
+ * // Handle cells[i]...
+ * }
+ * });
+ * (end)
+ *
+ * Event: mxEvent.UNDO
+ *
+ * Fires after the selection was changed in <changeSelection>. The
+ * <code>edit</code> property contains the <mxUndoableEdit> which contains the
+ * <mxSelectionChange>.
+ *
+ * Event: mxEvent.CHANGE
+ *
+ * Fires after the selection changes by executing an <mxSelectionChange>. The
+ * <code>added</code> and <code>removed</code> properties contain arrays of
+ * cells that have been added to or removed from the selection, respectively.
+ *
+ * Constructor: mxGraphSelectionModel
+ *
+ * Constructs a new graph selection model for the given <mxGraph>.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphSelectionModel(graph)
+{
+ this.graph = graph;
+ this.cells = [];
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphSelectionModel.prototype = new mxEventSource();
+mxGraphSelectionModel.prototype.constructor = mxGraphSelectionModel;
+
+/**
+ * Variable: doneResource
+ *
+ * Specifies the resource key for the status message after a long operation.
+ * If the resource for this key does not exist then the value is used as
+ * the status message. Default is 'done'.
+ */
+mxGraphSelectionModel.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
+
+/**
+ * Variable: updatingSelectionResource
+ *
+ * Specifies the resource key for the status message while the selection is
+ * being updated. If the resource for this key does not exist then the
+ * value is used as the status message. Default is 'updatingSelection'.
+ */
+mxGraphSelectionModel.prototype.updatingSelectionResource = (mxClient.language != 'none') ? 'updatingSelection' : '';
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphSelectionModel.prototype.graph = null;
+
+/**
+ * Variable: singleSelection
+ *
+ * Specifies if only one selected item at a time is allowed.
+ * Default is false.
+ */
+mxGraphSelectionModel.prototype.singleSelection = false;
+
+/**
+ * Function: isSingleSelection
+ *
+ * Returns <singleSelection> as a boolean.
+ */
+mxGraphSelectionModel.prototype.isSingleSelection = function()
+{
+ return this.singleSelection;
+};
+
+/**
+ * Function: setSingleSelection
+ *
+ * Sets the <singleSelection> flag.
+ *
+ * Parameters:
+ *
+ * singleSelection - Boolean that specifies the new value for
+ * <singleSelection>.
+ */
+mxGraphSelectionModel.prototype.setSingleSelection = function(singleSelection)
+{
+ this.singleSelection = singleSelection;
+};
+
+/**
+ * Function: isSelected
+ *
+ * Returns true if the given <mxCell> is selected.
+ */
+mxGraphSelectionModel.prototype.isSelected = function(cell)
+{
+ if (cell != null)
+ {
+ return mxUtils.indexOf(this.cells, cell) >= 0;
+ }
+
+ return false;
+};
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if no cells are currently selected.
+ */
+mxGraphSelectionModel.prototype.isEmpty = function()
+{
+ return this.cells.length == 0;
+};
+
+/**
+ * Function: clear
+ *
+ * Clears the selection and fires a <change> event if the selection was not
+ * empty.
+ */
+mxGraphSelectionModel.prototype.clear = function()
+{
+ this.changeSelection(null, this.cells);
+};
+
+/**
+ * Function: setCell
+ *
+ * Selects the specified <mxCell> using <setCells>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be selected.
+ */
+mxGraphSelectionModel.prototype.setCell = function(cell)
+{
+ if (cell != null)
+ {
+ this.setCells([cell]);
+ }
+};
+
+/**
+ * Function: setCells
+ *
+ * Selects the given array of <mxCells> and fires a <change> event.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be selected.
+ */
+mxGraphSelectionModel.prototype.setCells = function(cells)
+{
+ if (cells != null)
+ {
+ if (this.singleSelection)
+ {
+ cells = [this.getFirstSelectableCell(cells)];
+ }
+
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.graph.isCellSelectable(cells[i]))
+ {
+ tmp.push(cells[i]);
+ }
+ }
+
+ this.changeSelection(tmp, this.cells);
+ }
+};
+
+/**
+ * Function: getFirstSelectableCell
+ *
+ * Returns the first selectable cell in the given array of cells.
+ */
+mxGraphSelectionModel.prototype.getFirstSelectableCell = function(cells)
+{
+ if (cells != null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.graph.isCellSelectable(cells[i]))
+ {
+ return cells[i];
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: addCell
+ *
+ * Adds the given <mxCell> to the selection and fires a <select> event.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.addCell = function(cell)
+{
+ if (cell != null)
+ {
+ this.addCells([cell]);
+ }
+};
+
+/**
+ * Function: addCells
+ *
+ * Adds the given array of <mxCells> to the selection and fires a <select>
+ * event.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.addCells = function(cells)
+{
+ if (cells != null)
+ {
+ var remove = null;
+
+ if (this.singleSelection)
+ {
+ remove = this.cells;
+ cells = [this.getFirstSelectableCell(cells)];
+ }
+
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isSelected(cells[i]) &&
+ this.graph.isCellSelectable(cells[i]))
+ {
+ tmp.push(cells[i]);
+ }
+ }
+
+ this.changeSelection(tmp, remove);
+ }
+};
+
+/**
+ * Function: removeCell
+ *
+ * Removes the specified <mxCell> from the selection and fires a <select>
+ * event for the remaining cells.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to remove from the selection.
+ */
+mxGraphSelectionModel.prototype.removeCell = function(cell)
+{
+ if (cell != null)
+ {
+ this.removeCells([cell]);
+ }
+};
+
+/**
+ * Function: removeCells
+ */
+mxGraphSelectionModel.prototype.removeCells = function(cells)
+{
+ if (cells != null)
+ {
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.isSelected(cells[i]))
+ {
+ tmp.push(cells[i]);
+ }
+ }
+
+ this.changeSelection(null, tmp);
+ }
+};
+
+/**
+ * Function: changeSelection
+ *
+ * Inner callback to add the specified <mxCell> to the selection. No event
+ * is fired in this implementation.
+ *
+ * Paramters:
+ *
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.changeSelection = function(added, removed)
+{
+ if ((added != null &&
+ added.length > 0 &&
+ added[0] != null) ||
+ (removed != null &&
+ removed.length > 0 &&
+ removed[0] != null))
+ {
+ var change = new mxSelectionChange(this, added, removed);
+ change.execute();
+ var edit = new mxUndoableEdit(this, false);
+ edit.add(change);
+ this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ }
+};
+
+/**
+ * Function: cellAdded
+ *
+ * Inner callback to add the specified <mxCell> to the selection. No event
+ * is fired in this implementation.
+ *
+ * Paramters:
+ *
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.cellAdded = function(cell)
+{
+ if (cell != null &&
+ !this.isSelected(cell))
+ {
+ this.cells.push(cell);
+ }
+};
+
+/**
+ * Function: cellRemoved
+ *
+ * Inner callback to remove the specified <mxCell> from the selection. No
+ * event is fired in this implementation.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to remove from the selection.
+ */
+mxGraphSelectionModel.prototype.cellRemoved = function(cell)
+{
+ if (cell != null)
+ {
+ var index = mxUtils.indexOf(this.cells, cell);
+
+ if (index >= 0)
+ {
+ this.cells.splice(index, 1);
+ }
+ }
+};
+
+/**
+ * Class: mxSelectionChange
+ *
+ * Action to change the current root in a view.
+ *
+ * Constructor: mxCurrentRootChange
+ *
+ * Constructs a change of the current root in the given view.
+ */
+function mxSelectionChange(selectionModel, added, removed)
+{
+ this.selectionModel = selectionModel;
+ this.added = (added != null) ? added.slice() : null;
+ this.removed = (removed != null) ? removed.slice() : null;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the current root of the view.
+ */
+mxSelectionChange.prototype.execute = function()
+{
+ var t0 = mxLog.enter('mxSelectionChange.execute');
+ window.status = mxResources.get(
+ this.selectionModel.updatingSelectionResource) ||
+ this.selectionModel.updatingSelectionResource;
+
+ if (this.removed != null)
+ {
+ for (var i = 0; i < this.removed.length; i++)
+ {
+ this.selectionModel.cellRemoved(this.removed[i]);
+ }
+ }
+
+ if (this.added != null)
+ {
+ for (var i = 0; i < this.added.length; i++)
+ {
+ this.selectionModel.cellAdded(this.added[i]);
+ }
+ }
+
+ var tmp = this.added;
+ this.added = this.removed;
+ this.removed = tmp;
+
+ window.status = mxResources.get(this.selectionModel.doneResource) ||
+ this.selectionModel.doneResource;
+ mxLog.leave('mxSelectionChange.execute', t0);
+
+ this.selectionModel.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ 'added', this.added, 'removed', this.removed));
+};
diff --git a/src/js/view/mxGraphView.js b/src/js/view/mxGraphView.js
new file mode 100644
index 0000000..0ef2dc8
--- /dev/null
+++ b/src/js/view/mxGraphView.js
@@ -0,0 +1,2545 @@
+/**
+ * $Id: mxGraphView.js,v 1.195 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphView
+ *
+ * Extends <mxEventSource> to implement a view for a graph. This class is in
+ * charge of computing the absolute coordinates for the relative child
+ * geometries, the points for perimeters and edge styles and keeping them
+ * cached in <mxCellStates> for faster retrieval. The states are updated
+ * whenever the model or the view state (translate, scale) changes. The scale
+ * and translate are honoured in the bounds.
+ *
+ * Event: mxEvent.UNDO
+ *
+ * Fires after the root was changed in <setCurrentRoot>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> which contains the
+ * <mxCurrentRootChange>.
+ *
+ * Event: mxEvent.SCALE_AND_TRANSLATE
+ *
+ * Fires after the scale and translate have been changed in <scaleAndTranslate>.
+ * The <code>scale</code>, <code>previousScale</code>, <code>translate</code>
+ * and <code>previousTranslate</code> properties contain the new and previous
+ * scale and translate, respectively.
+ *
+ * Event: mxEvent.SCALE
+ *
+ * Fires after the scale was changed in <setScale>. The <code>scale</code> and
+ * <code>previousScale</code> properties contain the new and previous scale.
+ *
+ * Event: mxEvent.TRANSLATE
+ *
+ * Fires after the translate was changed in <setTranslate>. The
+ * <code>translate</code> and <code>previousTranslate</code> properties contain
+ * the new and previous value for translate.
+ *
+ * Event: mxEvent.DOWN and mxEvent.UP
+ *
+ * Fire if the current root is changed by executing an <mxCurrentRootChange>.
+ * The event name depends on the location of the root in the cell hierarchy
+ * with respect to the current root. The <code>root</code> and
+ * <code>previous</code> properties contain the new and previous root,
+ * respectively.
+ *
+ * Constructor: mxGraphView
+ *
+ * Constructs a new view for the given <mxGraph>.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphView(graph)
+{
+ this.graph = graph;
+ this.translate = new mxPoint();
+ this.graphBounds = new mxRectangle();
+ this.states = new mxDictionary();
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphView.prototype = new mxEventSource();
+mxGraphView.prototype.constructor = mxGraphView;
+
+/**
+ *
+ */
+mxGraphView.prototype.EMPTY_POINT = new mxPoint();
+
+/**
+ * Variable: doneResource
+ *
+ * Specifies the resource key for the status message after a long operation.
+ * If the resource for this key does not exist then the value is used as
+ * the status message. Default is 'done'.
+ */
+mxGraphView.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
+
+/**
+ * Function: updatingDocumentResource
+ *
+ * Specifies the resource key for the status message while the document is
+ * being updated. If the resource for this key does not exist then the
+ * value is used as the status message. Default is 'updatingDocument'.
+ */
+mxGraphView.prototype.updatingDocumentResource = (mxClient.language != 'none') ? 'updatingDocument' : '';
+
+/**
+ * Variable: allowEval
+ *
+ * Specifies if string values in cell styles should be evaluated using
+ * <mxUtils.eval>. This will only be used if the string values can't be mapped
+ * to objects using <mxStyleRegistry>. Default is false. NOTE: Enabling this
+ * switch carries a possible security risk (see the section on security in
+ * the manual).
+ */
+mxGraphView.prototype.allowEval = false;
+
+/**
+ * Variable: captureDocumentGesture
+ *
+ * Specifies if a gesture should be captured when it goes outside of the
+ * graph container. Default is true.
+ */
+mxGraphView.prototype.captureDocumentGesture = true;
+
+/**
+ * Variable: rendering
+ *
+ * Specifies if shapes should be created, updated and destroyed using the
+ * methods of <mxCellRenderer> in <graph>. Default is true.
+ */
+mxGraphView.prototype.rendering = true;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphView.prototype.graph = null;
+
+/**
+ * Variable: currentRoot
+ *
+ * <mxCell> that acts as the root of the displayed cell hierarchy.
+ */
+mxGraphView.prototype.currentRoot = null;
+
+/**
+ * Variable: graphBounds
+ *
+ * <mxRectangle> that caches the scales, translated bounds of the current view.
+ */
+mxGraphView.prototype.graphBounds = null;
+
+/**
+ * Variable: scale
+ *
+ * Specifies the scale. Default is 1 (100%).
+ */
+mxGraphView.prototype.scale = 1;
+
+/**
+ * Variable: translate
+ *
+ * <mxPoint> that specifies the current translation. Default is a new
+ * empty <mxPoint>.
+ */
+mxGraphView.prototype.translate = null;
+
+/**
+ * Variable: updateStyle
+ *
+ * Specifies if the style should be updated in each validation step. If this
+ * is false then the style is only updated if the state is created or if the
+ * style of the cell was changed. Default is false.
+ */
+mxGraphView.prototype.updateStyle = false;
+
+/**
+ * Function: getGraphBounds
+ *
+ * Returns <graphBounds>.
+ */
+mxGraphView.prototype.getGraphBounds = function()
+{
+ return this.graphBounds;
+};
+
+/**
+ * Function: setGraphBounds
+ *
+ * Sets <graphBounds>.
+ */
+mxGraphView.prototype.setGraphBounds = function(value)
+{
+ this.graphBounds = value;
+};
+
+/**
+ * Function: getBounds
+ *
+ * Returns the bounds (on the screen) for the given array of <mxCells>.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to return the bounds for.
+ */
+mxGraphView.prototype.getBounds = 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.getState(cells[i]);
+
+ if (state != null)
+ {
+ if (result == null)
+ {
+ result = new mxRectangle(state.x, state.y,
+ state.width, state.height);
+ }
+ else
+ {
+ result.add(state);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: setCurrentRoot
+ *
+ * Sets and returns the current root and fires an <undo> event before
+ * calling <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * root - <mxCell> that specifies the root of the displayed cell hierarchy.
+ */
+mxGraphView.prototype.setCurrentRoot = function(root)
+{
+ if (this.currentRoot != root)
+ {
+ var change = new mxCurrentRootChange(this, root);
+ change.execute();
+ var edit = new mxUndoableEdit(this, false);
+ edit.add(change);
+ this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ this.graph.sizeDidChange();
+ }
+
+ return root;
+};
+
+/**
+ * Function: scaleAndTranslate
+ *
+ * Sets the scale and translation and fires a <scale> and <translate> event
+ * before calling <revalidate> followed by <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * scale - Decimal value that specifies the new scale (1 is 100%).
+ * dx - X-coordinate of the translation.
+ * dy - Y-coordinate of the translation.
+ */
+mxGraphView.prototype.scaleAndTranslate = function(scale, dx, dy)
+{
+ var previousScale = this.scale;
+ var previousTranslate = new mxPoint(this.translate.x, this.translate.y);
+
+ if (this.scale != scale || this.translate.x != dx || this.translate.y != dy)
+ {
+ this.scale = scale;
+
+ this.translate.x = dx;
+ this.translate.y = dy;
+
+ if (this.isEventsEnabled())
+ {
+ this.revalidate();
+ this.graph.sizeDidChange();
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SCALE_AND_TRANSLATE,
+ 'scale', scale, 'previousScale', previousScale,
+ 'translate', this.translate, 'previousTranslate', previousTranslate));
+};
+
+/**
+ * Function: getScale
+ *
+ * Returns the <scale>.
+ */
+mxGraphView.prototype.getScale = function()
+{
+ return this.scale;
+};
+
+/**
+ * Function: setScale
+ *
+ * Sets the scale and fires a <scale> event before calling <revalidate> followed
+ * by <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * value - Decimal value that specifies the new scale (1 is 100%).
+ */
+mxGraphView.prototype.setScale = function(value)
+{
+ var previousScale = this.scale;
+
+ if (this.scale != value)
+ {
+ this.scale = value;
+
+ if (this.isEventsEnabled())
+ {
+ this.revalidate();
+ this.graph.sizeDidChange();
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SCALE,
+ 'scale', value, 'previousScale', previousScale));
+};
+
+/**
+ * Function: getTranslate
+ *
+ * Returns the <translate>.
+ */
+mxGraphView.prototype.getTranslate = function()
+{
+ return this.translate;
+};
+
+/**
+ * Function: setTranslate
+ *
+ * Sets the translation and fires a <translate> event before calling
+ * <revalidate> followed by <mxGraph.sizeDidChange>. The translation is the
+ * negative of the origin.
+ *
+ * Parameters:
+ *
+ * dx - X-coordinate of the translation.
+ * dy - Y-coordinate of the translation.
+ */
+mxGraphView.prototype.setTranslate = function(dx, dy)
+{
+ var previousTranslate = new mxPoint(this.translate.x, this.translate.y);
+
+ if (this.translate.x != dx || this.translate.y != dy)
+ {
+ this.translate.x = dx;
+ this.translate.y = dy;
+
+ if (this.isEventsEnabled())
+ {
+ this.revalidate();
+ this.graph.sizeDidChange();
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.TRANSLATE,
+ 'translate', this.translate, 'previousTranslate', previousTranslate));
+};
+
+/**
+ * Function: refresh
+ *
+ * Clears the view if <currentRoot> is not null and revalidates.
+ */
+mxGraphView.prototype.refresh = function()
+{
+ if (this.currentRoot != null)
+ {
+ this.clear();
+ }
+
+ this.revalidate();
+};
+
+/**
+ * Function: revalidate
+ *
+ * Revalidates the complete view with all cell states.
+ */
+mxGraphView.prototype.revalidate = function()
+{
+ this.invalidate();
+ this.validate();
+};
+
+/**
+ * Function: clear
+ *
+ * Removes the state of the given cell and all descendants if the given
+ * cell is not the current root.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> for which the state should be removed. Default
+ * is the root of the model.
+ * force - Boolean indicating if the current root should be ignored for
+ * recursion.
+ */
+mxGraphView.prototype.clear = function(cell, force, recurse)
+{
+ var model = this.graph.getModel();
+ cell = cell || model.getRoot();
+ force = (force != null) ? force : false;
+ recurse = (recurse != null) ? recurse : true;
+
+ this.removeState(cell);
+
+ if (recurse && (force || cell != this.currentRoot))
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.clear(model.getChildAt(cell, i), force);
+ }
+ }
+ else
+ {
+ this.invalidate(cell);
+ }
+};
+
+/**
+ * Function: invalidate
+ *
+ * Invalidates the state of the given cell, all its descendants and
+ * connected edges.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> to be invalidated. Default is the root of the
+ * model.
+ */
+mxGraphView.prototype.invalidate = function(cell, recurse, includeEdges, orderChanged)
+{
+ var model = this.graph.getModel();
+ cell = cell || model.getRoot();
+ recurse = (recurse != null) ? recurse : true;
+ includeEdges = (includeEdges != null) ? includeEdges : true;
+ orderChanged = (orderChanged != null) ? orderChanged : false;
+
+ var state = this.getState(cell);
+
+ if (state != null)
+ {
+ state.invalid = true;
+
+ if (orderChanged)
+ {
+ state.orderChanged = true;
+ }
+ }
+
+ // Recursively invalidates all descendants
+ if (recurse)
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+ this.invalidate(child, recurse, includeEdges, orderChanged);
+ }
+ }
+
+ // Propagates invalidation to all connected edges
+ if (includeEdges)
+ {
+ var edgeCount = model.getEdgeCount(cell);
+
+ for (var i = 0; i < edgeCount; i++)
+ {
+ this.invalidate(model.getEdgeAt(cell, i), recurse, includeEdges);
+ }
+ }
+};
+
+/**
+ * Function: validate
+ *
+ * First validates all bounds and then validates all points recursively on
+ * all visible cells starting at the given cell. Finally the background
+ * is validated using <validateBackground>.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> to be used as the root of the validation.
+ * Default is <currentRoot> or the root of the model.
+ */
+mxGraphView.prototype.validate = function(cell)
+{
+ var t0 = mxLog.enter('mxGraphView.validate');
+ window.status = mxResources.get(this.updatingDocumentResource) ||
+ this.updatingDocumentResource;
+
+ cell = cell || ((this.currentRoot != null) ?
+ this.currentRoot :
+ this.graph.getModel().getRoot());
+ this.validateBounds(null, cell);
+ var graphBounds = this.validatePoints(null, cell);
+
+ if (graphBounds == null)
+ {
+ graphBounds = new mxRectangle();
+ }
+
+ this.setGraphBounds(graphBounds);
+ this.validateBackground();
+
+ window.status = mxResources.get(this.doneResource) ||
+ this.doneResource;
+ mxLog.leave('mxGraphView.validate', t0);
+};
+
+/**
+ * Function: createBackgroundPageShape
+ *
+ * Creates and returns the shape used as the background page.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that represents the bounds of the shape.
+ */
+mxGraphView.prototype.createBackgroundPageShape = function(bounds)
+{
+ return new mxRectangleShape(bounds, 'white', 'black');
+};
+
+/**
+ * Function: validateBackground
+ *
+ * Validates the background image.
+ */
+mxGraphView.prototype.validateBackground = function()
+{
+ var bg = this.graph.getBackgroundImage();
+
+ if (bg != null)
+ {
+ if (this.backgroundImage == null || this.backgroundImage.image != bg.src)
+ {
+ if (this.backgroundImage != null)
+ {
+ this.backgroundImage.destroy();
+ }
+
+ var bounds = new mxRectangle(0, 0, 1, 1);
+
+ this.backgroundImage = new mxImageShape(bounds, bg.src);
+ this.backgroundImage.dialect = this.graph.dialect;
+ this.backgroundImage.init(this.backgroundPane);
+ this.backgroundImage.redraw();
+ }
+
+ this.redrawBackgroundImage(this.backgroundImage, bg);
+ }
+ else if (this.backgroundImage != null)
+ {
+ this.backgroundImage.destroy();
+ this.backgroundImage = null;
+ }
+
+ if (this.graph.pageVisible)
+ {
+ var bounds = this.getBackgroundPageBounds();
+
+ if (this.backgroundPageShape == null)
+ {
+ this.backgroundPageShape = this.createBackgroundPageShape(bounds);
+ this.backgroundPageShape.scale = this.scale;
+ this.backgroundPageShape.isShadow = true;
+ this.backgroundPageShape.dialect = this.graph.dialect;
+ this.backgroundPageShape.init(this.backgroundPane);
+ this.backgroundPageShape.redraw();
+
+ // Adds listener for double click handling on background
+ mxEvent.addListener(this.backgroundPageShape.node, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.dblClick(evt);
+ })
+ );
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ // Adds basic listeners for graph event dispatching outside of the
+ // container and finishing the handling of a single gesture
+ mxEvent.addListener(this.backgroundPageShape.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+ })
+ );
+ mxEvent.addListener(this.backgroundPageShape.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ // Hides the tooltip if mouse is outside container
+ if (this.graph.tooltipHandler != null &&
+ this.graph.tooltipHandler.isHideOnHover())
+ {
+ this.graph.tooltipHandler.hide();
+ }
+
+ if (this.graph.isMouseDown &&
+ !mxEvent.isConsumed(evt))
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+ mxEvent.addListener(this.backgroundPageShape.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt));
+ })
+ );
+ }
+ else
+ {
+ this.backgroundPageShape.scale = this.scale;
+ this.backgroundPageShape.bounds = bounds;
+ this.backgroundPageShape.redraw();
+ }
+ }
+ else if (this.backgroundPageShape != null)
+ {
+ this.backgroundPageShape.destroy();
+ this.backgroundPageShape = null;
+ }
+};
+
+/**
+ * Function: getBackgroundPageBounds
+ *
+ * Returns the bounds for the background page.
+ */
+mxGraphView.prototype.getBackgroundPageBounds = function()
+{
+ var fmt = this.graph.pageFormat;
+ var ps = this.scale * this.graph.pageScale;
+ var bounds = new mxRectangle(this.scale * this.translate.x, this.scale * this.translate.y,
+ fmt.width * ps, fmt.height * ps);
+
+ return bounds;
+};
+
+/**
+ * Function: redrawBackgroundImage
+ *
+ * Updates the bounds and redraws the background image.
+ *
+ * Example:
+ *
+ * If the background image should not be scaled, this can be replaced with
+ * the following.
+ *
+ * (code)
+ * mxGraphView.prototype.redrawBackground = function(backgroundImage, bg)
+ * {
+ * backgroundImage.bounds.x = this.translate.x;
+ * backgroundImage.bounds.y = this.translate.y;
+ * backgroundImage.bounds.width = bg.width;
+ * backgroundImage.bounds.height = bg.height;
+ *
+ * backgroundImage.redraw();
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * backgroundImage - <mxImageShape> that represents the background image.
+ * bg - <mxImage> that specifies the image and its dimensions.
+ */
+mxGraphView.prototype.redrawBackgroundImage = function(backgroundImage, bg)
+{
+ backgroundImage.scale = this.scale;
+ backgroundImage.bounds.x = this.scale * this.translate.x;
+ backgroundImage.bounds.y = this.scale * this.translate.y;
+ backgroundImage.bounds.width = this.scale * bg.width;
+ backgroundImage.bounds.height = this.scale * bg.height;
+
+ backgroundImage.redraw();
+};
+
+/**
+ * Function: validateBounds
+ *
+ * Validates the bounds of the given parent's child using the given parent
+ * state as the origin for the child. The validation is carried out
+ * recursively for all non-collapsed descendants.
+ *
+ * Parameters:
+ *
+ * parentState - <mxCellState> for the given parent.
+ * cell - <mxCell> for which the bounds in the state should be updated.
+ */
+mxGraphView.prototype.validateBounds = function(parentState, cell)
+{
+ var model = this.graph.getModel();
+ var state = this.getState(cell, true);
+
+ if (state != null && state.invalid)
+ {
+ if (!this.graph.isCellVisible(cell))
+ {
+ this.removeState(cell);
+ }
+
+ // Updates the cell state's origin
+ else if (cell != this.currentRoot && parentState != null)
+ {
+ state.absoluteOffset.x = 0;
+ state.absoluteOffset.y = 0;
+ state.origin.x = parentState.origin.x;
+ state.origin.y = parentState.origin.y;
+ var geo = this.graph.getCellGeometry(cell);
+
+ if (geo != null)
+ {
+ if (!model.isEdge(cell))
+ {
+ var offset = geo.offset || this.EMPTY_POINT;
+
+ if (geo.relative)
+ {
+ state.origin.x += geo.x * parentState.width /
+ this.scale + offset.x;
+ state.origin.y += geo.y * parentState.height /
+ this.scale + offset.y;
+ }
+ else
+ {
+ state.absoluteOffset.x = this.scale * offset.x;
+ state.absoluteOffset.y = this.scale * offset.y;
+ state.origin.x += geo.x;
+ state.origin.y += geo.y;
+ }
+ }
+
+ // Updates cell state's bounds
+ state.x = this.scale * (this.translate.x + state.origin.x);
+ state.y = this.scale * (this.translate.y + state.origin.y);
+ state.width = this.scale * geo.width;
+ state.height = this.scale * geo.height;
+
+ if (model.isVertex(cell))
+ {
+ this.updateVertexLabelOffset(state);
+ }
+ }
+ }
+
+ // Applies child offset to origin
+ var offset = this.graph.getChildOffsetForCell(cell);
+
+ if (offset != null)
+ {
+ state.origin.x += offset.x;
+ state.origin.y += offset.y;
+ }
+ }
+
+ // Recursively validates the child bounds
+ if (state != null && (!this.graph.isCellCollapsed(cell) ||
+ cell == this.currentRoot))
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+ this.validateBounds(state, child);
+ }
+ }
+};
+
+/**
+ * Function: updateVertexLabelOffset
+ *
+ * Updates the absoluteOffset of the given vertex cell state. This takes
+ * into account the label position styles.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose absolute offset should be updated.
+ */
+mxGraphView.prototype.updateVertexLabelOffset = function(state)
+{
+ var horizontal = mxUtils.getValue(state.style,
+ mxConstants.STYLE_LABEL_POSITION,
+ mxConstants.ALIGN_CENTER);
+
+ if (horizontal == mxConstants.ALIGN_LEFT)
+ {
+ state.absoluteOffset.x -= state.width;
+ }
+ else if (horizontal == mxConstants.ALIGN_RIGHT)
+ {
+ state.absoluteOffset.x += state.width;
+ }
+
+ var vertical = mxUtils.getValue(state.style,
+ mxConstants.STYLE_VERTICAL_LABEL_POSITION,
+ mxConstants.ALIGN_MIDDLE);
+
+ if (vertical == mxConstants.ALIGN_TOP)
+ {
+ state.absoluteOffset.y -= state.height;
+ }
+ else if (vertical == mxConstants.ALIGN_BOTTOM)
+ {
+ state.absoluteOffset.y += state.height;
+ }
+};
+
+/**
+ * Function: validatePoints
+ *
+ * Validates the points for the state of the given cell recursively if the
+ * cell is not collapsed and returns the bounding box of all visited states
+ * as an <mxRectangle>.
+ *
+ * Parameters:
+ *
+ * parentState - <mxCellState> for the parent cell.
+ * cell - <mxCell> whose points in the state should be updated.
+ */
+mxGraphView.prototype.validatePoints = function(parentState, cell)
+{
+ var model = this.graph.getModel();
+ var state = this.getState(cell);
+ var bbox = null;
+
+ if (state != null)
+ {
+ if (state.invalid)
+ {
+ var geo = this.graph.getCellGeometry(cell);
+
+ if (geo != null && model.isEdge(cell))
+ {
+ // Updates the points on the source terminal if its an edge
+ var source = this.getState(this.getVisibleTerminal(cell, true));
+ state.setVisibleTerminalState(source, true);
+
+ if (source != null && model.isEdge(source.cell) &&
+ !model.isAncestor(source.cell, cell))
+ {
+ var tmp = this.getState(model.getParent(source.cell));
+ this.validatePoints(tmp, source.cell);
+ }
+
+ // Updates the points on the target terminal if its an edge
+ var target = this.getState(this.getVisibleTerminal(cell, false));
+ state.setVisibleTerminalState(target, false);
+
+ if (target != null && model.isEdge(target.cell) &&
+ !model.isAncestor(target.cell, cell))
+ {
+ var tmp = this.getState(model.getParent(target.cell));
+ this.validatePoints(tmp, target.cell);
+ }
+
+ this.updateFixedTerminalPoints(state, source, target);
+ this.updatePoints(state, geo.points, source, target);
+ this.updateFloatingTerminalPoints(state, source, target);
+ this.updateEdgeBounds(state);
+ this.updateEdgeLabelOffset(state);
+ }
+ else if (geo != null && geo.relative && parentState != null &&
+ model.isEdge(parentState.cell))
+ {
+ var origin = this.getPoint(parentState, geo);
+
+ if (origin != null)
+ {
+ state.x = origin.x;
+ state.y = origin.y;
+
+ origin.x = (origin.x / this.scale) - this.translate.x;
+ origin.y = (origin.y / this.scale) - this.translate.y;
+ state.origin = origin;
+
+ this.childMoved(parentState, state);
+ }
+ }
+
+ state.invalid = false;
+
+ if (cell != this.currentRoot)
+ {
+ // NOTE: Label bounds currently ignored if rendering is false
+ this.graph.cellRenderer.redraw(state, false, this.isRendering());
+ }
+ }
+
+ if (model.isEdge(cell) || model.isVertex(cell))
+ {
+ if (state.shape != null && state.shape.boundingBox != null)
+ {
+ bbox = state.shape.boundingBox.clone();
+ }
+
+ if (state.text != null && !this.graph.isLabelClipped(state.cell))
+ {
+ // Adds label bounding box to graph bounds
+ if (state.text.boundingBox != null)
+ {
+ if (bbox != null)
+ {
+ bbox.add(state.text.boundingBox);
+ }
+ else
+ {
+ bbox = state.text.boundingBox.clone();
+ }
+ }
+ }
+ }
+ }
+
+ if (state != null && (!this.graph.isCellCollapsed(cell) ||
+ cell == this.currentRoot))
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+ var bounds = this.validatePoints(state, child);
+
+ if (bounds != null)
+ {
+ if (bbox == null)
+ {
+ bbox = bounds;
+ }
+ else
+ {
+ bbox.add(bounds);
+ }
+ }
+ }
+ }
+
+ return bbox;
+};
+
+/**
+ * Function: childMoved
+ *
+ * Invoked when a child state was moved as a result of late evaluation
+ * of its position. This is invoked for relative edge children whose
+ * position can only be determined after the points of the parent edge
+ * are updated in validatePoints, and validates the bounds of all
+ * descendants of the child using validateBounds.
+ *
+ * Parameters:
+ *
+ * parent - <mxCellState> that represents the parent state.
+ * child - <mxCellState> that represents the child state.
+ */
+mxGraphView.prototype.childMoved = function(parent, child)
+{
+ var cell = child.cell;
+
+ // Children of relative edge children need to validate
+ // their bounds after their parent state was updated
+ if (!this.graph.isCellCollapsed(cell) || cell == this.currentRoot)
+ {
+ var model = this.graph.getModel();
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.validateBounds(child, model.getChildAt(cell, i));
+ }
+ }
+};
+
+/**
+ * Function: updateFixedTerminalPoints
+ *
+ * Sets the initial absolute terminal points in the given state before the edge
+ * style is computed.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> whose initial terminal points should be updated.
+ * source - <mxCellState> which represents the source terminal.
+ * target - <mxCellState> which represents the target terminal.
+ */
+mxGraphView.prototype.updateFixedTerminalPoints = function(edge, source, target)
+{
+ this.updateFixedTerminalPoint(edge, source, true,
+ this.graph.getConnectionConstraint(edge, source, true));
+ this.updateFixedTerminalPoint(edge, target, false,
+ this.graph.getConnectionConstraint(edge, target, false));
+};
+
+/**
+ * Function: updateFixedTerminalPoint
+ *
+ * Sets the fixed source or target terminal point on the given edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> whose terminal point should be updated.
+ * terminal - <mxCellState> which represents the actual terminal.
+ * source - Boolean that specifies if the terminal is the source.
+ * constraint - <mxConnectionConstraint> that specifies the connection.
+ */
+mxGraphView.prototype.updateFixedTerminalPoint = function(edge, terminal, source, constraint)
+{
+ var pt = null;
+
+ if (constraint != null)
+ {
+ pt = this.graph.getConnectionPoint(terminal, constraint);
+ }
+
+ if (pt == null && terminal == null)
+ {
+ var s = this.scale;
+ var tr = this.translate;
+ var orig = edge.origin;
+ var geo = this.graph.getCellGeometry(edge.cell);
+ pt = geo.getTerminalPoint(source);
+
+ if (pt != null)
+ {
+ pt = new mxPoint(s * (tr.x + pt.x + orig.x),
+ s * (tr.y + pt.y + orig.y));
+ }
+ }
+
+ edge.setAbsoluteTerminalPoint(pt, source);
+};
+
+/**
+ * Function: updatePoints
+ *
+ * Updates the absolute points in the given state using the specified array
+ * of <mxPoints> as the relative points.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> whose absolute points should be updated.
+ * points - Array of <mxPoints> that constitute the relative points.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ */
+mxGraphView.prototype.updatePoints = function(edge, points, source, target)
+{
+ if (edge != null)
+ {
+ var pts = [];
+ pts.push(edge.absolutePoints[0]);
+ var edgeStyle = this.getEdgeStyle(edge, points, source, target);
+
+ if (edgeStyle != null)
+ {
+ var src = this.getTerminalPort(edge, source, true);
+ var trg = this.getTerminalPort(edge, target, false);
+
+ edgeStyle(edge, src, trg, points, pts);
+ }
+ else if (points != null)
+ {
+ for (var i = 0; i < points.length; i++)
+ {
+ if (points[i] != null)
+ {
+ var pt = mxUtils.clone(points[i]);
+ pts.push(this.transformControlPoint(edge, pt));
+ }
+ }
+ }
+
+ var tmp = edge.absolutePoints;
+ pts.push(tmp[tmp.length-1]);
+
+ edge.absolutePoints = pts;
+ }
+};
+
+/**
+ * Function: transformControlPoint
+ *
+ * Transforms the given control point to an absolute point.
+ */
+mxGraphView.prototype.transformControlPoint = function(state, pt)
+{
+ var orig = state.origin;
+
+ return new mxPoint(this.scale * (pt.x + this.translate.x + orig.x),
+ this.scale * (pt.y + this.translate.y + orig.y));
+};
+
+/**
+ * Function: getEdgeStyle
+ *
+ * Returns the edge style function to be used to render the given edge
+ * state.
+ */
+mxGraphView.prototype.getEdgeStyle = function(edge, points, source, target)
+{
+ var edgeStyle = (source != null && source == target) ?
+ mxUtils.getValue(edge.style, mxConstants.STYLE_LOOP,
+ this.graph.defaultLoopStyle) :
+ (!mxUtils.getValue(edge.style,
+ mxConstants.STYLE_NOEDGESTYLE, false) ?
+ edge.style[mxConstants.STYLE_EDGE] :
+ null);
+
+ // Converts string values to objects
+ if (typeof(edgeStyle) == "string")
+ {
+ var tmp = mxStyleRegistry.getValue(edgeStyle);
+
+ if (tmp == null && this.isAllowEval())
+ {
+ tmp = mxUtils.eval(edgeStyle);
+ }
+
+ edgeStyle = tmp;
+ }
+
+ if (typeof(edgeStyle) == "function")
+ {
+ return edgeStyle;
+ }
+
+ return null;
+};
+
+/**
+ * Function: updateFloatingTerminalPoints
+ *
+ * Updates the terminal points in the given state after the edge style was
+ * computed for the edge.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose terminal points should be updated.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ */
+mxGraphView.prototype.updateFloatingTerminalPoints = function(state, source, target)
+{
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length - 1];
+
+ if (pe == null && target != null)
+ {
+ this.updateFloatingTerminalPoint(state, target, source, false);
+ }
+
+ if (p0 == null && source != null)
+ {
+ this.updateFloatingTerminalPoint(state, source, target, true);
+ }
+};
+
+/**
+ * Function: updateFloatingTerminalPoint
+ *
+ * Updates the absolute terminal point in the given state for the given
+ * start and end state, where start is the source if source is true.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> whose terminal point should be updated.
+ * start - <mxCellState> for the terminal on "this" side of the edge.
+ * end - <mxCellState> for the terminal on the other side of the edge.
+ * source - Boolean indicating if start is the source terminal state.
+ */
+mxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source)
+{
+ start = this.getTerminalPort(edge, start, source);
+ var next = this.getNextPoint(edge, end, source);
+
+ var alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0'));
+ var center = new mxPoint(start.getCenterX(), start.getCenterY());
+
+ if (alpha != 0)
+ {
+ var cos = Math.cos(-alpha);
+ var sin = Math.sin(-alpha);
+ next = mxUtils.getRotatedPoint(next, cos, sin, center);
+ }
+
+ var border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+ border += parseFloat(edge.style[(source) ?
+ mxConstants.STYLE_SOURCE_PERIMETER_SPACING :
+ mxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0);
+ var pt = this.getPerimeterPoint(start, next, this.graph.isOrthogonal(edge), border);
+
+ if (alpha != 0)
+ {
+ var cos = Math.cos(alpha);
+ var sin = Math.sin(alpha);
+ pt = mxUtils.getRotatedPoint(pt, cos, sin, center);
+ }
+
+ edge.setAbsoluteTerminalPoint(pt, source);
+};
+
+/**
+ * Function: getTerminalPort
+ *
+ * Returns an <mxCellState> that represents the source or target terminal or
+ * port for the given edge.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the state of the edge.
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean indicating if the given terminal is the source terminal.
+ */
+mxGraphView.prototype.getTerminalPort = function(state, terminal, source)
+{
+ var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
+ mxConstants.STYLE_TARGET_PORT;
+ var id = mxUtils.getValue(state.style, key);
+
+ if (id != null)
+ {
+ var tmp = this.getState(this.graph.getModel().getCell(id));
+
+ // Only uses ports where a cell state exists
+ if (tmp != null)
+ {
+ terminal = tmp;
+ }
+ }
+
+ return terminal;
+};
+
+/**
+ * Function: getPerimeterPoint
+ *
+ * Returns an <mxPoint> that defines the location of the intersection point between
+ * the perimeter and the line between the center of the shape and the given point.
+ *
+ * Parameters:
+ *
+ * terminal - <mxCellState> for the source or target terminal.
+ * next - <mxPoint> that lies outside of the given terminal.
+ * orthogonal - Boolean that specifies if the orthogonal projection onto
+ * the perimeter should be returned. If this is false then the intersection
+ * of the perimeter and the line between the next and the center point is
+ * returned.
+ * border - Optional border between the perimeter and the shape.
+ */
+mxGraphView.prototype.getPerimeterPoint = function(terminal, next, orthogonal, border)
+{
+ var point = null;
+
+ if (terminal != null)
+ {
+ var perimeter = this.getPerimeterFunction(terminal);
+
+ if (perimeter != null && next != null)
+ {
+ var bounds = this.getPerimeterBounds(terminal, border);
+
+ if (bounds.width > 0 || bounds.height > 0)
+ {
+ point = perimeter(bounds, terminal, next, orthogonal);
+ }
+ }
+
+ if (point == null)
+ {
+ point = this.getPoint(terminal);
+ }
+ }
+
+ return point;
+};
+
+/**
+ * Function: getRoutingCenterX
+ *
+ * Returns the x-coordinate of the center point for automatic routing.
+ */
+mxGraphView.prototype.getRoutingCenterX = function (state)
+{
+ var f = (state.style != null) ? parseFloat(state.style
+ [mxConstants.STYLE_ROUTING_CENTER_X]) || 0 : 0;
+
+ return state.getCenterX() + f * state.width;
+};
+
+/**
+ * Function: getRoutingCenterY
+ *
+ * Returns the y-coordinate of the center point for automatic routing.
+ */
+mxGraphView.prototype.getRoutingCenterY = function (state)
+{
+ var f = (state.style != null) ? parseFloat(state.style
+ [mxConstants.STYLE_ROUTING_CENTER_Y]) || 0 : 0;
+
+ return state.getCenterY() + f * state.height;
+};
+
+/**
+ * Function: getPerimeterBounds
+ *
+ * Returns the perimeter bounds for the given terminal, edge pair as an
+ * <mxRectangle>.
+ *
+ * If you have a model where each terminal has a relative child that should
+ * act as the graphical endpoint for a connection from/to the terminal, then
+ * this method can be replaced as follows:
+ *
+ * (code)
+ * var oldGetPerimeterBounds = mxGraphView.prototype.getPerimeterBounds;
+ * mxGraphView.prototype.getPerimeterBounds = function(terminal, edge, isSource)
+ * {
+ * var model = this.graph.getModel();
+ * var childCount = model.getChildCount(terminal.cell);
+ *
+ * if (childCount > 0)
+ * {
+ * var child = model.getChildAt(terminal.cell, 0);
+ * var geo = model.getGeometry(child);
+ *
+ * if (geo != null &&
+ * geo.relative)
+ * {
+ * var state = this.getState(child);
+ *
+ * if (state != null)
+ * {
+ * terminal = state;
+ * }
+ * }
+ * }
+ *
+ * return oldGetPerimeterBounds.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * terminal - <mxCellState> that represents the terminal.
+ * border - Number that adds a border between the shape and the perimeter.
+ */
+mxGraphView.prototype.getPerimeterBounds = function(terminal, border)
+{
+ border = (border != null) ? border : 0;
+
+ if (terminal != null)
+ {
+ border += parseFloat(terminal.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+ }
+
+ return terminal.getPerimeterBounds(border * this.scale);
+};
+
+/**
+ * Function: getPerimeterFunction
+ *
+ * Returns the perimeter function for the given state.
+ */
+mxGraphView.prototype.getPerimeterFunction = function(state)
+{
+ var perimeter = state.style[mxConstants.STYLE_PERIMETER];
+
+ // Converts string values to objects
+ if (typeof(perimeter) == "string")
+ {
+ var tmp = mxStyleRegistry.getValue(perimeter);
+
+ if (tmp == null && this.isAllowEval())
+ {
+ tmp = mxUtils.eval(perimeter);
+ }
+
+ perimeter = tmp;
+ }
+
+ if (typeof(perimeter) == "function")
+ {
+ return perimeter;
+ }
+
+ return null;
+};
+
+/**
+ * Function: getNextPoint
+ *
+ * Returns the nearest point in the list of absolute points or the center
+ * of the opposite terminal.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> that represents the edge.
+ * opposite - <mxCellState> that represents the opposite terminal.
+ * source - Boolean indicating if the next point for the source or target
+ * should be returned.
+ */
+mxGraphView.prototype.getNextPoint = function(edge, opposite, source)
+{
+ var pts = edge.absolutePoints;
+ var point = null;
+
+ if (pts != null && (source || pts.length > 2 || opposite == null))
+ {
+ var count = pts.length;
+ point = pts[(source) ? Math.min(1, count - 1) : Math.max(0, count - 2)];
+ }
+
+ if (point == null && opposite != null)
+ {
+ point = new mxPoint(opposite.getCenterX(), opposite.getCenterY());
+ }
+
+ return point;
+};
+
+/**
+ * Function: getVisibleTerminal
+ *
+ * Returns the nearest ancestor terminal that is visible. The edge appears
+ * to be connected to this terminal on the display. The result of this method
+ * is cached in <mxCellState.getVisibleTerminalState>.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose visible terminal should be returned.
+ * source - Boolean that specifies if the source or target terminal
+ * should be returned.
+ */
+mxGraphView.prototype.getVisibleTerminal = function(edge, source)
+{
+ var model = this.graph.getModel();
+ var result = model.getTerminal(edge, source);
+ var best = result;
+
+ while (result != null && result != this.currentRoot)
+ {
+ if (!this.graph.isCellVisible(best) || this.graph.isCellCollapsed(result))
+ {
+ best = result;
+ }
+
+ result = model.getParent(result);
+ }
+
+ // Checks if the result is not a layer
+ if (model.getParent(best) == model.getRoot())
+ {
+ best = null;
+ }
+
+ return best;
+};
+
+/**
+ * Function: updateEdgeBounds
+ *
+ * Updates the given state using the bounding box of the absolute points.
+ * Also updates <mxCellState.terminalDistance>, <mxCellState.length> and
+ * <mxCellState.segments>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose bounds should be updated.
+ */
+mxGraphView.prototype.updateEdgeBounds = function(state)
+{
+ var points = state.absolutePoints;
+ state.length = 0;
+
+ if (points != null && points.length > 0)
+ {
+ var p0 = points[0];
+ var pe = points[points.length - 1];
+
+ if (p0 == null || pe == null)
+ {
+ // Drops the edge state if the edge is not the root
+ if (state.cell != this.currentRoot)
+ {
+ // Note: This condition normally occurs if a connected edge has a
+ // null-terminal, ie. edge.source == null or edge.target == null,
+ // and no corresponding terminal point defined, which happens for
+ // example if the terminal-id was not resolved at cell decoding time.
+ this.clear(state.cell, true);
+ }
+ }
+ else
+ {
+ if (p0.x != pe.x || p0.y != pe.y)
+ {
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ state.terminalDistance = Math.sqrt(dx * dx + dy * dy);
+ }
+ else
+ {
+ state.terminalDistance = 0;
+ }
+
+ var length = 0;
+ var segments = [];
+ var pt = p0;
+
+ if (pt != null)
+ {
+ var minX = pt.x;
+ var minY = pt.y;
+ var maxX = minX;
+ var maxY = minY;
+
+ for (var i = 1; i < points.length; i++)
+ {
+ var tmp = points[i];
+
+ if (tmp != null)
+ {
+ var dx = pt.x - tmp.x;
+ var dy = pt.y - tmp.y;
+
+ var segment = Math.sqrt(dx * dx + dy * dy);
+ segments.push(segment);
+ length += segment;
+
+ pt = tmp;
+
+ minX = Math.min(pt.x, minX);
+ minY = Math.min(pt.y, minY);
+ maxX = Math.max(pt.x, maxX);
+ maxY = Math.max(pt.y, maxY);
+ }
+ }
+
+ state.length = length;
+ state.segments = segments;
+
+ var markerSize = 1; // TODO: include marker size
+
+ state.x = minX;
+ state.y = minY;
+ state.width = Math.max(markerSize, maxX - minX);
+ state.height = Math.max(markerSize, maxY - minY);
+ }
+ }
+ }
+};
+
+/**
+ * Function: getPoint
+ *
+ * Returns the absolute point on the edge for the given relative
+ * <mxGeometry> as an <mxPoint>. The edge is represented by the given
+ * <mxCellState>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the state of the parent edge.
+ * geometry - <mxGeometry> that represents the relative location.
+ */
+mxGraphView.prototype.getPoint = function(state, geometry)
+{
+ var x = state.getCenterX();
+ var y = state.getCenterY();
+
+ if (state.segments != null && (geometry == null || geometry.relative))
+ {
+ var gx = (geometry != null) ? geometry.x / 2 : 0;
+ var pointCount = state.absolutePoints.length;
+ var dist = (gx + 0.5) * state.length;
+ var segment = state.segments[0];
+ var length = 0;
+ var index = 1;
+
+ while (dist > length + segment && index < pointCount-1)
+ {
+ length += segment;
+ segment = state.segments[index++];
+ }
+
+ var factor = (segment == 0) ? 0 : (dist - length) / segment;
+ var p0 = state.absolutePoints[index-1];
+ var pe = state.absolutePoints[index];
+
+ if (p0 != null && pe != null)
+ {
+ var gy = 0;
+ var offsetX = 0;
+ var offsetY = 0;
+
+ if (geometry != null)
+ {
+ gy = geometry.y;
+ var offset = geometry.offset;
+
+ if (offset != null)
+ {
+ offsetX = offset.x;
+ offsetY = offset.y;
+ }
+ }
+
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ var nx = (segment == 0) ? 0 : dy / segment;
+ var ny = (segment == 0) ? 0 : dx / segment;
+
+ x = p0.x + dx * factor + (nx * gy + offsetX) * this.scale;
+ y = p0.y + dy * factor - (ny * gy - offsetY) * this.scale;
+ }
+ }
+ else if (geometry != null)
+ {
+ var offset = geometry.offset;
+
+ if (offset != null)
+ {
+ x += offset.x;
+ y += offset.y;
+ }
+ }
+
+ return new mxPoint(x, y);
+};
+
+/**
+ * Function: getRelativePoint
+ *
+ * Gets the relative point that describes the given, absolute label
+ * position for the given edge state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the state of the parent edge.
+ * x - Specifies the x-coordinate of the absolute label location.
+ * y - Specifies the y-coordinate of the absolute label location.
+ */
+mxGraphView.prototype.getRelativePoint = function(edgeState, x, y)
+{
+ var model = this.graph.getModel();
+ var geometry = model.getGeometry(edgeState.cell);
+
+ if (geometry != null)
+ {
+ var pointCount = edgeState.absolutePoints.length;
+
+ if (geometry.relative && pointCount > 1)
+ {
+ var totalLength = edgeState.length;
+ var segments = edgeState.segments;
+
+ // Works which line segment the point of the label is closest to
+ var p0 = edgeState.absolutePoints[0];
+ var pe = edgeState.absolutePoints[1];
+ var minDist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
+
+ var index = 0;
+ var tmp = 0;
+ var length = 0;
+
+ for (var i = 2; i < pointCount; i++)
+ {
+ tmp += segments[i - 2];
+ pe = edgeState.absolutePoints[i];
+ var dist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
+
+ if (dist <= minDist)
+ {
+ minDist = dist;
+ index = i - 1;
+ length = tmp;
+ }
+
+ p0 = pe;
+ }
+
+ var seg = segments[index];
+ p0 = edgeState.absolutePoints[index];
+ pe = edgeState.absolutePoints[index + 1];
+
+ var x2 = p0.x;
+ var y2 = p0.y;
+
+ var x1 = pe.x;
+ var y1 = pe.y;
+
+ var px = x;
+ var py = y;
+
+ var xSegment = x2 - x1;
+ var ySegment = y2 - y1;
+
+ px -= x1;
+ py -= y1;
+ var projlenSq = 0;
+
+ px = xSegment - px;
+ py = ySegment - py;
+ var dotprod = px * xSegment + py * ySegment;
+
+ if (dotprod <= 0.0)
+ {
+ projlenSq = 0;
+ }
+ else
+ {
+ projlenSq = dotprod * dotprod
+ / (xSegment * xSegment + ySegment * ySegment);
+ }
+
+ var projlen = Math.sqrt(projlenSq);
+
+ if (projlen > seg)
+ {
+ projlen = seg;
+ }
+
+ var yDistance = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, pe
+ .x, pe.y, x, y));
+ var direction = mxUtils.relativeCcw(p0.x, p0.y, pe.x, pe.y, x, y);
+
+ if (direction == -1)
+ {
+ yDistance = -yDistance;
+ }
+
+ // Constructs the relative point for the label
+ return new mxPoint(((totalLength / 2 - length - projlen) / totalLength) * -2,
+ yDistance / this.scale);
+ }
+ }
+
+ return new mxPoint();
+};
+
+/**
+ * Function: updateEdgeLabelOffset
+ *
+ * Updates <mxCellState.absoluteOffset> for the given state. The absolute
+ * offset is normally used for the position of the edge label. Is is
+ * calculated from the geometry as an absolute offset from the center
+ * between the two endpoints if the geometry is absolute, or as the
+ * relative distance between the center along the line and the absolute
+ * orthogonal distance if the geometry is relative.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose absolute offset should be updated.
+ */
+mxGraphView.prototype.updateEdgeLabelOffset = function(state)
+{
+ var points = state.absolutePoints;
+
+ state.absoluteOffset.x = state.getCenterX();
+ state.absoluteOffset.y = state.getCenterY();
+
+ if (points != null && points.length > 0 && state.segments != null)
+ {
+ var geometry = this.graph.getCellGeometry(state.cell);
+
+ if (geometry.relative)
+ {
+ var offset = this.getPoint(state, geometry);
+
+ if (offset != null)
+ {
+ state.absoluteOffset = offset;
+ }
+ }
+ else
+ {
+ var p0 = points[0];
+ var pe = points[points.length - 1];
+
+ if (p0 != null && pe != null)
+ {
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ var x0 = 0;
+ var y0 = 0;
+
+ var off = geometry.offset;
+
+ if (off != null)
+ {
+ x0 = off.x;
+ y0 = off.y;
+ }
+
+ var x = p0.x + dx / 2 + x0 * this.scale;
+ var y = p0.y + dy / 2 + y0 * this.scale;
+
+ state.absoluteOffset.x = x;
+ state.absoluteOffset.y = y;
+ }
+ }
+ }
+};
+
+/**
+ * Function: getState
+ *
+ * Returns the <mxCellState> for the given cell. If create is true, then
+ * the state is created if it does not yet exist.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the <mxCellState> should be returned.
+ * create - Optional boolean indicating if a new state should be created
+ * if it does not yet exist. Default is false.
+ */
+mxGraphView.prototype.getState = function(cell, create)
+{
+ create = create || false;
+ var state = null;
+
+ if (cell != null)
+ {
+ state = this.states.get(cell);
+
+ if (this.graph.isCellVisible(cell))
+ {
+ if (state == null && create && this.graph.isCellVisible(cell))
+ {
+ state = this.createState(cell);
+ this.states.put(cell, state);
+ }
+ else if (create && state != null && this.updateStyle)
+ {
+ state.style = this.graph.getCellStyle(cell);
+ }
+ }
+ }
+
+ return state;
+};
+
+/**
+ * Function: isRendering
+ *
+ * Returns <rendering>.
+ */
+mxGraphView.prototype.isRendering = function()
+{
+ return this.rendering;
+};
+
+/**
+ * Function: setRendering
+ *
+ * Sets <rendering>.
+ */
+mxGraphView.prototype.setRendering = function(value)
+{
+ this.rendering = value;
+};
+
+/**
+ * Function: isAllowEval
+ *
+ * Returns <allowEval>.
+ */
+mxGraphView.prototype.isAllowEval = function()
+{
+ return this.allowEval;
+};
+
+/**
+ * Function: setAllowEval
+ *
+ * Sets <allowEval>.
+ */
+mxGraphView.prototype.setAllowEval = function(value)
+{
+ this.allowEval = value;
+};
+
+/**
+ * Function: getStates
+ *
+ * Returns <states>.
+ */
+mxGraphView.prototype.getStates = function()
+{
+ return this.states;
+};
+
+/**
+ * Function: setStates
+ *
+ * Sets <states>.
+ */
+mxGraphView.prototype.setStates = function(value)
+{
+ this.states = value;
+};
+
+/**
+ * Function: getCellStates
+ *
+ * Returns the <mxCellStates> for the given array of <mxCells>. The array
+ * contains all states that are not null, that is, the returned array may
+ * have less elements than the given array. If no argument is given, then
+ * this returns <states>.
+ */
+mxGraphView.prototype.getCellStates = function(cells)
+{
+ if (cells == null)
+ {
+ return this.states;
+ }
+ else
+ {
+ var result = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var state = this.getState(cells[i]);
+
+ if (state != null)
+ {
+ result.push(state);
+ }
+ }
+
+ return result;
+ }
+};
+
+/**
+ * Function: removeState
+ *
+ * Removes and returns the <mxCellState> for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the <mxCellState> should be removed.
+ */
+mxGraphView.prototype.removeState = function(cell)
+{
+ var state = null;
+
+ if (cell != null)
+ {
+ state = this.states.remove(cell);
+
+ if (state != null)
+ {
+ this.graph.cellRenderer.destroy(state);
+ state.destroy();
+ }
+ }
+
+ return state;
+};
+
+/**
+ * Function: createState
+ *
+ * Creates and returns an <mxCellState> for the given cell and initializes
+ * it using <mxCellRenderer.initialize>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which a new <mxCellState> should be created.
+ */
+mxGraphView.prototype.createState = function(cell)
+{
+ var style = this.graph.getCellStyle(cell);
+ var state = new mxCellState(this, cell, style);
+ this.graph.cellRenderer.initialize(state, this.isRendering());
+
+ return state;
+};
+
+/**
+ * Function: getCanvas
+ *
+ * Returns the DOM node that contains the background-, draw- and
+ * overlaypane.
+ */
+mxGraphView.prototype.getCanvas = function()
+{
+ return this.canvas;
+};
+
+/**
+ * Function: getBackgroundPane
+ *
+ * Returns the DOM node that represents the background layer.
+ */
+mxGraphView.prototype.getBackgroundPane = function()
+{
+ return this.backgroundPane;
+};
+
+/**
+ * Function: getDrawPane
+ *
+ * Returns the DOM node that represents the main drawing layer.
+ */
+mxGraphView.prototype.getDrawPane = function()
+{
+ return this.drawPane;
+};
+
+/**
+ * Function: getOverlayPane
+ *
+ * Returns the DOM node that represents the topmost drawing layer.
+ */
+mxGraphView.prototype.getOverlayPane = function()
+{
+ return this.overlayPane;
+};
+
+/**
+ * Function: isContainerEvent
+ *
+ * Returns true if the event origin is one of the drawing panes or
+ * containers of the view.
+ */
+mxGraphView.prototype.isContainerEvent = function(evt)
+{
+ var source = mxEvent.getSource(evt);
+
+ return (source == this.graph.container ||
+ source.parentNode == this.backgroundPane ||
+ (source.parentNode != null &&
+ source.parentNode.parentNode == this.backgroundPane) ||
+ source == this.canvas.parentNode ||
+ source == this.canvas ||
+ source == this.backgroundPane ||
+ source == this.drawPane ||
+ source == this.overlayPane);
+};
+
+/**
+ * Function: isScrollEvent
+ *
+ * Returns true if the event origin is one of the scrollbars of the
+ * container in IE. Such events are ignored.
+ */
+ mxGraphView.prototype.isScrollEvent = function(evt)
+{
+ var offset = mxUtils.getOffset(this.graph.container);
+ var pt = new mxPoint(evt.clientX - offset.x, evt.clientY - offset.y);
+
+ var outWidth = this.graph.container.offsetWidth;
+ var inWidth = this.graph.container.clientWidth;
+
+ if (outWidth > inWidth && pt.x > inWidth + 2 && pt.x <= outWidth)
+ {
+ return true;
+ }
+
+ var outHeight = this.graph.container.offsetHeight;
+ var inHeight = this.graph.container.clientHeight;
+
+ if (outHeight > inHeight && pt.y > inHeight + 2 && pt.y <= outHeight)
+ {
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the graph event dispatch loop for the specified container
+ * and invokes <create> to create the required DOM nodes for the display.
+ */
+mxGraphView.prototype.init = function()
+{
+ this.installListeners();
+
+ // Creates the DOM nodes for the respective display dialect
+ var graph = this.graph;
+
+ if (graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.createSvg();
+ }
+ else if (graph.dialect == mxConstants.DIALECT_VML)
+ {
+ this.createVml();
+ }
+ else
+ {
+ this.createHtml();
+ }
+};
+
+/**
+ * Function: installListeners
+ *
+ * Installs the required listeners in the container.
+ */
+mxGraphView.prototype.installListeners = function()
+{
+ var graph = this.graph;
+ var container = graph.container;
+
+ if (container != null)
+ {
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ // Adds basic listeners for graph event dispatching
+ mxEvent.addListener(container, md,
+ mxUtils.bind(this, function(evt)
+ {
+ // Workaround for touch-based device not transferring
+ // the focus while editing with virtual keyboard
+ if (mxClient.IS_TOUCH && graph.isEditing())
+ {
+ graph.stopEditing(!graph.isInvokesStopCellEditing());
+ }
+
+ // Condition to avoid scrollbar events starting a rubberband
+ // selection
+ if (this.isContainerEvent(evt) && ((!mxClient.IS_IE &&
+ !mxClient.IS_GC && !mxClient.IS_OP && !mxClient.IS_SF) ||
+ !this.isScrollEvent(evt)))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+ mxEvent.addListener(container, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isContainerEvent(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+ mxEvent.addListener(container, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isContainerEvent(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+
+ // Adds listener for double click handling on background
+ mxEvent.addListener(container, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ graph.dblClick(evt);
+ })
+ );
+
+ // Workaround for touch events which started on some DOM node
+ // on top of the container, in which case the cells under the
+ // mouse for the move and up events are not detected.
+ var getState = function(evt)
+ {
+ var state = null;
+
+ // Workaround for touch events which started on some DOM node
+ // on top of the container, in which case the cells under the
+ // mouse for the move and up events are not detected.
+ if (mxClient.IS_TOUCH)
+ {
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+
+ // Dispatches the drop event to the graph which
+ // consumes and executes the source function
+ var pt = mxUtils.convertPoint(container, x, y);
+ state = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+ }
+
+ return state;
+ };
+
+ // Adds basic listeners for graph event dispatching outside of the
+ // container and finishing the handling of a single gesture
+ // Implemented via graph event dispatch loop to avoid duplicate events
+ // in Firefox and Chrome
+ graph.addMouseListener(
+ {
+ mouseDown: function(sender, me)
+ {
+ graph.panningHandler.hideMenu();
+ },
+ mouseMove: function() { },
+ mouseUp: function() { }
+ });
+ mxEvent.addListener(document, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ // Hides the tooltip if mouse is outside container
+ if (graph.tooltipHandler != null &&
+ graph.tooltipHandler.isHideOnHover())
+ {
+ graph.tooltipHandler.hide();
+ }
+
+ if (this.captureDocumentGesture && graph.isMouseDown &&
+ !mxEvent.isConsumed(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, getState(evt)));
+ }
+ })
+ );
+ mxEvent.addListener(document, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.captureDocumentGesture)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+ }
+};
+
+/**
+ * Function: create
+ *
+ * Creates the DOM nodes for the HTML display.
+ */
+mxGraphView.prototype.createHtml = function()
+{
+ var container = this.graph.container;
+
+ if (container != null)
+ {
+ this.canvas = this.createHtmlPane('100%', '100%');
+
+ // Uses minimal size for inner DIVs on Canvas. This is required
+ // for correct event processing in IE. If we have an overlapping
+ // DIV then the events on the cells are only fired for labels.
+ this.backgroundPane = this.createHtmlPane('1px', '1px');
+ this.drawPane = this.createHtmlPane('1px', '1px');
+ this.overlayPane = this.createHtmlPane('1px', '1px');
+
+ this.canvas.appendChild(this.backgroundPane);
+ this.canvas.appendChild(this.drawPane);
+ this.canvas.appendChild(this.overlayPane);
+
+ container.appendChild(this.canvas);
+
+ // Implements minWidth/minHeight in quirks mode
+ if (mxClient.IS_QUIRKS)
+ {
+ var onResize = mxUtils.bind(this, function(evt)
+ {
+ var bounds = this.getGraphBounds();
+ var width = bounds.x + bounds.width + this.graph.border;
+ var height = bounds.y + bounds.height + this.graph.border;
+
+ this.updateHtmlCanvasSize(width, height);
+ });
+
+ mxEvent.addListener(window, 'resize', onResize);
+ }
+ }
+};
+
+/**
+ * Function: updateHtmlCanvasSize
+ *
+ * Updates the size of the HTML canvas.
+ */
+mxGraphView.prototype.updateHtmlCanvasSize = function(width, height)
+{
+ if (this.graph.container != null)
+ {
+ var ow = this.graph.container.offsetWidth;
+ var oh = this.graph.container.offsetHeight;
+
+ if (ow < width)
+ {
+ this.canvas.style.width = width + 'px';
+ }
+ else
+ {
+ this.canvas.style.width = '100%';
+ }
+
+ if (oh < height)
+ {
+ this.canvas.style.height = height + 'px';
+ }
+ else
+ {
+ this.canvas.style.height = '100%';
+ }
+ }
+};
+
+/**
+ * Function: createHtmlPane
+ *
+ * Creates and returns a drawing pane in HTML (DIV).
+ */
+mxGraphView.prototype.createHtmlPane = function(width, height)
+{
+ var pane = document.createElement('DIV');
+
+ if (width != null && height != null)
+ {
+ pane.style.position = 'absolute';
+ pane.style.left = '0px';
+ pane.style.top = '0px';
+
+ pane.style.width = width;
+ pane.style.height = height;
+ }
+ else
+ {
+ pane.style.position = 'relative';
+ }
+
+ return pane;
+};
+
+/**
+ * Function: create
+ *
+ * Creates the DOM nodes for the VML display.
+ */
+mxGraphView.prototype.createVml = function()
+{
+ var container = this.graph.container;
+
+ if (container != null)
+ {
+ var width = container.offsetWidth;
+ var height = container.offsetHeight;
+ this.canvas = this.createVmlPane(width, height);
+
+ this.backgroundPane = this.createVmlPane(width, height);
+ this.drawPane = this.createVmlPane(width, height);
+ this.overlayPane = this.createVmlPane(width, height);
+
+ this.canvas.appendChild(this.backgroundPane);
+ this.canvas.appendChild(this.drawPane);
+ this.canvas.appendChild(this.overlayPane);
+
+ container.appendChild(this.canvas);
+ }
+};
+
+/**
+ * Function: createVmlPane
+ *
+ * Creates a drawing pane in VML (group).
+ */
+mxGraphView.prototype.createVmlPane = function(width, height)
+{
+ var pane = document.createElement('v:group');
+
+ // At this point the width and height are potentially
+ // uninitialized. That's OK.
+ pane.style.position = 'absolute';
+ pane.style.left = '0px';
+ pane.style.top = '0px';
+
+ pane.style.width = width+'px';
+ pane.style.height = height+'px';
+
+ pane.setAttribute('coordsize', width+','+height);
+ pane.setAttribute('coordorigin', '0,0');
+
+ return pane;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the DOM nodes for the SVG display.
+ */
+mxGraphView.prototype.createSvg = function()
+{
+ var container = this.graph.container;
+ this.canvas = document.createElementNS(mxConstants.NS_SVG, 'g');
+
+ // For background image
+ this.backgroundPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ this.canvas.appendChild(this.backgroundPane);
+
+ // Adds two layers (background is early feature)
+ this.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ this.canvas.appendChild(this.drawPane);
+
+ this.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ this.canvas.appendChild(this.overlayPane);
+
+ var root = document.createElementNS(mxConstants.NS_SVG, 'svg');
+ root.style.width = '100%';
+ root.style.height = '100%';
+
+ if (mxClient.IS_IE)
+ {
+ root.style.marginBottom = '-4px';
+ }
+
+ root.appendChild(this.canvas);
+
+ if (container != null)
+ {
+ container.appendChild(root);
+
+ // Workaround for offset of container
+ var style = mxUtils.getCurrentStyle(container);
+
+ if (style.position == 'static')
+ {
+ container.style.position = 'relative';
+ }
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the view and all its resources.
+ */
+mxGraphView.prototype.destroy = function()
+{
+ var root = (this.canvas != null) ? this.canvas.ownerSVGElement : null;
+
+ if (root == null)
+ {
+ root = this.canvas;
+ }
+
+ if (root != null && root.parentNode != null)
+ {
+ this.clear(this.currentRoot, true);
+ mxEvent.removeAllListeners(document);
+ mxEvent.release(this.graph.container);
+ root.parentNode.removeChild(root);
+
+ this.canvas = null;
+ this.backgroundPane = null;
+ this.drawPane = null;
+ this.overlayPane = null;
+ }
+};
+
+/**
+ * Class: mxCurrentRootChange
+ *
+ * Action to change the current root in a view.
+ *
+ * Constructor: mxCurrentRootChange
+ *
+ * Constructs a change of the current root in the given view.
+ */
+function mxCurrentRootChange(view, root)
+{
+ this.view = view;
+ this.root = root;
+ this.previous = root;
+ this.isUp = root == null;
+
+ if (!this.isUp)
+ {
+ var tmp = this.view.currentRoot;
+ var model = this.view.graph.getModel();
+
+ while (tmp != null)
+ {
+ if (tmp == root)
+ {
+ this.isUp = true;
+ break;
+ }
+
+ tmp = model.getParent(tmp);
+ }
+ }
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the current root of the view.
+ */
+mxCurrentRootChange.prototype.execute = function()
+{
+ var tmp = this.view.currentRoot;
+ this.view.currentRoot = this.previous;
+ this.previous = tmp;
+
+ var translate = this.view.graph.getTranslateForRoot(this.view.currentRoot);
+
+ if (translate != null)
+ {
+ this.view.translate = new mxPoint(-translate.x, -translate.y);
+ }
+
+ var name = (this.isUp) ? mxEvent.UP : mxEvent.DOWN;
+ this.view.fireEvent(new mxEventObject(name,
+ 'root', this.view.currentRoot, 'previous', this.previous));
+
+ if (this.isUp)
+ {
+ this.view.clear(this.view.currentRoot, true);
+ this.view.validate();
+ }
+ else
+ {
+ this.view.refresh();
+ }
+
+ this.isUp = !this.isUp;
+};
diff --git a/src/js/view/mxLayoutManager.js b/src/js/view/mxLayoutManager.js
new file mode 100644
index 0000000..ee8ec65
--- /dev/null
+++ b/src/js/view/mxLayoutManager.js
@@ -0,0 +1,375 @@
+/**
+ * $Id: mxLayoutManager.js,v 1.21 2012-01-04 10:01:16 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxLayoutManager
+ *
+ * Implements a layout manager that updates the layout for a given transaction.
+ *
+ * Example:
+ *
+ * (code)
+ * var layoutMgr = new mxLayoutManager(graph);
+ * layoutMgr.getLayout = function(cell)
+ * {
+ * return layout;
+ * };
+ * (end)
+ *
+ * Event: mxEvent.LAYOUT_CELLS
+ *
+ * Fires between begin- and endUpdate after all cells have been layouted in
+ * <layoutCells>. The <code>cells</code> property contains all cells that have
+ * been passed to <layoutCells>.
+ *
+ * Constructor: mxLayoutManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing graph.
+ */
+function mxLayoutManager(graph)
+{
+ // Executes the layout before the changes are dispatched
+ this.undoHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.beforeUndo(evt.getProperty('edit'));
+ }
+ });
+
+ // Notifies the layout of a move operation inside a parent
+ this.moveHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.cellsMoved(evt.getProperty('cells'), evt.getProperty('event'));
+ }
+ });
+
+ this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxLayoutManager.prototype = new mxEventSource();
+mxLayoutManager.prototype.constructor = mxLayoutManager;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxLayoutManager.prototype.graph = null;
+
+/**
+ * Variable: bubbling
+ *
+ * Specifies if the layout should bubble along
+ * the cell hierarchy. Default is true.
+ */
+mxLayoutManager.prototype.bubbling = true;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxLayoutManager.prototype.enabled = true;
+
+/**
+ * Variable: updateHandler
+ *
+ * Holds the function that handles the endUpdate event.
+ */
+mxLayoutManager.prototype.updateHandler = null;
+
+/**
+ * Variable: moveHandler
+ *
+ * Holds the function that handles the move event.
+ */
+mxLayoutManager.prototype.moveHandler = null;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxLayoutManager.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxLayoutManager.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: isBubbling
+ *
+ * Returns true if a layout should bubble, that is, if the parent layout
+ * should be executed whenever a cell layout (layout of the children of
+ * a cell) has been executed. This implementation returns <bubbling>.
+ */
+mxLayoutManager.prototype.isBubbling = function()
+{
+ return this.bubbling;
+};
+
+/**
+ * Function: setBubbling
+ *
+ * Sets <bubbling>.
+ */
+mxLayoutManager.prototype.setBubbling = function(value)
+{
+ this.bubbling = value;
+};
+
+/**
+ * Function: getGraph
+ *
+ * Returns the graph that this layout operates on.
+ */
+mxLayoutManager.prototype.getGraph = function()
+{
+ return this.graph;
+};
+
+/**
+ * Function: setGraph
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxLayoutManager.prototype.setGraph = function(graph)
+{
+ if (this.graph != null)
+ {
+ var model = this.graph.getModel();
+ model.removeListener(this.undoHandler);
+ this.graph.removeListener(this.moveHandler);
+ }
+
+ this.graph = graph;
+
+ if (this.graph != null)
+ {
+ var model = this.graph.getModel();
+ model.addListener(mxEvent.BEFORE_UNDO, this.undoHandler);
+ this.graph.addListener(mxEvent.MOVE_CELLS, this.moveHandler);
+ }
+};
+
+/**
+ * Function: getLayout
+ *
+ * Returns the layout to be executed for the given graph and parent.
+ */
+mxLayoutManager.prototype.getLayout = function(parent)
+{
+ return null;
+};
+
+/**
+ * Function: beforeUndo
+ *
+ * Called from the undoHandler.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxLayoutManager.prototype.beforeUndo = function(undoableEdit)
+{
+ var cells = this.getCellsForChanges(undoableEdit.changes);
+ var model = this.getGraph().getModel();
+
+ // Adds all parent ancestors
+ if (this.isBubbling())
+ {
+ var tmp = model.getParents(cells);
+
+ while (tmp.length > 0)
+ {
+ cells = cells.concat(tmp);
+ tmp = model.getParents(tmp);
+ }
+ }
+
+ this.layoutCells(mxUtils.sortCells(cells, false));
+};
+
+/**
+ * Function: cellsMoved
+ *
+ * Called from the moveHandler.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxLayoutManager.prototype.cellsMoved = function(cells, evt)
+{
+ if (cells != null &&
+ evt != null)
+ {
+ var point = mxUtils.convertPoint(this.getGraph().container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+ var model = this.getGraph().getModel();
+
+ // Checks if a layout exists to take care of the moving
+ for (var i = 0; i < cells.length; i++)
+ {
+ var layout = this.getLayout(model.getParent(cells[i]));
+
+ if (layout != null)
+ {
+ layout.moveCell(cells[i], point.x, point.y);
+ }
+ }
+ }
+};
+
+/**
+ * Function: getCellsForEdit
+ *
+ * Returns the cells to be layouted for the given sequence of changes.
+ */
+mxLayoutManager.prototype.getCellsForChanges = function(changes)
+{
+ var result = [];
+ var hash = new Object();
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ if (change instanceof mxRootChange)
+ {
+ return [];
+ }
+ else
+ {
+ var cells = this.getCellsForChange(change);
+
+ for (var j = 0; j < cells.length; j++)
+ {
+ if (cells[j] != null)
+ {
+ var id = mxCellPath.create(cells[j]);
+
+ if (hash[id] == null)
+ {
+ hash[id] = cells[j];
+ result.push(cells[j]);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getCellsForChange
+ *
+ * Executes all layouts which have been scheduled during the
+ * changes.
+ */
+mxLayoutManager.prototype.getCellsForChange = function(change)
+{
+ var model = this.getGraph().getModel();
+
+ if (change instanceof mxChildChange)
+ {
+ return [change.child, change.previous, model.getParent(change.child)];
+ }
+ else if (change instanceof mxTerminalChange ||
+ change instanceof mxGeometryChange)
+ {
+ return [change.cell, model.getParent(change.cell)];
+ }
+
+ return [];
+};
+
+/**
+ * Function: layoutCells
+ *
+ * Executes all layouts which have been scheduled during the
+ * changes.
+ */
+mxLayoutManager.prototype.layoutCells = function(cells)
+{
+ if (cells.length > 0)
+ {
+ // Invokes the layouts while removing duplicates
+ var model = this.getGraph().getModel();
+
+ model.beginUpdate();
+ try
+ {
+ var last = null;
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] != model.getRoot() &&
+ cells[i] != last)
+ {
+ last = cells[i];
+ this.executeLayout(this.getLayout(last), last);
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, 'cells', cells));
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: executeLayout
+ *
+ * Executes the given layout on the given parent.
+ */
+mxLayoutManager.prototype.executeLayout = function(layout, parent)
+{
+ if (layout != null && parent != null)
+ {
+ layout.execute(parent);
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxLayoutManager.prototype.destroy = function()
+{
+ this.setGraph(null);
+};
diff --git a/src/js/view/mxMultiplicity.js b/src/js/view/mxMultiplicity.js
new file mode 100644
index 0000000..c927d3f
--- /dev/null
+++ b/src/js/view/mxMultiplicity.js
@@ -0,0 +1,257 @@
+/**
+ * $Id: mxMultiplicity.js,v 1.24 2010-11-03 14:52:40 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxMultiplicity
+ *
+ * Defines invalid connections along with the error messages that they produce.
+ * To add or remove rules on a graph, you must add/remove instances of this
+ * class to <mxGraph.multiplicities>.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.multiplicities.push(new mxMultiplicity(
+ * true, 'rectangle', null, null, 0, 2, ['circle'],
+ * 'Only 2 targets allowed',
+ * 'Only circle targets allowed'));
+ * (end)
+ *
+ * Defines a rule where each rectangle must be connected to no more than 2
+ * circles and no other types of targets are allowed.
+ *
+ * Constructor: mxMultiplicity
+ *
+ * Instantiate class mxMultiplicity in order to describe allowed
+ * connections in a graph. Not all constraints can be enforced while
+ * editing, some must be checked at validation time. The <countError> and
+ * <typeError> are treated as resource keys in <mxResources>.
+ *
+ * Parameters:
+ *
+ * source - Boolean indicating if this rule applies to the source or target
+ * terminal.
+ * type - Type of the source or target terminal that this rule applies to.
+ * See <type> for more information.
+ * attr - Optional attribute name to match the source or target terminal.
+ * value - Optional attribute value to match the source or target terminal.
+ * min - Minimum number of edges for this rule. Default is 1.
+ * max - Maximum number of edges for this rule. n means infinite. Default
+ * is n.
+ * validNeighbors - Array of types of the opposite terminal for which this
+ * rule applies.
+ * countError - Error to be displayed for invalid number of edges.
+ * typeError - Error to be displayed for invalid opposite terminals.
+ * validNeighborsAllowed - Optional boolean indicating if the array of
+ * opposite types should be valid or invalid.
+ */
+function mxMultiplicity(source, type, attr, value, min, max,
+ validNeighbors, countError, typeError, validNeighborsAllowed)
+{
+ this.source = source;
+ this.type = type;
+ this.attr = attr;
+ this.value = value;
+ this.min = (min != null) ? min : 0;
+ this.max = (max != null) ? max : 'n';
+ this.validNeighbors = validNeighbors;
+ this.countError = mxResources.get(countError) || countError;
+ this.typeError = mxResources.get(typeError) || typeError;
+ this.validNeighborsAllowed = (validNeighborsAllowed != null) ?
+ validNeighborsAllowed : true;
+};
+
+/**
+ * Variable: type
+ *
+ * Defines the type of the source or target terminal. The type is a string
+ * passed to <mxUtils.isNode> together with the source or target vertex
+ * value as the first argument.
+ */
+mxMultiplicity.prototype.type = null;
+
+/**
+ * Variable: attr
+ *
+ * Optional string that specifies the attributename to be passed to
+ * <mxUtils.isNode> to check if the rule applies to a cell.
+ */
+mxMultiplicity.prototype.attr = null;
+
+/**
+ * Variable: value
+ *
+ * Optional string that specifies the value of the attribute to be passed
+ * to <mxUtils.isNode> to check if the rule applies to a cell.
+ */
+mxMultiplicity.prototype.value = null;
+
+/**
+ * Variable: source
+ *
+ * Boolean that specifies if the rule is applied to the source or target
+ * terminal of an edge.
+ */
+mxMultiplicity.prototype.source = null;
+
+/**
+ * Variable: min
+ *
+ * Defines the minimum number of connections for which this rule applies.
+ * Default is 0.
+ */
+mxMultiplicity.prototype.min = null;
+
+/**
+ * Variable: max
+ *
+ * Defines the maximum number of connections for which this rule applies.
+ * A value of 'n' means unlimited times. Default is 'n'.
+ */
+mxMultiplicity.prototype.max = null;
+
+/**
+ * Variable: validNeighbors
+ *
+ * Holds an array of strings that specify the type of neighbor for which
+ * this rule applies. The strings are used in <mxCell.is> on the opposite
+ * terminal to check if the rule applies to the connection.
+ */
+mxMultiplicity.prototype.validNeighbors = null;
+
+/**
+ * Variable: validNeighborsAllowed
+ *
+ * Boolean indicating if the list of validNeighbors are those that are allowed
+ * for this rule or those that are not allowed for this rule.
+ */
+mxMultiplicity.prototype.validNeighborsAllowed = true;
+
+/**
+ * Variable: countError
+ *
+ * Holds the localized error message to be displayed if the number of
+ * connections for which the rule applies is smaller than <min> or greater
+ * than <max>.
+ */
+mxMultiplicity.prototype.countError = null;
+
+/**
+ * Variable: typeError
+ *
+ * Holds the localized error message to be displayed if the type of the
+ * neighbor for a connection does not match the rule.
+ */
+mxMultiplicity.prototype.typeError = null;
+
+/**
+ * Function: check
+ *
+ * Checks the multiplicity for the given arguments and returns the error
+ * for the given connection or null if the multiplicity does not apply.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph> instance.
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * sourceOut - Number of outgoing edges from the source terminal.
+ * targetIn - Number of incoming edges for the target terminal.
+ */
+mxMultiplicity.prototype.check = function(graph, edge, source, target, sourceOut, targetIn)
+{
+ var error = '';
+
+ if ((this.source && this.checkTerminal(graph, source, edge)) ||
+ (!this.source && this.checkTerminal(graph, target, edge)))
+ {
+ if (this.countError != null &&
+ ((this.source && (this.max == 0 || (sourceOut >= this.max))) ||
+ (!this.source && (this.max == 0 || (targetIn >= this.max)))))
+ {
+ error += this.countError + '\n';
+ }
+
+ if (this.validNeighbors != null && this.typeError != null && this.validNeighbors.length > 0)
+ {
+ var isValid = this.checkNeighbors(graph, edge, source, target);
+
+ if (!isValid)
+ {
+ error += this.typeError + '\n';
+ }
+ }
+ }
+
+ return (error.length > 0) ? error : null;
+};
+
+/**
+ * Function: checkNeighbors
+ *
+ * Checks if there are any valid neighbours in <validNeighbors>. This is only
+ * called if <validNeighbors> is a non-empty array.
+ */
+mxMultiplicity.prototype.checkNeighbors = function(graph, edge, source, target)
+{
+ var sourceValue = graph.model.getValue(source);
+ var targetValue = graph.model.getValue(target);
+ var isValid = !this.validNeighborsAllowed;
+ var valid = this.validNeighbors;
+
+ for (var j = 0; j < valid.length; j++)
+ {
+ if (this.source &&
+ this.checkType(graph, targetValue, valid[j]))
+ {
+ isValid = this.validNeighborsAllowed;
+ break;
+ }
+ else if (!this.source &&
+ this.checkType(graph, sourceValue, valid[j]))
+ {
+ isValid = this.validNeighborsAllowed;
+ break;
+ }
+ }
+
+ return isValid;
+};
+
+/**
+ * Function: checkTerminal
+ *
+ * Checks the given terminal cell and returns true if this rule applies. The
+ * given cell is the source or target of the given edge, depending on
+ * <source>. This implementation uses <checkType> on the terminal's value.
+ */
+mxMultiplicity.prototype.checkTerminal = function(graph, terminal, edge)
+{
+ var value = graph.model.getValue(terminal);
+
+ return this.checkType(graph, value, this.type, this.attr, this.value);
+};
+
+/**
+ * Function: checkType
+ *
+ * Checks the type of the given value.
+ */
+mxMultiplicity.prototype.checkType = function(graph, value, type, attr, attrValue)
+{
+ if (value != null)
+ {
+ if (!isNaN(value.nodeType)) // Checks if value is a DOM node
+ {
+ return mxUtils.isNode(value, type, attr, attrValue);
+ }
+ else
+ {
+ return value == type;
+ }
+ }
+
+ return false;
+};
diff --git a/src/js/view/mxOutline.js b/src/js/view/mxOutline.js
new file mode 100644
index 0000000..a0d6fd3
--- /dev/null
+++ b/src/js/view/mxOutline.js
@@ -0,0 +1,649 @@
+/**
+ * $Id: mxOutline.js,v 1.81 2012-06-20 14:13:37 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxOutline
+ *
+ * Implements an outline (aka overview) for a graph. Set <updateOnPan> to true
+ * to enable updates while the source graph is panning.
+ *
+ * Example:
+ *
+ * (code)
+ * var outline = new mxOutline(graph, div);
+ * (end)
+ *
+ * If the selection border in the outline appears behind the contents of the
+ * graph, then you can use the following code. (This may happen when using a
+ * transparent container for the outline in IE.)
+ *
+ * (code)
+ * mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_EXACT;
+ * (end)
+ *
+ * To move the graph to the top, left corner the following code can be used.
+ *
+ * (code)
+ * var scale = graph.view.scale;
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);
+ * (end)
+ *
+ * To toggle the suspended mode, the following can be used.
+ *
+ * (code)
+ * outline.suspended = !outln.suspended;
+ * if (!outline.suspended)
+ * {
+ * outline.update(true);
+ * }
+ * (end)
+ *
+ * Constructor: mxOutline
+ *
+ * Constructs a new outline for the specified graph inside the given
+ * container.
+ *
+ * Parameters:
+ *
+ * source - <mxGraph> to create the outline for.
+ * container - DOM node that will contain the outline.
+ */
+function mxOutline(source, container)
+{
+ this.source = source;
+
+ if (container != null)
+ {
+ this.init(container);
+ }
+};
+
+/**
+ * Function: source
+ *
+ * Reference to the source <mxGraph>.
+ */
+mxOutline.prototype.source = null;
+
+/**
+ * Function: outline
+ *
+ * Reference to the outline <mxGraph>.
+ */
+mxOutline.prototype.outline = null;
+
+/**
+ * Function: graphRenderHint
+ *
+ * Renderhint to be used for the outline graph. Default is faster.
+ */
+mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxOutline.prototype.enabled = true;
+
+/**
+ * Variable: showViewport
+ *
+ * Specifies a viewport rectangle should be shown. Default is true.
+ */
+mxOutline.prototype.showViewport = true;
+
+/**
+ * Variable: border
+ *
+ * Border to be added at the bottom and right. Default is 10.
+ */
+mxOutline.prototype.border = 10;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies the size of the sizer handler. Default is 8.
+ */
+mxOutline.prototype.sizerSize = 8;
+
+/**
+ * Variable: updateOnPan
+ *
+ * Specifies if <update> should be called for <mxEvent.PAN> in the source
+ * graph. Default is false.
+ */
+mxOutline.prototype.updateOnPan = false;
+
+/**
+ * Variable: sizerImage
+ *
+ * Optional <mxImage> to be used for the sizer. Default is null.
+ */
+mxOutline.prototype.sizerImage = null;
+
+/**
+ * Variable: suspended
+ *
+ * Optional boolean flag to suspend updates. Default is false. This flag will
+ * also suspend repaints of the outline. To toggle this switch, use the
+ * following code.
+ *
+ * (code)
+ * nav.suspended = !nav.suspended;
+ *
+ * if (!nav.suspended)
+ * {
+ * nav.update(true);
+ * }
+ * (end)
+ */
+mxOutline.prototype.suspended = false;
+
+/**
+ * Function: init
+ *
+ * Initializes the outline inside the given container.
+ */
+mxOutline.prototype.init = function(container)
+{
+ this.outline = new mxGraph(container, this.source.getModel(), this.graphRenderHint, this.source.getStylesheet());
+ this.outline.foldingEnabled = false;
+ this.outline.autoScroll = false;
+
+ // Do not repaint when suspended
+ var outlineGraphModelChanged = this.outline.graphModelChanged;
+ this.outline.graphModelChanged = mxUtils.bind(this, function(changes)
+ {
+ if (!this.suspended && this.outline != null)
+ {
+ outlineGraphModelChanged.apply(this.outline, arguments);
+ }
+ });
+
+ // Enables faster painting in SVG
+ if (mxClient.IS_SVG)
+ {
+ var node = this.outline.getView().getCanvas().parentNode;
+ node.setAttribute('shape-rendering', 'optimizeSpeed');
+ node.setAttribute('image-rendering', 'optimizeSpeed');
+ }
+
+ // Hides cursors and labels
+ this.outline.labelsVisible = false;
+ this.outline.setEnabled(false);
+
+ this.updateHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (!this.suspended && !this.active)
+ {
+ this.update();
+ }
+ });
+
+ // Updates the scale of the outline after a change of the main graph
+ this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
+ this.outline.addMouseListener(this);
+
+ // Adds listeners to keep the outline in sync with the source graph
+ var view = this.source.getView();
+ view.addListener(mxEvent.SCALE, this.updateHandler);
+ view.addListener(mxEvent.TRANSLATE, this.updateHandler);
+ view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
+ view.addListener(mxEvent.DOWN, this.updateHandler);
+ view.addListener(mxEvent.UP, this.updateHandler);
+
+ // Updates blue rectangle on scroll
+ mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+
+ this.panHandler = mxUtils.bind(this, function(sender)
+ {
+ if (this.updateOnPan)
+ {
+ this.updateHandler.apply(this, arguments);
+ }
+ });
+ this.source.addListener(mxEvent.PAN, this.panHandler);
+
+ // Refreshes the graph in the outline after a refresh of the main graph
+ this.refreshHandler = mxUtils.bind(this, function(sender)
+ {
+ this.outline.setStylesheet(this.source.getStylesheet());
+ this.outline.refresh();
+ });
+ this.source.addListener(mxEvent.REFRESH, this.refreshHandler);
+
+ // Creates the blue rectangle for the viewport
+ this.bounds = new mxRectangle(0, 0, 0, 0);
+ this.selectionBorder = new mxRectangleShape(this.bounds, null,
+ mxConstants.OUTLINE_COLOR, mxConstants.OUTLINE_STROKEWIDTH);
+ this.selectionBorder.dialect =
+ (this.outline.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ this.selectionBorder.crisp = true;
+ this.selectionBorder.init(this.outline.getView().getOverlayPane());
+ mxEvent.redirectMouseEvents(this.selectionBorder.node, this.outline);
+ this.selectionBorder.node.style.background = '';
+
+ // Creates a small blue rectangle for sizing (sizer handle)
+ this.sizer = this.createSizer();
+ this.sizer.init(this.outline.getView().getOverlayPane());
+
+ if (this.enabled)
+ {
+ this.sizer.node.style.cursor = 'pointer';
+ }
+
+ // Redirects all events from the sizerhandle to the outline
+ mxEvent.addListener(this.sizer.node, (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown',
+ mxUtils.bind(this, function(evt)
+ {
+ this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+ })
+ );
+
+ this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+ this.sizer.node.style.display = this.selectionBorder.node.style.display;
+ this.selectionBorder.node.style.cursor = 'move';
+
+ this.update(false);
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxOutline.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: setZoomEnabled
+ *
+ * Enables or disables the zoom handling by showing or hiding the respective
+ * handle.
+ *
+ * Parameters:
+ *
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setZoomEnabled = function(value)
+{
+ this.sizer.node.style.visibility = (value) ? 'visible' : 'hidden';
+};
+
+/**
+ * Function: refresh
+ *
+ * Invokes <update> and revalidate the outline. This method is deprecated.
+ */
+mxOutline.prototype.refresh = function()
+{
+ this.update(true);
+};
+
+/**
+ * Function: createSizer
+ *
+ * Creates the shape used as the sizer.
+ */
+mxOutline.prototype.createSizer = function()
+{
+ if (this.sizerImage != null)
+ {
+ var sizer = new mxImageShape(new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height), this.sizerImage.src);
+ sizer.dialect = this.outline.dialect;
+
+ return sizer;
+ }
+ else
+ {
+ var sizer = new mxRectangleShape(new mxRectangle(0, 0, this.sizerSize, this.sizerSize),
+ mxConstants.OUTLINE_HANDLE_FILLCOLOR, mxConstants.OUTLINE_HANDLE_STROKECOLOR);
+ sizer.dialect = this.outline.dialect;
+ sizer.crisp = true;
+
+ return sizer;
+ }
+};
+
+/**
+ * Function: getSourceContainerSize
+ *
+ * Returns the size of the source container.
+ */
+mxOutline.prototype.getSourceContainerSize = function()
+{
+ return new mxRectangle(0, 0, this.source.container.scrollWidth, this.source.container.scrollHeight);
+};
+
+/**
+ * Function: getOutlineOffset
+ *
+ * Returns the offset for drawing the outline graph.
+ */
+mxOutline.prototype.getOutlineOffset = function(scale)
+{
+ return null;
+};
+
+/**
+ * Function: update
+ *
+ * Updates the outline.
+ */
+mxOutline.prototype.update = function(revalidate)
+{
+ if (this.source != null)
+ {
+ var sourceScale = this.source.view.scale;
+ var scaledGraphBounds = this.source.getGraphBounds();
+ var unscaledGraphBounds = new mxRectangle(scaledGraphBounds.x / sourceScale + this.source.panDx,
+ scaledGraphBounds.y / sourceScale + this.source.panDy, scaledGraphBounds.width / sourceScale,
+ scaledGraphBounds.height / sourceScale);
+
+ var unscaledFinderBounds = new mxRectangle(0, 0,
+ this.source.container.clientWidth / sourceScale,
+ this.source.container.clientHeight / sourceScale);
+
+ var union = unscaledGraphBounds.clone();
+ union.add(unscaledFinderBounds);
+
+ // Zooms to the scrollable area if that is bigger than the graph
+ var size = this.getSourceContainerSize();
+ var completeWidth = Math.max(size.width / sourceScale, union.width);
+ var completeHeight = Math.max(size.height / sourceScale, union.height);
+
+ var availableWidth = Math.max(0, this.outline.container.clientWidth - this.border);
+ var availableHeight = Math.max(0, this.outline.container.clientHeight - this.border);
+
+ var outlineScale = Math.min(availableWidth / completeWidth, availableHeight / completeHeight);
+ var scale = outlineScale;
+
+ if (scale > 0)
+ {
+ if (this.outline.getView().scale != scale)
+ {
+ this.outline.getView().scale = scale;
+ revalidate = true;
+ }
+
+ var navView = this.outline.getView();
+
+ if (navView.currentRoot != this.source.getView().currentRoot)
+ {
+ navView.setCurrentRoot(this.source.getView().currentRoot);
+ }
+
+ var t = this.source.view.translate;
+ var tx = t.x + this.source.panDx;
+ var ty = t.y + this.source.panDy;
+
+ var off = this.getOutlineOffset(scale);
+
+ if (off != null)
+ {
+ tx += off.x;
+ ty += off.y;
+ }
+
+ if (unscaledGraphBounds.x < 0)
+ {
+ tx = tx - unscaledGraphBounds.x;
+ }
+ if (unscaledGraphBounds.y < 0)
+ {
+ ty = ty - unscaledGraphBounds.y;
+ }
+
+ if (navView.translate.x != tx || navView.translate.y != ty)
+ {
+ navView.translate.x = tx;
+ navView.translate.y = ty;
+ revalidate = true;
+ }
+
+ // Prepares local variables for computations
+ var t2 = navView.translate;
+ scale = this.source.getView().scale;
+ var scale2 = scale / navView.scale;
+ var scale3 = 1.0 / navView.scale;
+ var container = this.source.container;
+
+ // Updates the bounds of the viewrect in the navigation
+ this.bounds = new mxRectangle(
+ (t2.x - t.x - this.source.panDx) / scale3,
+ (t2.y - t.y - this.source.panDy) / scale3,
+ (container.clientWidth / scale2),
+ (container.clientHeight / scale2));
+
+ // Adds the scrollbar offset to the finder
+ this.bounds.x += this.source.container.scrollLeft * navView.scale / scale;
+ this.bounds.y += this.source.container.scrollTop * navView.scale / scale;
+
+ var b = this.selectionBorder.bounds;
+
+ if (b.x != this.bounds.x || b.y != this.bounds.y || b.width != this.bounds.width || b.height != this.bounds.height)
+ {
+ this.selectionBorder.bounds = this.bounds;
+ this.selectionBorder.redraw();
+ }
+
+ // Updates the bounds of the zoom handle at the bottom right
+ var b = this.sizer.bounds;
+ var b2 = new mxRectangle(this.bounds.x + this.bounds.width - b.width / 2,
+ this.bounds.y + this.bounds.height - b.height / 2, b.width, b.height);
+
+ if (b.x != b2.x || b.y != b2.y || b.width != b2.width || b.height != b2.height)
+ {
+ this.sizer.bounds = b2;
+
+ // Avoids update of visibility in redraw for VML
+ if (this.sizer.node.style.visibility != 'hidden')
+ {
+ this.sizer.redraw();
+ }
+ }
+
+ if (revalidate)
+ {
+ this.outline.view.revalidate();
+ }
+ }
+ }
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by starting a translation or zoom.
+ */
+mxOutline.prototype.mouseDown = function(sender, me)
+{
+ if (this.enabled && this.showViewport)
+ {
+ this.zoom = me.isSource(this.sizer);
+ this.startX = me.getX();
+ this.startY = me.getY();
+ this.active = true;
+
+ if (this.source.useScrollbarsForPanning &&
+ mxUtils.hasScrollbars(this.source.container))
+ {
+ this.dx0 = this.source.container.scrollLeft;
+ this.dy0 = this.source.container.scrollTop;
+ }
+ else
+ {
+ this.dx0 = 0;
+ this.dy0 = 0;
+ }
+ }
+
+ me.consume();
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by previewing the viewrect in <graph> and updating the
+ * rectangle that represents the viewrect in the outline.
+ */
+mxOutline.prototype.mouseMove = function(sender, me)
+{
+ if (this.active)
+ {
+ this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+ this.sizer.node.style.display = this.selectionBorder.node.style.display;
+
+ var dx = me.getX() - this.startX;
+ var dy = me.getY() - this.startY;
+ var bounds = null;
+
+ if (!this.zoom)
+ {
+ // Previews the panning on the source graph
+ var scale = this.outline.getView().scale;
+ bounds = new mxRectangle(this.bounds.x + dx,
+ this.bounds.y + dy, this.bounds.width, this.bounds.height);
+ this.selectionBorder.bounds = bounds;
+ this.selectionBorder.redraw();
+ dx /= scale;
+ dx *= this.source.getView().scale;
+ dy /= scale;
+ dy *= this.source.getView().scale;
+ this.source.panGraph(-dx - this.dx0, -dy - this.dy0);
+ }
+ else
+ {
+ // Does *not* preview zooming on the source graph
+ var container = this.source.container;
+ var viewRatio = container.clientWidth / container.clientHeight;
+ dy = dx / viewRatio;
+ bounds = new mxRectangle(this.bounds.x,
+ this.bounds.y,
+ Math.max(1, this.bounds.width + dx),
+ Math.max(1, this.bounds.height + dy));
+ this.selectionBorder.bounds = bounds;
+ this.selectionBorder.redraw();
+ }
+
+ // Updates the zoom handle
+ var b = this.sizer.bounds;
+ this.sizer.bounds = new mxRectangle(
+ bounds.x + bounds.width - b.width / 2,
+ bounds.y + bounds.height - b.height / 2,
+ b.width, b.height);
+
+ // Avoids update of visibility in redraw for VML
+ if (this.sizer.node.style.visibility != 'hidden')
+ {
+ this.sizer.redraw();
+ }
+
+ me.consume();
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by applying the translation or zoom to <graph>.
+ */
+mxOutline.prototype.mouseUp = function(sender, me)
+{
+ if (this.active)
+ {
+ var dx = me.getX() - this.startX;
+ var dy = me.getY() - this.startY;
+
+ if (Math.abs(dx) > 0 || Math.abs(dy) > 0)
+ {
+ if (!this.zoom)
+ {
+ // Applies the new translation if the source
+ // has no scrollbars
+ if (!this.source.useScrollbarsForPanning ||
+ !mxUtils.hasScrollbars(this.source.container))
+ {
+ this.source.panGraph(0, 0);
+ dx /= this.outline.getView().scale;
+ dy /= this.outline.getView().scale;
+ var t = this.source.getView().translate;
+ this.source.getView().setTranslate(t.x - dx, t.y - dy);
+ }
+ }
+ else
+ {
+ // Applies the new zoom
+ var w = this.selectionBorder.bounds.width;
+ var scale = this.source.getView().scale;
+ this.source.zoomTo(scale - (dx * scale) / w, false);
+ }
+
+ this.update();
+ me.consume();
+ }
+
+ // Resets the state of the handler
+ this.index = null;
+ this.active = false;
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroy this outline and removes all listeners from <source>.
+ */
+mxOutline.prototype.destroy = function()
+{
+ if (this.source != null)
+ {
+ this.source.removeListener(this.panHandler);
+ this.source.removeListener(this.refreshHandler);
+ this.source.getModel().removeListener(this.updateHandler);
+ this.source.getView().removeListener(this.updateHandler);
+ mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+ this.source = null;
+ }
+
+ if (this.outline != null)
+ {
+ this.outline.removeMouseListener(this);
+ this.outline.destroy();
+ this.outline = null;
+ }
+
+ if (this.selectionBorder != null)
+ {
+ this.selectionBorder.destroy();
+ this.selectionBorder = null;
+ }
+
+ if (this.sizer != null)
+ {
+ this.sizer.destroy();
+ this.sizer = null;
+ }
+};
diff --git a/src/js/view/mxPerimeter.js b/src/js/view/mxPerimeter.js
new file mode 100644
index 0000000..7aaa187
--- /dev/null
+++ b/src/js/view/mxPerimeter.js
@@ -0,0 +1,484 @@
+/**
+ * $Id: mxPerimeter.js,v 1.28 2012-01-11 09:06:56 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxPerimeter =
+{
+ /**
+ * Class: mxPerimeter
+ *
+ * Provides various perimeter functions to be used in a style
+ * as the value of <mxConstants.STYLE_PERIMETER>. Perimeters for
+ * rectangle, circle, rhombus and triangle are available.
+ *
+ * Example:
+ *
+ * (code)
+ * <add as="perimeter">mxPerimeter.RightAngleRectanglePerimeter</add>
+ * (end)
+ *
+ * Or programmatically:
+ *
+ * (code)
+ * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+ * (end)
+ *
+ * When adding new perimeter functions, it is recommended to use the
+ * mxPerimeter-namespace as follows:
+ *
+ * (code)
+ * mxPerimeter.CustomPerimeter = function (bounds, vertex, next, orthogonal)
+ * {
+ * var x = 0; // Calculate x-coordinate
+ * var y = 0; // Calculate y-coordainte
+ *
+ * return new mxPoint(x, y);
+ * }
+ * (end)
+ *
+ * The new perimeter should then be registered in the <mxStyleRegistry> as follows:
+ * (code)
+ * mxStyleRegistry.putValue('customPerimeter', mxPerimeter.CustomPerimeter);
+ * (end)
+ *
+ * The custom perimeter above can now be used in a specific vertex as follows:
+ *
+ * (code)
+ * model.setStyle(vertex, 'perimeter=customPerimeter');
+ * (end)
+ *
+ * Note that the key of the <mxStyleRegistry> entry for the function should
+ * be used in string values, unless <mxGraphView.allowEval> is true, in
+ * which case you can also use mxPerimeter.CustomPerimeter for the value in
+ * the cell style above.
+ *
+ * Or it can be used for all vertices in the graph as follows:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.CustomPerimeter;
+ * (end)
+ *
+ * Note that the object can be used directly when programmatically setting
+ * the value, but the key in the <mxStyleRegistry> should be used when
+ * setting the value via a key, value pair in a cell style.
+ *
+ * The parameters are explained in <RectanglePerimeter>.
+ *
+ * Function: RectanglePerimeter
+ *
+ * Describes a rectangular perimeter for the given bounds.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that represents the absolute bounds of the
+ * vertex.
+ * vertex - <mxCellState> that represents the vertex.
+ * next - <mxPoint> that represents the nearest neighbour point on the
+ * given edge.
+ * orthogonal - Boolean that specifies if the orthogonal projection onto
+ * the perimeter should be returned. If this is false then the intersection
+ * of the perimeter and the line between the next and the center point is
+ * returned.
+ */
+ RectanglePerimeter: function (bounds, vertex, next, orthogonal)
+ {
+ var cx = bounds.getCenterX();
+ var cy = bounds.getCenterY();
+ var dx = next.x - cx;
+ var dy = next.y - cy;
+ var alpha = Math.atan2(dy, dx);
+ var p = new mxPoint(0, 0);
+ var pi = Math.PI;
+ var pi2 = Math.PI/2;
+ var beta = pi2 - alpha;
+ var t = Math.atan2(bounds.height, bounds.width);
+
+ if (alpha < -pi + t || alpha > pi - t)
+ {
+ // Left edge
+ p.x = bounds.x;
+ p.y = cy - bounds.width * Math.tan(alpha) / 2;
+ }
+ else if (alpha < -t)
+ {
+ // Top Edge
+ p.y = bounds.y;
+ p.x = cx - bounds.height * Math.tan(beta) / 2;
+ }
+ else if (alpha < t)
+ {
+ // Right Edge
+ p.x = bounds.x + bounds.width;
+ p.y = cy + bounds.width * Math.tan(alpha) / 2;
+ }
+ else
+ {
+ // Bottom Edge
+ p.y = bounds.y + bounds.height;
+ p.x = cx + bounds.height * Math.tan(beta) / 2;
+ }
+
+ if (orthogonal)
+ {
+ if (next.x >= bounds.x &&
+ next.x <= bounds.x + bounds.width)
+ {
+ p.x = next.x;
+ }
+ else if (next.y >= bounds.y &&
+ next.y <= bounds.y + bounds.height)
+ {
+ p.y = next.y;
+ }
+ if (next.x < bounds.x)
+ {
+ p.x = bounds.x;
+ }
+ else if (next.x > bounds.x + bounds.width)
+ {
+ p.x = bounds.x + bounds.width;
+ }
+ if (next.y < bounds.y)
+ {
+ p.y = bounds.y;
+ }
+ else if (next.y > bounds.y + bounds.height)
+ {
+ p.y = bounds.y + bounds.height;
+ }
+ }
+
+ return p;
+ },
+
+ /**
+ * Function: EllipsePerimeter
+ *
+ * Describes an elliptic perimeter. See <RectanglePerimeter>
+ * for a description of the parameters.
+ */
+ EllipsePerimeter: function (bounds, vertex, next, orthogonal)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var a = bounds.width / 2;
+ var b = bounds.height / 2;
+ var cx = x + a;
+ var cy = y + b;
+ var px = next.x;
+ var py = next.y;
+
+ // Calculates straight line equation through
+ // point and ellipse center y = d * x + h
+ var dx = parseInt(px - cx);
+ var dy = parseInt(py - cy);
+
+ if (dx == 0 && dy != 0)
+ {
+ return new mxPoint(cx, cy + b * dy / Math.abs(dy));
+ }
+ else if (dx == 0 && dy == 0)
+ {
+ return new mxPoint(px, py);
+ }
+
+ if (orthogonal)
+ {
+ if (py >= y && py <= y + bounds.height)
+ {
+ var ty = py - cy;
+ var tx = Math.sqrt(a*a*(1-(ty*ty)/(b*b))) || 0;
+
+ if (px <= x)
+ {
+ tx = -tx;
+ }
+
+ return new mxPoint(cx+tx, py);
+ }
+
+ if (px >= x && px <= x + bounds.width)
+ {
+ var tx = px - cx;
+ var ty = Math.sqrt(b*b*(1-(tx*tx)/(a*a))) || 0;
+
+ if (py <= y)
+ {
+ ty = -ty;
+ }
+
+ return new mxPoint(px, cy+ty);
+ }
+ }
+
+ // Calculates intersection
+ var d = dy / dx;
+ var h = cy - d * cx;
+ var e = a * a * d * d + b * b;
+ var f = -2 * cx * e;
+ var g = a * a * d * d * cx * cx +
+ b * b * cx * cx -
+ a * a * b * b;
+ var det = Math.sqrt(f * f - 4 * e * g);
+
+ // Two solutions (perimeter points)
+ var xout1 = (-f + det) / (2 * e);
+ var xout2 = (-f - det) / (2 * e);
+ var yout1 = d * xout1 + h;
+ var yout2 = d * xout2 + h;
+ var dist1 = Math.sqrt(Math.pow((xout1 - px), 2)
+ + Math.pow((yout1 - py), 2));
+ var dist2 = Math.sqrt(Math.pow((xout2 - px), 2)
+ + Math.pow((yout2 - py), 2));
+
+ // Correct solution
+ var xout = 0;
+ var yout = 0;
+
+ if (dist1 < dist2)
+ {
+ xout = xout1;
+ yout = yout1;
+ }
+ else
+ {
+ xout = xout2;
+ yout = yout2;
+ }
+
+ return new mxPoint(xout, yout);
+ },
+
+ /**
+ * Function: RhombusPerimeter
+ *
+ * Describes a rhombus (aka diamond) perimeter. See <RectanglePerimeter>
+ * for a description of the parameters.
+ */
+ RhombusPerimeter: function (bounds, vertex, next, orthogonal)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+
+ var cx = x + w / 2;
+ var cy = y + h / 2;
+
+ var px = next.x;
+ var py = next.y;
+
+ // Special case for intersecting the diamond's corners
+ if (cx == px)
+ {
+ if (cy > py)
+ {
+ return new mxPoint(cx, y); // top
+ }
+ else
+ {
+ return new mxPoint(cx, y + h); // bottom
+ }
+ }
+ else if (cy == py)
+ {
+ if (cx > px)
+ {
+ return new mxPoint(x, cy); // left
+ }
+ else
+ {
+ return new mxPoint(x + w, cy); // right
+ }
+ }
+
+ var tx = cx;
+ var ty = cy;
+
+ if (orthogonal)
+ {
+ if (px >= x && px <= x + w)
+ {
+ tx = px;
+ }
+ else if (py >= y && py <= y + h)
+ {
+ ty = py;
+ }
+ }
+
+ // In which quadrant will the intersection be?
+ // set the slope and offset of the border line accordingly
+ if (px < cx)
+ {
+ if (py < cy)
+ {
+ return mxUtils.intersection(px, py, tx, ty, cx, y, x, cy);
+ }
+ else
+ {
+ return mxUtils.intersection(px, py, tx, ty, cx, y + h, x, cy);
+ }
+ }
+ else if (py < cy)
+ {
+ return mxUtils.intersection(px, py, tx, ty, cx, y, x + w, cy);
+ }
+ else
+ {
+ return mxUtils.intersection(px, py, tx, ty, cx, y + h, x + w, cy);
+ }
+ },
+
+ /**
+ * Function: TrianglePerimeter
+ *
+ * Describes a triangle perimeter. See <RectanglePerimeter>
+ * for a description of the parameters.
+ */
+ TrianglePerimeter: function (bounds, vertex, next, orthogonal)
+ {
+ var direction = (vertex != null) ?
+ vertex.style[mxConstants.STYLE_DIRECTION] : null;
+ var vertical = direction == mxConstants.DIRECTION_NORTH ||
+ direction == mxConstants.DIRECTION_SOUTH;
+
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+
+ var cx = x + w / 2;
+ var cy = y + h / 2;
+
+ var start = new mxPoint(x, y);
+ var corner = new mxPoint(x + w, cy);
+ var end = new mxPoint(x, y + h);
+
+ if (direction == mxConstants.DIRECTION_NORTH)
+ {
+ start = end;
+ corner = new mxPoint(cx, y);
+ end = new mxPoint(x + w, y + h);
+ }
+ else if (direction == mxConstants.DIRECTION_SOUTH)
+ {
+ corner = new mxPoint(cx, y + h);
+ end = new mxPoint(x + w, y);
+ }
+ else if (direction == mxConstants.DIRECTION_WEST)
+ {
+ start = new mxPoint(x + w, y);
+ corner = new mxPoint(x, cy);
+ end = new mxPoint(x + w, y + h);
+ }
+
+ var dx = next.x - cx;
+ var dy = next.y - cy;
+
+ var alpha = (vertical) ? Math.atan2(dx, dy) : Math.atan2(dy, dx);
+ var t = (vertical) ? Math.atan2(w, h) : Math.atan2(h, w);
+
+ var base = false;
+
+ if (direction == mxConstants.DIRECTION_NORTH ||
+ direction == mxConstants.DIRECTION_WEST)
+ {
+ base = alpha > -t && alpha < t;
+ }
+ else
+ {
+ base = alpha < -Math.PI + t || alpha > Math.PI - t;
+ }
+
+ var result = null;
+
+ if (base)
+ {
+ if (orthogonal && ((vertical && next.x >= start.x && next.x <= end.x) ||
+ (!vertical && next.y >= start.y && next.y <= end.y)))
+ {
+ if (vertical)
+ {
+ result = new mxPoint(next.x, start.y);
+ }
+ else
+ {
+ result = new mxPoint(start.x, next.y);
+ }
+ }
+ else
+ {
+ if (direction == mxConstants.DIRECTION_NORTH)
+ {
+ result = new mxPoint(x + w / 2 + h * Math.tan(alpha) / 2,
+ y + h);
+ }
+ else if (direction == mxConstants.DIRECTION_SOUTH)
+ {
+ result = new mxPoint(x + w / 2 - h * Math.tan(alpha) / 2,
+ y);
+ }
+ else if (direction == mxConstants.DIRECTION_WEST)
+ {
+ result = new mxPoint(x + w, y + h / 2 +
+ w * Math.tan(alpha) / 2);
+ }
+ else
+ {
+ result = new mxPoint(x, y + h / 2 -
+ w * Math.tan(alpha) / 2);
+ }
+ }
+ }
+ else
+ {
+ if (orthogonal)
+ {
+ var pt = new mxPoint(cx, cy);
+
+ if (next.y >= y && next.y <= y + h)
+ {
+ pt.x = (vertical) ? cx : (
+ (direction == mxConstants.DIRECTION_WEST) ?
+ x + w : x);
+ pt.y = next.y;
+ }
+ else if (next.x >= x && next.x <= x + w)
+ {
+ pt.x = next.x;
+ pt.y = (!vertical) ? cy : (
+ (direction == mxConstants.DIRECTION_NORTH) ?
+ y + h : y);
+ }
+
+ // Compute angle
+ dx = next.x - pt.x;
+ dy = next.y - pt.y;
+
+ cx = pt.x;
+ cy = pt.y;
+ }
+
+ if ((vertical && next.x <= x + w / 2) ||
+ (!vertical && next.y <= y + h / 2))
+ {
+ result = mxUtils.intersection(next.x, next.y, cx, cy,
+ start.x, start.y, corner.x, corner.y);
+ }
+ else
+ {
+ result = mxUtils.intersection(next.x, next.y, cx, cy,
+ corner.x, corner.y, end.x, end.y);
+ }
+ }
+
+ if (result == null)
+ {
+ result = new mxPoint(cx, cy);
+ }
+
+ return result;
+ }
+};
diff --git a/src/js/view/mxPrintPreview.js b/src/js/view/mxPrintPreview.js
new file mode 100644
index 0000000..24a65e6
--- /dev/null
+++ b/src/js/view/mxPrintPreview.js
@@ -0,0 +1,801 @@
+/**
+ * $Id: mxPrintPreview.js,v 1.61 2012-05-15 14:12:40 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPrintPreview
+ *
+ * Implements printing of a diagram across multiple pages. The following opens
+ * a print preview for an existing graph:
+ *
+ * (code)
+ * var preview = new mxPrintPreview(graph);
+ * preview.open();
+ * (end)
+ *
+ * Use <mxUtils.getScaleForPageCount> as follows in order to print the graph
+ * across a given number of pages:
+ *
+ * (code)
+ * var pageCount = mxUtils.prompt('Enter page count', '1');
+ *
+ * if (pageCount != null)
+ * {
+ * var scale = mxUtils.getScaleForPageCount(pageCount, graph);
+ * var preview = new mxPrintPreview(graph, scale);
+ * preview.open();
+ * }
+ * (end)
+ *
+ * Headers:
+ *
+ * Apart from setting the title argument in the mxPrintPreview constructor you
+ * can override <renderPage> as follows to add a header to any page:
+ *
+ * (code)
+ * var oldRenderPage = mxPrintPreview.prototype.renderPage;
+ * mxPrintPreview.prototype.renderPage = function(w, h, dx, dy, scale, pageNumber)
+ * {
+ * var div = oldRenderPage.apply(this, arguments);
+ *
+ * var header = document.createElement('div');
+ * header.style.position = 'absolute';
+ * header.style.top = '0px';
+ * header.style.width = '100%';
+ * header.style.textAlign = 'right';
+ * mxUtils.write(header, 'Your header here - Page ' + pageNumber + ' / ' + this.pageCount);
+ * div.firstChild.appendChild(header);
+ *
+ * return div;
+ * };
+ * (end)
+ *
+ * Page Format:
+ *
+ * For landscape printing, use <mxConstants.PAGE_FORMAT_A4_LANDSCAPE> as
+ * the pageFormat in <mxUtils.getScaleForPageCount> and <mxPrintPreview>.
+ * Keep in mind that one can not set the defaults for the print dialog
+ * of the operating system from JavaScript so the user must manually choose
+ * a page format that matches this setting.
+ *
+ * You can try passing the following CSS directive to <open> to set the
+ * page format in the print dialog to landscape. However, this CSS
+ * directive seems to be ignored in most major browsers, including IE.
+ *
+ * (code)
+ * @page {
+ * size: landscape;
+ * }
+ * (end)
+ *
+ * Note that the print preview behaves differently in IE when used from the
+ * filesystem or via HTTP so printing should always be tested via HTTP.
+ *
+ * If you are using a DOCTYPE in the source page you can override <getDoctype>
+ * and provide the same DOCTYPE for the print preview if required. Here is
+ * an example for IE8 standards mode.
+ *
+ * (code)
+ * var preview = new mxPrintPreview(graph);
+ * preview.getDoctype = function()
+ * {
+ * return '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=8" ><![endif]-->';
+ * };
+ * preview.open();
+ * (end)
+ *
+ * Constructor: mxPrintPreview
+ *
+ * Constructs a new print preview for the given parameters.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> to be previewed.
+ * scale - Optional scale of the output. Default is 1 / <mxGraph.pageScale>.
+ * border - Border in pixels along each side of every page. Note that the
+ * actual print function in the browser will add another border for
+ * printing.
+ * pageFormat - <mxRectangle> that specifies the page format (in pixels).
+ * This should match the page format of the printer. Default uses the
+ * <mxGraph.pageFormat> of the given graph.
+ * x0 - Optional left offset of the output. Default is 0.
+ * y0 - Optional top offset of the output. Default is 0.
+ * borderColor - Optional color of the page border. Default is no border.
+ * Note that a border is sometimes useful to highlight the printed page
+ * border in the print preview of the browser.
+ * title - Optional string that is used for the window title. Default
+ * is 'Printer-friendly version'.
+ * pageSelector - Optional boolean that specifies if the page selector
+ * should appear in the window with the print preview. Default is true.
+ */
+function mxPrintPreview(graph, scale, pageFormat, border, x0, y0, borderColor, title, pageSelector)
+{
+ this.graph = graph;
+ this.scale = (scale != null) ? scale : 1 / graph.pageScale;
+ this.border = (border != null) ? border : 0;
+ this.pageFormat = (pageFormat != null) ? pageFormat : graph.pageFormat;
+ this.title = (title != null) ? title : 'Printer-friendly version';
+ this.x0 = (x0 != null) ? x0 : 0;
+ this.y0 = (y0 != null) ? y0 : 0;
+ this.borderColor = borderColor;
+ this.pageSelector = (pageSelector != null) ? pageSelector : true;
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the <mxGraph> that should be previewed.
+ */
+mxPrintPreview.prototype.graph = null;
+
+/**
+ * Variable: pageFormat
+ *
+ * Holds the <mxRectangle> that defines the page format.
+ */
+mxPrintPreview.prototype.pageFormat = null;
+
+/**
+ * Variable: scale
+ *
+ * Holds the scale of the print preview.
+ */
+mxPrintPreview.prototype.scale = null;
+
+/**
+ * Variable: border
+ *
+ * The border inset around each side of every page in the preview. This is set
+ * to 0 if autoOrigin is false.
+ */
+mxPrintPreview.prototype.border = 0;
+
+/**
+/**
+ * Variable: x0
+ *
+ * Holds the horizontal offset of the output.
+ */
+mxPrintPreview.prototype.x0 = 0;
+
+/**
+ * Variable: y0
+ *
+ * Holds the vertical offset of the output.
+ */
+mxPrintPreview.prototype.y0 = 0;
+
+/**
+ * Variable: autoOrigin
+ *
+ * Specifies if the origin should be automatically computed based on the top,
+ * left corner of the actual diagram contents. If this is set to false then the
+ * values for <x0> and <y0> will be overridden in <open>. Default is true.
+ */
+mxPrintPreview.prototype.autoOrigin = true;
+
+/**
+ * Variable: printOverlays
+ *
+ * Specifies if overlays should be printed. Default is false.
+ */
+mxPrintPreview.prototype.printOverlays = false;
+
+/**
+ * Variable: borderColor
+ *
+ * Holds the color value for the page border.
+ */
+mxPrintPreview.prototype.borderColor = null;
+
+/**
+ * Variable: title
+ *
+ * Holds the title of the preview window.
+ */
+mxPrintPreview.prototype.title = null;
+
+/**
+ * Variable: pageSelector
+ *
+ * Boolean that specifies if the page selector should be
+ * displayed. Default is true.
+ */
+mxPrintPreview.prototype.pageSelector = null;
+
+/**
+ * Variable: wnd
+ *
+ * Reference to the preview window.
+ */
+mxPrintPreview.prototype.wnd = null;
+
+/**
+ * Variable: pageCount
+ *
+ * Holds the actual number of pages in the preview.
+ */
+mxPrintPreview.prototype.pageCount = 0;
+
+/**
+ * Function: getWindow
+ *
+ * Returns <wnd>.
+ */
+mxPrintPreview.prototype.getWindow = function()
+{
+ return this.wnd;
+};
+
+/**
+ * Function: getDocType
+ *
+ * Returns the string that should go before the HTML tag in the print preview
+ * page. This implementation returns an empty string.
+ */
+mxPrintPreview.prototype.getDoctype = function()
+{
+ return '';
+};
+
+/**
+ * Function: open
+ *
+ * Shows the print preview window. The window is created here if it does
+ * not exist.
+ *
+ * Parameters:
+ *
+ * css - Optional CSS string to be used in the new page's head section.
+ */
+mxPrintPreview.prototype.open = function(css)
+{
+ // Closing the window while the page is being rendered may cause an
+ // exception in IE. This and any other exceptions are simply ignored.
+ var previousInitializeOverlay = this.graph.cellRenderer.initializeOverlay;
+ var div = null;
+
+ try
+ {
+ // Temporarily overrides the method to redirect rendering of overlays
+ // to the draw pane so that they are visible in the printout
+ if (this.printOverlays)
+ {
+ this.graph.cellRenderer.initializeOverlay = function(state, overlay)
+ {
+ overlay.init(state.view.getDrawPane());
+ };
+ }
+
+ if (this.wnd == null)
+ {
+ this.wnd = window.open();
+ var doc = this.wnd.document;
+ var dt = this.getDoctype();
+
+ if (dt != null && dt.length > 0)
+ {
+ doc.writeln(dt);
+ }
+
+ doc.writeln('<html>');
+ doc.writeln('<head>');
+ this.writeHead(doc, css);
+ doc.writeln('</head>');
+ doc.writeln('<body class="mxPage">');
+
+ // Adds all required stylesheets and namespaces
+ mxClient.link('stylesheet', mxClient.basePath + '/css/common.css', doc);
+
+ if (mxClient.IS_IE && document.documentMode != 9)
+ {
+ doc.namespaces.add('v', 'urn:schemas-microsoft-com:vml');
+ doc.namespaces.add('o', 'urn:schemas-microsoft-com:office:office');
+ var ss = doc.createStyleSheet();
+ ss.cssText = 'v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}';
+ mxClient.link('stylesheet', mxClient.basePath + '/css/explorer.css', doc);
+ }
+
+ // Computes the horizontal and vertical page count
+ var bounds = this.graph.getGraphBounds().clone();
+ var currentScale = this.graph.getView().getScale();
+ var sc = currentScale / this.scale;
+ var tr = this.graph.getView().getTranslate();
+
+ // Uses the absolute origin with no offset for all printing
+ if (!this.autoOrigin)
+ {
+ this.x0 = -tr.x * this.scale;
+ this.y0 = -tr.y * this.scale;
+ bounds.width += bounds.x;
+ bounds.height += bounds.y;
+ bounds.x = 0;
+ bounds.y = 0;
+ this.border = 0;
+ }
+
+ // Compute the unscaled, untranslated bounds to find
+ // the number of vertical and horizontal pages
+ bounds.width /= sc;
+ bounds.height /= sc;
+
+ // Store the available page area
+ var availableWidth = this.pageFormat.width - (this.border * 2);
+ var availableHeight = this.pageFormat.height - (this.border * 2);
+
+ var hpages = Math.max(1, Math.ceil((bounds.width + this.x0) / availableWidth));
+ var vpages = Math.max(1, Math.ceil((bounds.height + this.y0) / availableHeight));
+ this.pageCount = hpages * vpages;
+
+ var writePageSelector = mxUtils.bind(this, function()
+ {
+ if (this.pageSelector && (vpages > 1 || hpages > 1))
+ {
+ var table = this.createPageSelector(vpages, hpages);
+ doc.body.appendChild(table);
+
+ // Workaround for position: fixed which isn't working in IE
+ if (mxClient.IS_IE)
+ {
+ table.style.position = 'absolute';
+
+ var update = function()
+ {
+ table.style.top = (doc.body.scrollTop + 10) + 'px';
+ };
+
+ mxEvent.addListener(this.wnd, 'scroll', function(evt)
+ {
+ update();
+ });
+
+ mxEvent.addListener(this.wnd, 'resize', function(evt)
+ {
+ update();
+ });
+ }
+ }
+ });
+
+ // Stores pages for later retrieval
+ var pages = null;
+
+ // Workaround for aspect of image shapes updated asynchronously
+ // in VML so we need to fetch the markup of the DIV containing
+ // the image after the udpate of the style of the DOM node.
+ // LATER: Allow document for display markup to be customized.
+ if (mxClient.IS_IE && document.documentMode != 9)
+ {
+ pages = [];
+
+ // Overrides asynchronous loading of images for fetching HTML markup
+ var waitCounter = 0;
+ var isDone = false;
+
+ var mxImageShapeScheduleUpdateAspect = mxImageShape.prototype.scheduleUpdateAspect;
+ var mxImageShapeUpdateAspect = mxImageShape.prototype.updateAspect;
+
+ var writePages = function()
+ {
+ if (isDone && waitCounter == 0)
+ {
+ // Restores previous implementations
+ mxImageShape.prototype.scheduleUpdateAspect = mxImageShapeScheduleUpdateAspect;
+ mxImageShape.prototype.updateAspect = mxImageShapeUpdateAspect;
+
+ var markup = '';
+
+ for (var i = 0; i < pages.length; i++)
+ {
+ markup += pages[i].outerHTML;
+ pages[i].parentNode.removeChild(pages[i]);
+
+ if (i < pages.length - 1)
+ {
+ markup += '<hr/>';
+ }
+ }
+
+ doc.body.innerHTML = markup;
+ writePageSelector();
+ }
+ };
+
+ // Overrides functions to implement wait counter
+ mxImageShape.prototype.scheduleUpdateAspect = function()
+ {
+ waitCounter++;
+ mxImageShapeScheduleUpdateAspect.apply(this, arguments);
+ };
+
+ // Overrides functions to implement wait counter
+ mxImageShape.prototype.updateAspect = function()
+ {
+ mxImageShapeUpdateAspect.apply(this, arguments);
+ waitCounter--;
+ writePages();
+ };
+ }
+
+ // Appends each page to the page output for printing, making
+ // sure there will be a page break after each page (ie. div)
+ for (var i = 0; i < vpages; i++)
+ {
+ var dy = i * availableHeight / this.scale - this.y0 / this.scale +
+ (bounds.y - tr.y * currentScale) / currentScale;
+
+ for (var j = 0; j < hpages; j++)
+ {
+ if (this.wnd == null)
+ {
+ return null;
+ }
+
+ var dx = j * availableWidth / this.scale - this.x0 / this.scale +
+ (bounds.x - tr.x * currentScale) / currentScale;
+ var pageNum = i * hpages + j + 1;
+
+ div = this.renderPage(this.pageFormat.width, this.pageFormat.height,
+ -dx, -dy, this.scale, pageNum);
+
+ // Gives the page a unique ID for later accessing the page
+ div.setAttribute('id', 'mxPage-'+pageNum);
+
+ // Border of the DIV (aka page) inside the document
+ if (this.borderColor != null)
+ {
+ div.style.borderColor = this.borderColor;
+ div.style.borderStyle = 'solid';
+ div.style.borderWidth = '1px';
+ }
+
+ // Needs to be assigned directly because IE doesn't support
+ // child selectors, eg. body > div { background: white; }
+ div.style.background = 'white';
+
+ if (i < vpages - 1 || j < hpages - 1)
+ {
+ div.style.pageBreakAfter = 'always';
+ }
+
+ // NOTE: We are dealing with cross-window DOM here, which
+ // is a problem in IE, so we copy the HTML markup instead.
+ // The underlying problem is that the graph display markup
+ // creation (in mxShape, mxGraphView) is hardwired to using
+ // document.createElement and hence we must use document
+ // to create the complete page and then copy it over to the
+ // new window.document. This can be fixed later by using the
+ // ownerDocument of the container in mxShape and mxGraphView.
+ if (mxClient.IS_IE)
+ {
+ // For some obscure reason, removing the DIV from the
+ // parent before fetching its outerHTML has missing
+ // fillcolor properties and fill children, so the div
+ // must be removed afterwards to keep the fillcolors.
+ // For delayed output we remote the DIV from the
+ // original document when we write out all pages.
+ doc.writeln(div.outerHTML);
+
+ if (pages != null)
+ {
+ pages.push(div);
+ }
+ else
+ {
+ div.parentNode.removeChild(div);
+ }
+ }
+ else
+ {
+ div.parentNode.removeChild(div);
+ doc.body.appendChild(div);
+ }
+
+ if (i < vpages - 1 || j < hpages - 1)
+ {
+ var hr = doc.createElement('hr');
+ hr.className = 'mxPageBreak';
+ doc.body.appendChild(hr);
+ }
+ }
+ }
+
+ doc.writeln('</body>');
+ doc.writeln('</html>');
+ doc.close();
+
+ // Marks the printing complete for async handling
+ if (pages != null)
+ {
+ isDone = true;
+ writePages();
+ }
+ else
+ {
+ writePageSelector();
+ }
+
+ // Removes all event handlers in the print output
+ mxEvent.release(doc.body);
+ }
+
+ this.wnd.focus();
+ }
+ catch (e)
+ {
+ // Removes the DIV from the document in case of an error
+ if (div != null && div.parentNode != null)
+ {
+ div.parentNode.removeChild(div);
+ }
+ }
+ finally
+ {
+ this.graph.cellRenderer.initializeOverlay = previousInitializeOverlay;
+ }
+
+ return this.wnd;
+};
+
+/**
+ * Function: writeHead
+ *
+ * Writes the HEAD section into the given document, without the opening
+ * and closing HEAD tags.
+ */
+mxPrintPreview.prototype.writeHead = function(doc, css)
+{
+ if (this.title != null)
+ {
+ doc.writeln('<title>' + this.title + '</title>');
+ }
+
+ // Makes sure no horizontal rulers are printed
+ doc.writeln('<style type="text/css">');
+ doc.writeln('@media print {');
+ doc.writeln(' table.mxPageSelector { display: none; }');
+ doc.writeln(' hr.mxPageBreak { display: none; }');
+ doc.writeln('}');
+ doc.writeln('@media screen {');
+
+ // NOTE: position: fixed is not supported in IE, so the page selector
+ // position (absolute) needs to be updated in IE (see below)
+ doc.writeln(' table.mxPageSelector { position: fixed; right: 10px; top: 10px;' +
+ 'font-family: Arial; font-size:10pt; border: solid 1px darkgray;' +
+ 'background: white; border-collapse:collapse; }');
+ doc.writeln(' table.mxPageSelector td { border: solid 1px gray; padding:4px; }');
+ doc.writeln(' body.mxPage { background: gray; }');
+ doc.writeln('}');
+
+ if (css != null)
+ {
+ doc.writeln(css);
+ }
+
+ doc.writeln('</style>');
+};
+
+/**
+ * Function: createPageSelector
+ *
+ * Creates the page selector table.
+ */
+mxPrintPreview.prototype.createPageSelector = function(vpages, hpages)
+{
+ var doc = this.wnd.document;
+ var table = doc.createElement('table');
+ table.className = 'mxPageSelector';
+ table.setAttribute('border', '0');
+
+ var tbody = doc.createElement('tbody');
+
+ for (var i = 0; i < vpages; i++)
+ {
+ var row = doc.createElement('tr');
+
+ for (var j = 0; j < hpages; j++)
+ {
+ var pageNum = i * hpages + j + 1;
+ var cell = doc.createElement('td');
+
+ // Needs anchor for all browers to work without JavaScript
+ // LATER: Does not work in Firefox because the generated document
+ // has the URL of the opening document, the anchor is appended
+ // to that URL and the full URL is loaded on click.
+ if (!mxClient.IS_NS || mxClient.IS_SF || mxClient.IS_GC)
+ {
+ var a = doc.createElement('a');
+ a.setAttribute('href', '#mxPage-' + pageNum);
+ mxUtils.write(a, pageNum, doc);
+ cell.appendChild(a);
+ }
+ else
+ {
+ mxUtils.write(cell, pageNum, doc);
+ }
+
+ row.appendChild(cell);
+ }
+
+ tbody.appendChild(row);
+ }
+
+ table.appendChild(tbody);
+
+ return table;
+};
+
+/**
+ * Function: renderPage
+ *
+ * Creates a DIV that prints a single page of the given
+ * graph using the given scale and returns the DIV that
+ * represents the page.
+ *
+ * Parameters:
+ *
+ * w - Width of the page in pixels.
+ * h - Height of the page in pixels.
+ * dx - Horizontal translation for the diagram.
+ * dy - Vertical translation for the diagram.
+ * scale - Scale for the diagram.
+ * pageNumber - Number of the page to be rendered.
+ */
+mxPrintPreview.prototype.renderPage = function(w, h, dx, dy, scale, pageNumber)
+{
+ var div = document.createElement('div');
+
+ try
+ {
+ div.style.width = w + 'px';
+ div.style.height = h + 'px';
+ div.style.overflow = 'hidden';
+ div.style.pageBreakInside = 'avoid';
+
+ var innerDiv = document.createElement('div');
+ innerDiv.style.top = this.border + 'px';
+ innerDiv.style.left = this.border + 'px';
+ innerDiv.style.width = (w - 2 * this.border) + 'px';
+ innerDiv.style.height = (h - 2 * this.border) + 'px';
+ innerDiv.style.overflow = 'hidden';
+
+ if (this.graph.dialect == mxConstants.DIALECT_VML)
+ {
+ innerDiv.style.position = 'absolute';
+ }
+
+ div.appendChild(innerDiv);
+ document.body.appendChild(div);
+ var view = this.graph.getView();
+
+ var previousContainer = this.graph.container;
+ this.graph.container = innerDiv;
+
+ var canvas = view.getCanvas();
+ var backgroundPane = view.getBackgroundPane();
+ var drawPane = view.getDrawPane();
+ var overlayPane = view.getOverlayPane();
+
+ if (this.graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ view.createSvg();
+ }
+ else if (this.graph.dialect == mxConstants.DIALECT_VML)
+ {
+ view.createVml();
+ }
+ else
+ {
+ view.createHtml();
+ }
+
+ // Disables events on the view
+ var eventsEnabled = view.isEventsEnabled();
+ view.setEventsEnabled(false);
+
+ // Disables the graph to avoid cursors
+ var graphEnabled = this.graph.isEnabled();
+ this.graph.setEnabled(false);
+
+ // Resets the translation
+ var translate = view.getTranslate();
+ view.translate = new mxPoint(dx, dy);
+
+ var temp = null;
+
+ try
+ {
+ // Creates the temporary cell states in the view and
+ // draws them onto the temporary DOM nodes in the view
+ var model = this.graph.getModel();
+ var cells = [model.getRoot()];
+ temp = new mxTemporaryCellStates(view, scale, cells);
+ }
+ finally
+ {
+ // Removes overlay pane with selection handles
+ // controls and icons from the print output
+ if (mxClient.IS_IE)
+ {
+ view.overlayPane.innerHTML = '';
+ }
+ else
+ {
+ // Removes everything but the SVG node
+ var tmp = innerDiv.firstChild;
+
+ while (tmp != null)
+ {
+ var next = tmp.nextSibling;
+ var name = tmp.nodeName.toLowerCase();
+
+ // Note: Width and heigh are required in FF 11
+ if (name == 'svg')
+ {
+ tmp.setAttribute('width', parseInt(innerDiv.style.width));
+ tmp.setAttribute('height', parseInt(innerDiv.style.height));
+ }
+ // Tries to fetch all text labels and only text labels
+ else if (tmp.style.cursor != 'default' && name != 'table')
+ {
+ tmp.parentNode.removeChild(tmp);
+ }
+
+ tmp = next;
+ }
+ }
+
+ // Completely removes the overlay pane to remove more handles
+ view.overlayPane.parentNode.removeChild(view.overlayPane);
+
+ // Restores the state of the view
+ this.graph.setEnabled(graphEnabled);
+ this.graph.container = previousContainer;
+ view.canvas = canvas;
+ view.backgroundPane = backgroundPane;
+ view.drawPane = drawPane;
+ view.overlayPane = overlayPane;
+ view.translate = translate;
+ temp.destroy();
+ view.setEventsEnabled(eventsEnabled);
+ }
+ }
+ catch (e)
+ {
+ div.parentNode.removeChild(div);
+ div = null;
+
+ throw e;
+ }
+
+ return div;
+};
+
+/**
+ * Function: print
+ *
+ * Opens the print preview and shows the print dialog.
+ */
+mxPrintPreview.prototype.print = function()
+{
+ var wnd = this.open();
+
+ if (wnd != null)
+ {
+ wnd.print();
+ }
+};
+
+/**
+ * Function: close
+ *
+ * Closes the print preview window.
+ */
+mxPrintPreview.prototype.close = function()
+{
+ if (this.wnd != null)
+ {
+ this.wnd.close();
+ this.wnd = null;
+ }
+};
diff --git a/src/js/view/mxSpaceManager.js b/src/js/view/mxSpaceManager.js
new file mode 100644
index 0000000..2a2dd11
--- /dev/null
+++ b/src/js/view/mxSpaceManager.js
@@ -0,0 +1,460 @@
+/**
+ * $Id: mxSpaceManager.js,v 1.9 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxSpaceManager
+ *
+ * In charge of moving cells after a resize.
+ *
+ * Constructor: mxSpaceManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing graph.
+ */
+function mxSpaceManager(graph, shiftRightwards, shiftDownwards, extendParents)
+{
+ this.resizeHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.cellsResized(evt.getProperty('cells'));
+ }
+ });
+
+ this.foldHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.cellsResized(evt.getProperty('cells'));
+ }
+ });
+
+ this.shiftRightwards = (shiftRightwards != null) ? shiftRightwards : true;
+ this.shiftDownwards = (shiftDownwards != null) ? shiftDownwards : true;
+ this.extendParents = (extendParents != null) ? extendParents : true;
+ this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSpaceManager.prototype = new mxEventSource();
+mxSpaceManager.prototype.constructor = mxSpaceManager;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSpaceManager.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSpaceManager.prototype.enabled = true;
+
+/**
+ * Variable: shiftRightwards
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSpaceManager.prototype.shiftRightwards = true;
+
+/**
+ * Variable: shiftDownwards
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSpaceManager.prototype.shiftDownwards = true;
+
+/**
+ * Variable: extendParents
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSpaceManager.prototype.extendParents = true;
+
+/**
+ * Variable: resizeHandler
+ *
+ * Holds the function that handles the move event.
+ */
+mxSpaceManager.prototype.resizeHandler = null;
+
+/**
+ * Variable: foldHandler
+ *
+ * Holds the function that handles the fold event.
+ */
+mxSpaceManager.prototype.foldHandler = null;
+
+/**
+ * Function: isCellIgnored
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxSpaceManager.prototype.isCellIgnored = function(cell)
+{
+ return !this.getGraph().getModel().isVertex(cell);
+};
+
+/**
+ * Function: isCellShiftable
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxSpaceManager.prototype.isCellShiftable = function(cell)
+{
+ return this.getGraph().getModel().isVertex(cell) &&
+ this.getGraph().isCellMovable(cell);
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSpaceManager.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSpaceManager.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isShiftRightwards
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSpaceManager.prototype.isShiftRightwards = function()
+{
+ return this.shiftRightwards;
+};
+
+/**
+ * Function: setShiftRightwards
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSpaceManager.prototype.setShiftRightwards = function(value)
+{
+ this.shiftRightwards = value;
+};
+
+/**
+ * Function: isShiftDownwards
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSpaceManager.prototype.isShiftDownwards = function()
+{
+ return this.shiftDownwards;
+};
+
+/**
+ * Function: setShiftDownwards
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSpaceManager.prototype.setShiftDownwards = function(value)
+{
+ this.shiftDownwards = value;
+};
+
+/**
+ * Function: isExtendParents
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSpaceManager.prototype.isExtendParents = function()
+{
+ return this.extendParents;
+};
+
+/**
+ * Function: setShiftDownwards
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSpaceManager.prototype.setExtendParents = function(value)
+{
+ this.extendParents = value;
+};
+
+/**
+ * Function: getGraph
+ *
+ * Returns the graph that this layout operates on.
+ */
+mxSpaceManager.prototype.getGraph = function()
+{
+ return this.graph;
+};
+
+/**
+ * Function: setGraph
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxSpaceManager.prototype.setGraph = function(graph)
+{
+ if (this.graph != null)
+ {
+ this.graph.removeListener(this.resizeHandler);
+ this.graph.removeListener(this.foldHandler);
+ }
+
+ this.graph = graph;
+
+ if (this.graph != null)
+ {
+ this.graph.addListener(mxEvent.RESIZE_CELLS, this.resizeHandler);
+ this.graph.addListener(mxEvent.FOLD_CELLS, this.foldHandler);
+ }
+};
+
+/**
+ * Function: cellsResized
+ *
+ * Called from <moveCellsIntoParent> to invoke the <move> hook in the
+ * automatic layout of each modified cell's parent. The event is used to
+ * define the x- and y-coordinates passed to the move function.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been resized.
+ */
+mxSpaceManager.prototype.cellsResized = function(cells)
+{
+ if (cells != null)
+ {
+ var model = this.graph.getModel();
+
+ // Raising the update level should not be required
+ // since only one call is made below
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isCellIgnored(cells[i]))
+ {
+ this.cellResized(cells[i]);
+ break;
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: cellResized
+ *
+ * Called from <moveCellsIntoParent> to invoke the <move> hook in the
+ * automatic layout of each modified cell's parent. The event is used to
+ * define the x- and y-coordinates passed to the move function.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that has been resized.
+ */
+mxSpaceManager.prototype.cellResized = function(cell)
+{
+ var graph = this.getGraph();
+ var view = graph.getView();
+ var model = graph.getModel();
+
+ var state = view.getState(cell);
+ var pstate = view.getState(model.getParent(cell));
+
+ if (state != null &&
+ pstate != null)
+ {
+ var cells = this.getCellsToShift(state);
+ var geo = model.getGeometry(cell);
+
+ if (cells != null &&
+ geo != null)
+ {
+ var tr = view.translate;
+ var scale = view.scale;
+
+ var x0 = state.x - pstate.origin.x - tr.x * scale;
+ var y0 = state.y - pstate.origin.y - tr.y * scale;
+ var right = state.x + state.width;
+ var bottom = state.y + state.height;
+
+ var dx = state.width - geo.width * scale + x0 - geo.x * scale;
+ var dy = state.height - geo.height * scale + y0 - geo.y * scale;
+
+ var fx = 1 - geo.width * scale / state.width;
+ var fy = 1 - geo.height * scale / state.height;
+
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] != cell &&
+ this.isCellShiftable(cells[i]))
+ {
+ this.shiftCell(cells[i], dx, dy, x0, y0, right, bottom, fx, fy,
+ this.isExtendParents() &&
+ graph.isExtendParent(cells[i]));
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ }
+};
+
+/**
+ * Function: shiftCell
+ *
+ * Called from <moveCellsIntoParent> to invoke the <move> hook in the
+ * automatic layout of each modified cell's parent. The event is used to
+ * define the x- and y-coordinates passed to the move function.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxSpaceManager.prototype.shiftCell = function(cell, dx, dy, Ox0, y0, right,
+ bottom, fx, fy, extendParent)
+{
+ var graph = this.getGraph();
+ var state = graph.getView().getState(cell);
+
+ if (state != null)
+ {
+ var model = graph.getModel();
+ var geo = model.getGeometry(cell);
+
+ if (geo != null)
+ {
+ model.beginUpdate();
+ try
+ {
+ if (this.isShiftRightwards())
+ {
+ if (state.x >= right)
+ {
+ geo = geo.clone();
+ geo.translate(-dx, 0);
+ }
+ else
+ {
+ var tmpDx = Math.max(0, state.x - x0);
+ geo = geo.clone();
+ geo.translate(-fx * tmpDx, 0);
+ }
+ }
+
+ if (this.isShiftDownwards())
+ {
+ if (state.y >= bottom)
+ {
+ geo = geo.clone();
+ geo.translate(0, -dy);
+ }
+ else
+ {
+ var tmpDy = Math.max(0, state.y - y0);
+ geo = geo.clone();
+ geo.translate(0, -fy * tmpDy);
+ }
+ }
+
+ if (geo != model.getGeometry(cell))
+ {
+ model.setGeometry(cell, geo);
+
+ // Parent size might need to be updated if this
+ // is seen as part of the resize
+ if (extendParent)
+ {
+ graph.extendParent(cell);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ }
+};
+
+/**
+ * Function: getCellsToShift
+ *
+ * Returns the cells to shift after a resize of the
+ * specified <mxCellState>.
+ */
+mxSpaceManager.prototype.getCellsToShift = function(state)
+{
+ var graph = this.getGraph();
+ var parent = graph.getModel().getParent(state.cell);
+ var down = this.isShiftDownwards();
+ var right = this.isShiftRightwards();
+
+ return graph.getCellsBeyond(state.x + ((down) ? 0 : state.width),
+ state.y + ((down && right) ? 0 : state.height), parent, right, down);
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxSpaceManager.prototype.destroy = function()
+{
+ this.setGraph(null);
+};
diff --git a/src/js/view/mxStyleRegistry.js b/src/js/view/mxStyleRegistry.js
new file mode 100644
index 0000000..6ad878d
--- /dev/null
+++ b/src/js/view/mxStyleRegistry.js
@@ -0,0 +1,70 @@
+/**
+ * $Id: mxStyleRegistry.js,v 1.10 2011-04-27 10:15:39 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxStyleRegistry =
+{
+ /**
+ * Class: mxStyleRegistry
+ *
+ * Singleton class that acts as a global converter from string to object values
+ * in a style. This is currently only used to perimeters and edge styles.
+ *
+ * Variable: values
+ *
+ * Maps from strings to objects.
+ */
+ values: [],
+
+ /**
+ * Function: putValue
+ *
+ * Puts the given object into the registry under the given name.
+ */
+ putValue: function(name, obj)
+ {
+ mxStyleRegistry.values[name] = obj;
+ },
+
+ /**
+ * Function: getValue
+ *
+ * Returns the value associated with the given name.
+ */
+ getValue: function(name)
+ {
+ return mxStyleRegistry.values[name];
+ },
+
+ /**
+ * Function: getName
+ *
+ * Returns the name for the given value.
+ */
+ getName: function(value)
+ {
+ for (var key in mxStyleRegistry.values)
+ {
+ if (mxStyleRegistry.values[key] == value)
+ {
+ return key;
+ }
+ }
+
+ return null;
+ }
+
+};
+
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ELBOW, mxEdgeStyle.ElbowConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ENTITY_RELATION, mxEdgeStyle.EntityRelation);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_LOOP, mxEdgeStyle.Loop);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SIDETOSIDE, mxEdgeStyle.SideToSide);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_TOPTOBOTTOM, mxEdgeStyle.TopToBottom);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ORTHOGONAL, mxEdgeStyle.OrthConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SEGMENT, mxEdgeStyle.SegmentConnector);
+
+mxStyleRegistry.putValue(mxConstants.PERIMETER_ELLIPSE, mxPerimeter.EllipsePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RECTANGLE, mxPerimeter.RectanglePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RHOMBUS, mxPerimeter.RhombusPerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_TRIANGLE, mxPerimeter.TrianglePerimeter);
diff --git a/src/js/view/mxStylesheet.js b/src/js/view/mxStylesheet.js
new file mode 100644
index 0000000..82a520e
--- /dev/null
+++ b/src/js/view/mxStylesheet.js
@@ -0,0 +1,266 @@
+/**
+ * $Id: mxStylesheet.js,v 1.35 2010-03-26 10:24:58 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxStylesheet
+ *
+ * Defines the appearance of the cells in a graph. See <putCellStyle> for an
+ * example of creating a new cell style. It is recommended to use objects, not
+ * arrays for holding cell styles. Existing styles can be cloned using
+ * <mxUtils.clone> and turned into a string for debugging using
+ * <mxUtils.toString>.
+ *
+ * Default Styles:
+ *
+ * The stylesheet contains two built-in styles, which are used if no style is
+ * defined for a cell:
+ *
+ * defaultVertex - Default style for vertices
+ * defaultEdge - Default style for edges
+ *
+ * Example:
+ *
+ * (code)
+ * var vertexStyle = stylesheet.getDefaultVertexStyle();
+ * vertexStyle[mxConstants.ROUNDED] = true;
+ * var edgeStyle = stylesheet.getDefaultEdgeStyle();
+ * edgeStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation;
+ * (end)
+ *
+ * Modifies the built-in default styles.
+ *
+ * To avoid the default style for a cell, add a leading semicolon
+ * to the style definition, eg.
+ *
+ * (code)
+ * ;shadow=1
+ * (end)
+ *
+ * Removing keys:
+ *
+ * For removing a key in a cell style of the form [stylename;|key=value;] the
+ * special value none can be used, eg. highlight;fillColor=none
+ *
+ * See also the helper methods in mxUtils to modify strings of this format,
+ * namely <mxUtils.setStyle>, <mxUtils.indexOfStylename>,
+ * <mxUtils.addStylename>, <mxUtils.removeStylename>,
+ * <mxUtils.removeAllStylenames> and <mxUtils.setStyleFlag>.
+ *
+ * Constructor: mxStylesheet
+ *
+ * Constructs a new stylesheet and assigns default styles.
+ */
+function mxStylesheet()
+{
+ this.styles = new Object();
+
+ this.putDefaultVertexStyle(this.createDefaultVertexStyle());
+ this.putDefaultEdgeStyle(this.createDefaultEdgeStyle());
+};
+
+/**
+ * Function: styles
+ *
+ * Maps from names to cell styles. Each cell style is a map of key,
+ * value pairs.
+ */
+mxStylesheet.prototype.styles;
+
+/**
+ * Function: createDefaultVertexStyle
+ *
+ * Creates and returns the default vertex style.
+ */
+mxStylesheet.prototype.createDefaultVertexStyle = function()
+{
+ var style = new Object();
+
+ style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+ style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+ style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+ style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+ style[mxConstants.STYLE_FILLCOLOR] = '#C3D9FF';
+ style[mxConstants.STYLE_STROKECOLOR] = '#6482B9';
+ style[mxConstants.STYLE_FONTCOLOR] = '#774400';
+
+ return style;
+};
+
+/**
+ * Function: createDefaultEdgeStyle
+ *
+ * Creates and returns the default edge style.
+ */
+mxStylesheet.prototype.createDefaultEdgeStyle = function()
+{
+ var style = new Object();
+
+ style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR;
+ style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
+ style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+ style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+ style[mxConstants.STYLE_STROKECOLOR] = '#6482B9';
+ style[mxConstants.STYLE_FONTCOLOR] = '#446299';
+
+ return style;
+};
+
+/**
+ * Function: putDefaultVertexStyle
+ *
+ * Sets the default style for vertices using defaultVertex as the
+ * stylename.
+ *
+ * Parameters:
+ * style - Key, value pairs that define the style.
+ */
+mxStylesheet.prototype.putDefaultVertexStyle = function(style)
+{
+ this.putCellStyle('defaultVertex', style);
+};
+
+/**
+ * Function: putDefaultEdgeStyle
+ *
+ * Sets the default style for edges using defaultEdge as the stylename.
+ */
+mxStylesheet.prototype.putDefaultEdgeStyle = function(style)
+{
+ this.putCellStyle('defaultEdge', style);
+};
+
+/**
+ * Function: getDefaultVertexStyle
+ *
+ * Returns the default style for vertices.
+ */
+mxStylesheet.prototype.getDefaultVertexStyle = function()
+{
+ return this.styles['defaultVertex'];
+};
+
+/**
+ * Function: getDefaultEdgeStyle
+ *
+ * Sets the default style for edges.
+ */
+mxStylesheet.prototype.getDefaultEdgeStyle = function()
+{
+ return this.styles['defaultEdge'];
+};
+
+/**
+ * Function: putCellStyle
+ *
+ * Stores the given map of key, value pairs under the given name in
+ * <styles>.
+ *
+ * Example:
+ *
+ * The following example adds a new style called 'rounded' into an
+ * existing stylesheet:
+ *
+ * (code)
+ * var style = new Object();
+ * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+ * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+ * style[mxConstants.STYLE_ROUNDED] = true;
+ * graph.getStylesheet().putCellStyle('rounded', style);
+ * (end)
+ *
+ * In the above example, the new style is an object. The possible keys of
+ * the object are all the constants in <mxConstants> that start with STYLE
+ * and the values are either JavaScript objects, such as
+ * <mxPerimeter.RightAngleRectanglePerimeter> (which is in fact a function)
+ * or expressions, such as true. Note that not all keys will be
+ * interpreted by all shapes (eg. the line shape ignores the fill color).
+ * The final call to this method associates the style with a name in the
+ * stylesheet. The style is used in a cell with the following code:
+ *
+ * (code)
+ * model.setStyle(cell, 'rounded');
+ * (end)
+ *
+ * Parameters:
+ *
+ * name - Name for the style to be stored.
+ * style - Key, value pairs that define the style.
+ */
+mxStylesheet.prototype.putCellStyle = function(name, style)
+{
+ this.styles[name] = style;
+};
+
+/**
+ * Function: getCellStyle
+ *
+ * Returns the cell style for the specified stylename or the given
+ * defaultStyle if no style can be found for the given stylename.
+ *
+ * Parameters:
+ *
+ * name - String of the form [(stylename|key=value);] that represents the
+ * style.
+ * defaultStyle - Default style to be returned if no style can be found.
+ */
+mxStylesheet.prototype.getCellStyle = function(name, defaultStyle)
+{
+ var style = defaultStyle;
+
+ if (name != null && name.length > 0)
+ {
+ var pairs = name.split(';');
+
+ if (style != null &&
+ name.charAt(0) != ';')
+ {
+ style = mxUtils.clone(style);
+ }
+ else
+ {
+ style = new Object();
+ }
+
+ // Parses each key, value pair into the existing style
+ for (var i = 0; i < pairs.length; i++)
+ {
+ var tmp = pairs[i];
+ var pos = tmp.indexOf('=');
+
+ if (pos >= 0)
+ {
+ var key = tmp.substring(0, pos);
+ var value = tmp.substring(pos + 1);
+
+ if (value == mxConstants.NONE)
+ {
+ delete style[key];
+ }
+ else if (mxUtils.isNumeric(value))
+ {
+ style[key] = parseFloat(value);
+ }
+ else
+ {
+ style[key] = value;
+ }
+ }
+ else
+ {
+ // Merges the entries from a named style
+ var tmpStyle = this.styles[tmp];
+
+ if (tmpStyle != null)
+ {
+ for (var key in tmpStyle)
+ {
+ style[key] = tmpStyle[key];
+ }
+ }
+ }
+ }
+ }
+
+ return style;
+};
diff --git a/src/js/view/mxSwimlaneManager.js b/src/js/view/mxSwimlaneManager.js
new file mode 100644
index 0000000..fe40613
--- /dev/null
+++ b/src/js/view/mxSwimlaneManager.js
@@ -0,0 +1,449 @@
+/**
+ * $Id: mxSwimlaneManager.js,v 1.17 2011-01-14 15:21:10 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxSwimlaneManager
+ *
+ * Manager for swimlanes and nested swimlanes that sets the size of newly added
+ * swimlanes to that of their siblings, and propagates changes to the size of a
+ * swimlane to its siblings, if <siblings> is true, and its ancestors, if
+ * <bubbling> is true.
+ *
+ * Constructor: mxSwimlaneManager
+ *
+ * Constructs a new swimlane manager for the given graph.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing graph.
+ */
+function mxSwimlaneManager(graph, horizontal, addEnabled, resizeEnabled)
+{
+ this.horizontal = (horizontal != null) ? horizontal : true;
+ this.addEnabled = (addEnabled != null) ? addEnabled : true;
+ this.resizeEnabled = (resizeEnabled != null) ? resizeEnabled : true;
+
+ this.addHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled() && this.isAddEnabled())
+ {
+ this.cellsAdded(evt.getProperty('cells'));
+ }
+ });
+
+ this.resizeHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled() && this.isResizeEnabled())
+ {
+ this.cellsResized(evt.getProperty('cells'));
+ }
+ });
+
+ this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSwimlaneManager.prototype = new mxEventSource();
+mxSwimlaneManager.prototype.constructor = mxSwimlaneManager;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSwimlaneManager.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSwimlaneManager.prototype.enabled = true;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the swimlanes. Default is true.
+ */
+mxSwimlaneManager.prototype.horizontal = true;
+
+/**
+ * Variable: addEnabled
+ *
+ * Specifies if newly added cells should be resized to match the size of their
+ * existing siblings. Default is true.
+ */
+mxSwimlaneManager.prototype.addEnabled = true;
+
+/**
+ * Variable: resizeEnabled
+ *
+ * Specifies if resizing of swimlanes should be handled. Default is true.
+ */
+mxSwimlaneManager.prototype.resizeEnabled = true;
+
+/**
+ * Variable: moveHandler
+ *
+ * Holds the function that handles the move event.
+ */
+mxSwimlaneManager.prototype.addHandler = null;
+
+/**
+ * Variable: moveHandler
+ *
+ * Holds the function that handles the move event.
+ */
+mxSwimlaneManager.prototype.resizeHandler = null;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSwimlaneManager.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSwimlaneManager.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isHorizontal
+ *
+ * Returns <horizontal>.
+ */
+mxSwimlaneManager.prototype.isHorizontal = function()
+{
+ return this.horizontal;
+};
+
+/**
+ * Function: setHorizontal
+ *
+ * Sets <horizontal>.
+ */
+mxSwimlaneManager.prototype.setHorizontal = function(value)
+{
+ this.horizontal = value;
+};
+
+/**
+ * Function: isAddEnabled
+ *
+ * Returns <addEnabled>.
+ */
+mxSwimlaneManager.prototype.isAddEnabled = function()
+{
+ return this.addEnabled;
+};
+
+/**
+ * Function: setAddEnabled
+ *
+ * Sets <addEnabled>.
+ */
+mxSwimlaneManager.prototype.setAddEnabled = function(value)
+{
+ this.addEnabled = value;
+};
+
+/**
+ * Function: isResizeEnabled
+ *
+ * Returns <resizeEnabled>.
+ */
+mxSwimlaneManager.prototype.isResizeEnabled = function()
+{
+ return this.resizeEnabled;
+};
+
+/**
+ * Function: setResizeEnabled
+ *
+ * Sets <resizeEnabled>.
+ */
+mxSwimlaneManager.prototype.setResizeEnabled = function(value)
+{
+ this.resizeEnabled = value;
+};
+
+/**
+ * Function: getGraph
+ *
+ * Returns the graph that this manager operates on.
+ */
+mxSwimlaneManager.prototype.getGraph = function()
+{
+ return this.graph;
+};
+
+/**
+ * Function: setGraph
+ *
+ * Sets the graph that the manager operates on.
+ */
+mxSwimlaneManager.prototype.setGraph = function(graph)
+{
+ if (this.graph != null)
+ {
+ this.graph.removeListener(this.addHandler);
+ this.graph.removeListener(this.resizeHandler);
+ }
+
+ this.graph = graph;
+
+ if (this.graph != null)
+ {
+ this.graph.addListener(mxEvent.ADD_CELLS, this.addHandler);
+ this.graph.addListener(mxEvent.CELLS_RESIZED, this.resizeHandler);
+ }
+};
+
+/**
+ * Function: isSwimlaneIgnored
+ *
+ * Returns true if the given swimlane should be ignored.
+ */
+mxSwimlaneManager.prototype.isSwimlaneIgnored = function(swimlane)
+{
+ return !this.getGraph().isSwimlane(swimlane);
+};
+
+/**
+ * Function: isCellHorizontal
+ *
+ * Returns true if the given cell is horizontal. If the given cell is not a
+ * swimlane, then the global orientation is returned.
+ */
+mxSwimlaneManager.prototype.isCellHorizontal = function(cell)
+{
+ if (this.graph.isSwimlane(cell))
+ {
+ var state = this.graph.view.getState(cell);
+ var style = (state != null) ? state.style : this.graph.getCellStyle(cell);
+
+ return mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, 1) == 1;
+ }
+
+ return !this.isHorizontal();
+};
+
+/**
+ * Function: cellsAdded
+ *
+ * Called if any cells have been added.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been added.
+ */
+mxSwimlaneManager.prototype.cellsAdded = function(cells)
+{
+ if (cells != null)
+ {
+ var model = this.getGraph().getModel();
+
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isSwimlaneIgnored(cells[i]))
+ {
+ this.swimlaneAdded(cells[i]);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: swimlaneAdded
+ *
+ * Updates the size of the given swimlane to match that of any existing
+ * siblings swimlanes.
+ *
+ * Parameters:
+ *
+ * swimlane - <mxCell> that represents the new swimlane.
+ */
+mxSwimlaneManager.prototype.swimlaneAdded = function(swimlane)
+{
+ var model = this.getGraph().getModel();
+ var parent = model.getParent(swimlane);
+ var childCount = model.getChildCount(parent);
+ var geo = null;
+
+ // Finds the first valid sibling swimlane as reference
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+
+ if (child != swimlane && !this.isSwimlaneIgnored(child))
+ {
+ geo = model.getGeometry(child);
+
+ if (geo != null)
+ {
+ break;
+ }
+ }
+ }
+
+ // Applies the size of the refernece to the newly added swimlane
+ if (geo != null)
+ {
+ this.resizeSwimlane(swimlane, geo.width, geo.height);
+ }
+};
+
+/**
+ * Function: cellsResized
+ *
+ * Called if any cells have been resizes. Calls <swimlaneResized> for all
+ * swimlanes where <isSwimlaneIgnored> returns false.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose size was changed.
+ */
+mxSwimlaneManager.prototype.cellsResized = function(cells)
+{
+ if (cells != null)
+ {
+ var model = this.getGraph().getModel();
+
+ model.beginUpdate();
+ try
+ {
+ // Finds the top-level swimlanes and adds offsets
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isSwimlaneIgnored(cells[i]))
+ {
+ var geo = model.getGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var size = new mxRectangle(0, 0, geo.width, geo.height);
+ var top = cells[i];
+ var current = top;
+
+ while (current != null)
+ {
+ top = current;
+ current = model.getParent(current);
+ var tmp = (this.graph.isSwimlane(current)) ?
+ this.graph.getStartSize(current) :
+ new mxRectangle();
+ size.width += tmp.width;
+ size.height += tmp.height;
+ }
+
+ this.resizeSwimlane(top, size.width, size.height);
+ }
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: resizeSwimlane
+ *
+ * Called from <cellsResized> for all swimlanes that are not ignored to update
+ * the size of the siblings and the size of the parent swimlanes, recursively,
+ * if <bubbling> is true.
+ *
+ * Parameters:
+ *
+ * swimlane - <mxCell> whose size has changed.
+ */
+mxSwimlaneManager.prototype.resizeSwimlane = function(swimlane, w, h)
+{
+ var model = this.getGraph().getModel();
+
+ model.beginUpdate();
+ try
+ {
+ if (!this.isSwimlaneIgnored(swimlane))
+ {
+ var geo = model.getGeometry(swimlane);
+
+ if (geo != null)
+ {
+ var horizontal = this.isCellHorizontal(swimlane);
+
+ if ((horizontal && geo.height != h) || (!horizontal && geo.width != w))
+ {
+ geo = geo.clone();
+
+ if (horizontal)
+ {
+ geo.height = h;
+ }
+ else
+ {
+ geo.width = w;
+ }
+
+ model.setGeometry(swimlane, geo);
+ }
+ }
+ }
+
+ var tmp = (this.graph.isSwimlane(swimlane)) ?
+ this.graph.getStartSize(swimlane) :
+ new mxRectangle();
+ w -= tmp.width;
+ h -= tmp.height;
+
+ var childCount = model.getChildCount(swimlane);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(swimlane, i);
+ this.resizeSwimlane(child, w, h);
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxSwimlaneManager.prototype.destroy = function()
+{
+ this.setGraph(null);
+};
diff --git a/src/js/view/mxTemporaryCellStates.js b/src/js/view/mxTemporaryCellStates.js
new file mode 100644
index 0000000..ce8232c
--- /dev/null
+++ b/src/js/view/mxTemporaryCellStates.js
@@ -0,0 +1,105 @@
+/**
+ * $Id: mxTemporaryCellStates.js,v 1.10 2010-04-20 14:43:12 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxTemporaryCellStates
+ *
+ * Extends <mxPoint> to implement a 2-dimensional rectangle with double
+ * precision coordinates.
+ *
+ * Constructor: mxRectangle
+ *
+ * Constructs a new rectangle for the optional parameters. If no parameters
+ * are given then the respective default values are used.
+ */
+function mxTemporaryCellStates(view, scale, cells)
+{
+ this.view = view;
+ scale = (scale != null) ? scale : 1;
+
+ // Stores the previous state
+ this.oldBounds = view.getGraphBounds();
+ this.oldStates = view.getStates();
+ this.oldScale = view.getScale();
+
+ // Creates space for new states
+ view.setStates(new mxDictionary());
+ view.setScale(scale);
+
+ if (cells != null)
+ {
+ // Creates virtual parent state for validation
+ var state = view.createState(new mxCell());
+
+ // Validates the vertices and edges without adding them to
+ // the model so that the original cells are not modified
+ for (var i = 0; i < cells.length; i++)
+ {
+ view.validateBounds(state, cells[i]);
+ }
+
+ var bbox = null;
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var bounds = view.validatePoints(state, cells[i]);
+
+ if (bbox == null)
+ {
+ bbox = bounds;
+ }
+ else
+ {
+ bbox.add(bounds);
+ }
+ }
+
+ if (bbox == null)
+ {
+ bbox = new mxRectangle();
+ }
+
+ view.setGraphBounds(bbox);
+ }
+};
+
+/**
+ * Variable: view
+ *
+ * Holds the width of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.view = null;
+
+/**
+ * Variable: oldStates
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldStates = null;
+
+/**
+ * Variable: oldBounds
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldBounds = null;
+
+/**
+ * Variable: oldScale
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldScale = null;
+
+/**
+ * Function: destroy
+ *
+ * Returns the top, left corner as a new <mxPoint>.
+ */
+mxTemporaryCellStates.prototype.destroy = function()
+{
+ this.view.setScale(this.oldScale);
+ this.view.setStates(this.oldStates);
+ this.view.setGraphBounds(this.oldBounds);
+};