diff options
Diffstat (limited to 'src/js/model')
-rw-r--r-- | src/js/model/mxCell.js | 806 | ||||
-rw-r--r-- | src/js/model/mxCellPath.js | 163 | ||||
-rw-r--r-- | src/js/model/mxGeometry.js | 277 | ||||
-rw-r--r-- | src/js/model/mxGraphModel.js | 2622 |
4 files changed, 3868 insertions, 0 deletions
diff --git a/src/js/model/mxCell.js b/src/js/model/mxCell.js new file mode 100644 index 0000000..cb5eb9f --- /dev/null +++ b/src/js/model/mxCell.js @@ -0,0 +1,806 @@ +/** + * $Id: mxCell.js,v 1.36 2011-06-17 13:45:08 gaudenz Exp $ + * Copyright (c) 2006-2010, JGraph Ltd + */ +/** + * Class: mxCell + * + * Cells are the elements of the graph model. They represent the state + * of the groups, vertices and edges in a graph. + * + * Custom attributes: + * + * For custom attributes we recommend using an XML node as the value of a cell. + * The following code can be used to create a cell with an XML node as the + * value: + * + * (code) + * var doc = mxUtils.createXmlDocument(); + * var node = doc.createElement('MyNode') + * node.setAttribute('label', 'MyLabel'); + * node.setAttribute('attribute1', 'value1'); + * graph.insertVertex(graph.getDefaultParent(), null, node, 40, 40, 80, 30); + * (end) + * + * For the label to work, <mxGraph.convertValueToString> and + * <mxGraph.cellLabelChanged> should be overridden as follows: + * + * (code) + * graph.convertValueToString = function(cell) + * { + * if (mxUtils.isNode(cell.value)) + * { + * return cell.getAttribute('label', '') + * } + * }; + * + * var cellLabelChanged = graph.cellLabelChanged; + * graph.cellLabelChanged = function(cell, newValue, autoSize) + * { + * if (mxUtils.isNode(cell.value)) + * { + * // Clones the value for correct undo/redo + * var elt = cell.value.cloneNode(true); + * elt.setAttribute('label', newValue); + * newValue = elt; + * } + * + * cellLabelChanged.apply(this, arguments); + * }; + * (end) + * + * Callback: onInit + * + * Called from within the constructor. + * + * Constructor: mxCell + * + * Constructs a new cell to be used in a graph model. + * This method invokes <onInit> upon completion. + * + * Parameters: + * + * value - Optional object that represents the cell value. + * geometry - Optional <mxGeometry> that specifies the geometry. + * style - Optional formatted string that defines the style. + */ +function mxCell(value, geometry, style) +{ + this.value = value; + this.setGeometry(geometry); + this.setStyle(style); + + if (this.onInit != null) + { + this.onInit(); + } +}; + +/** + * Variable: id + * + * Holds the Id. Default is null. + */ +mxCell.prototype.id = null; + +/** + * Variable: value + * + * Holds the user object. Default is null. + */ +mxCell.prototype.value = null; + +/** + * Variable: geometry + * + * Holds the <mxGeometry>. Default is null. + */ +mxCell.prototype.geometry = null; + +/** + * Variable: style + * + * Holds the style as a string of the form [(stylename|key=value);]. Default is + * null. + */ +mxCell.prototype.style = null; + +/** + * Variable: vertex + * + * Specifies whether the cell is a vertex. Default is false. + */ +mxCell.prototype.vertex = false; + +/** + * Variable: edge + * + * Specifies whether the cell is an edge. Default is false. + */ +mxCell.prototype.edge = false; + +/** + * Variable: connectable + * + * Specifies whether the cell is connectable. Default is true. + */ +mxCell.prototype.connectable = true; + +/** + * Variable: visible + * + * Specifies whether the cell is visible. Default is true. + */ +mxCell.prototype.visible = true; + +/** + * Variable: collapsed + * + * Specifies whether the cell is collapsed. Default is false. + */ +mxCell.prototype.collapsed = false; + +/** + * Variable: parent + * + * Reference to the parent cell. + */ +mxCell.prototype.parent = null; + +/** + * Variable: source + * + * Reference to the source terminal. + */ +mxCell.prototype.source = null; + +/** + * Variable: target + * + * Reference to the target terminal. + */ +mxCell.prototype.target = null; + +/** + * Variable: children + * + * Holds the child cells. + */ +mxCell.prototype.children = null; + +/** + * Variable: edges + * + * Holds the edges. + */ +mxCell.prototype.edges = null; + +/** + * Variable: mxTransient + * + * List of members that should not be cloned inside <clone>. This field is + * passed to <mxUtils.clone> and is not made persistent in <mxCellCodec>. + * This is not a convention for all classes, it is only used in this class + * to mark transient fields since transient modifiers are not supported by + * the language. + */ +mxCell.prototype.mxTransient = ['id', 'value', 'parent', 'source', + 'target', 'children', 'edges']; + +/** + * Function: getId + * + * Returns the Id of the cell as a string. + */ +mxCell.prototype.getId = function() +{ + return this.id; +}; + +/** + * Function: setId + * + * Sets the Id of the cell to the given string. + */ +mxCell.prototype.setId = function(id) +{ + this.id = id; +}; + +/** + * Function: getValue + * + * Returns the user object of the cell. The user + * object is stored in <value>. + */ +mxCell.prototype.getValue = function() +{ + return this.value; +}; + +/** + * Function: setValue + * + * Sets the user object of the cell. The user object + * is stored in <value>. + */ +mxCell.prototype.setValue = function(value) +{ + this.value = value; +}; + +/** + * Function: valueChanged + * + * Changes the user object after an in-place edit + * and returns the previous value. This implementation + * replaces the user object with the given value and + * returns the old user object. + */ +mxCell.prototype.valueChanged = function(newValue) +{ + var previous = this.getValue(); + this.setValue(newValue); + + return previous; +}; + +/** + * Function: getGeometry + * + * Returns the <mxGeometry> that describes the <geometry>. + */ +mxCell.prototype.getGeometry = function() +{ + return this.geometry; +}; + +/** + * Function: setGeometry + * + * Sets the <mxGeometry> to be used as the <geometry>. + */ +mxCell.prototype.setGeometry = function(geometry) +{ + this.geometry = geometry; +}; + +/** + * Function: getStyle + * + * Returns a string that describes the <style>. + */ +mxCell.prototype.getStyle = function() +{ + return this.style; +}; + +/** + * Function: setStyle + * + * Sets the string to be used as the <style>. + */ +mxCell.prototype.setStyle = function(style) +{ + this.style = style; +}; + +/** + * Function: isVertex + * + * Returns true if the cell is a vertex. + */ +mxCell.prototype.isVertex = function() +{ + return this.vertex; +}; + +/** + * Function: setVertex + * + * Specifies if the cell is a vertex. This should only be assigned at + * construction of the cell and not be changed during its lifecycle. + * + * Parameters: + * + * vertex - Boolean that specifies if the cell is a vertex. + */ +mxCell.prototype.setVertex = function(vertex) +{ + this.vertex = vertex; +}; + +/** + * Function: isEdge + * + * Returns true if the cell is an edge. + */ +mxCell.prototype.isEdge = function() +{ + return this.edge; +}; + +/** + * Function: setEdge + * + * Specifies if the cell is an edge. This should only be assigned at + * construction of the cell and not be changed during its lifecycle. + * + * Parameters: + * + * edge - Boolean that specifies if the cell is an edge. + */ +mxCell.prototype.setEdge = function(edge) +{ + this.edge = edge; +}; + +/** + * Function: isConnectable + * + * Returns true if the cell is connectable. + */ +mxCell.prototype.isConnectable = function() +{ + return this.connectable; +}; + +/** + * Function: setConnectable + * + * Sets the connectable state. + * + * Parameters: + * + * connectable - Boolean that specifies the new connectable state. + */ +mxCell.prototype.setConnectable = function(connectable) +{ + this.connectable = connectable; +}; + +/** + * Function: isVisible + * + * Returns true if the cell is visibile. + */ +mxCell.prototype.isVisible = function() +{ + return this.visible; +}; + +/** + * Function: setVisible + * + * Specifies if the cell is visible. + * + * Parameters: + * + * visible - Boolean that specifies the new visible state. + */ +mxCell.prototype.setVisible = function(visible) +{ + this.visible = visible; +}; + +/** + * Function: isCollapsed + * + * Returns true if the cell is collapsed. + */ +mxCell.prototype.isCollapsed = function() +{ + return this.collapsed; +}; + +/** + * Function: setCollapsed + * + * Sets the collapsed state. + * + * Parameters: + * + * collapsed - Boolean that specifies the new collapsed state. + */ +mxCell.prototype.setCollapsed = function(collapsed) +{ + this.collapsed = collapsed; +}; + +/** + * Function: getParent + * + * Returns the cell's parent. + */ +mxCell.prototype.getParent = function() +{ + return this.parent; +}; + +/** + * Function: setParent + * + * Sets the parent cell. + * + * Parameters: + * + * parent - <mxCell> that represents the new parent. + */ +mxCell.prototype.setParent = function(parent) +{ + this.parent = parent; +}; + +/** + * Function: getTerminal + * + * Returns the source or target terminal. + * + * Parameters: + * + * source - Boolean that specifies if the source terminal should be + * returned. + */ +mxCell.prototype.getTerminal = function(source) +{ + return (source) ? this.source : this.target; +}; + +/** + * Function: setTerminal + * + * Sets the source or target terminal and returns the new terminal. + * + * Parameters: + * + * terminal - <mxCell> that represents the new source or target terminal. + * isSource - Boolean that specifies if the source or target terminal + * should be set. + */ +mxCell.prototype.setTerminal = function(terminal, isSource) +{ + if (isSource) + { + this.source = terminal; + } + else + { + this.target = terminal; + } + + return terminal; +}; + +/** + * Function: getChildCount + * + * Returns the number of child cells. + */ +mxCell.prototype.getChildCount = function() +{ + return (this.children == null) ? 0 : this.children.length; +}; + +/** + * Function: getIndex + * + * Returns the index of the specified child in the child array. + * + * Parameters: + * + * child - Child whose index should be returned. + */ +mxCell.prototype.getIndex = function(child) +{ + return mxUtils.indexOf(this.children, child); +}; + +/** + * Function: getChildAt + * + * Returns the child at the specified index. + * + * Parameters: + * + * index - Integer that specifies the child to be returned. + */ +mxCell.prototype.getChildAt = function(index) +{ + return (this.children == null) ? null : this.children[index]; +}; + +/** + * Function: insert + * + * Inserts the specified child into the child array at the specified index + * and updates the parent reference of the child. If not childIndex is + * specified then the child is appended to the child array. Returns the + * inserted child. + * + * Parameters: + * + * child - <mxCell> to be inserted or appended to the child array. + * index - Optional integer that specifies the index at which the child + * should be inserted into the child array. + */ +mxCell.prototype.insert = function(child, index) +{ + if (child != null) + { + if (index == null) + { + index = this.getChildCount(); + + if (child.getParent() == this) + { + index--; + } + } + + child.removeFromParent(); + child.setParent(this); + + if (this.children == null) + { + this.children = []; + this.children.push(child); + } + else + { + this.children.splice(index, 0, child); + } + } + + return child; +}; + +/** + * Function: remove + * + * Removes the child at the specified index from the child array and + * returns the child that was removed. Will remove the parent reference of + * the child. + * + * Parameters: + * + * index - Integer that specifies the index of the child to be + * removed. + */ +mxCell.prototype.remove = function(index) +{ + var child = null; + + if (this.children != null && index >= 0) + { + child = this.getChildAt(index); + + if (child != null) + { + this.children.splice(index, 1); + child.setParent(null); + } + } + + return child; +}; + +/** + * Function: removeFromParent + * + * Removes the cell from its parent. + */ +mxCell.prototype.removeFromParent = function() +{ + if (this.parent != null) + { + var index = this.parent.getIndex(this); + this.parent.remove(index); + } +}; + +/** + * Function: getEdgeCount + * + * Returns the number of edges in the edge array. + */ +mxCell.prototype.getEdgeCount = function() +{ + return (this.edges == null) ? 0 : this.edges.length; +}; + +/** + * Function: getEdgeIndex + * + * Returns the index of the specified edge in <edges>. + * + * Parameters: + * + * edge - <mxCell> whose index in <edges> should be returned. + */ +mxCell.prototype.getEdgeIndex = function(edge) +{ + return mxUtils.indexOf(this.edges, edge); +}; + +/** + * Function: getEdgeAt + * + * Returns the edge at the specified index in <edges>. + * + * Parameters: + * + * index - Integer that specifies the index of the edge to be returned. + */ +mxCell.prototype.getEdgeAt = function(index) +{ + return (this.edges == null) ? null : this.edges[index]; +}; + +/** + * Function: insertEdge + * + * Inserts the specified edge into the edge array and returns the edge. + * Will update the respective terminal reference of the edge. + * + * Parameters: + * + * edge - <mxCell> to be inserted into the edge array. + * isOutgoing - Boolean that specifies if the edge is outgoing. + */ +mxCell.prototype.insertEdge = function(edge, isOutgoing) +{ + if (edge != null) + { + edge.removeFromTerminal(isOutgoing); + edge.setTerminal(this, isOutgoing); + + if (this.edges == null || + edge.getTerminal(!isOutgoing) != this || + mxUtils.indexOf(this.edges, edge) < 0) + { + if (this.edges == null) + { + this.edges = []; + } + + this.edges.push(edge); + } + } + + return edge; +}; + +/** + * Function: removeEdge + * + * Removes the specified edge from the edge array and returns the edge. + * Will remove the respective terminal reference from the edge. + * + * Parameters: + * + * edge - <mxCell> to be removed from the edge array. + * isOutgoing - Boolean that specifies if the edge is outgoing. + */ +mxCell.prototype.removeEdge = function(edge, isOutgoing) +{ + if (edge != null) + { + if (edge.getTerminal(!isOutgoing) != this && + this.edges != null) + { + var index = this.getEdgeIndex(edge); + + if (index >= 0) + { + this.edges.splice(index, 1); + } + } + + edge.setTerminal(null, isOutgoing); + } + + return edge; +}; + +/** + * Function: removeFromTerminal + * + * Removes the edge from its source or target terminal. + * + * Parameters: + * + * isSource - Boolean that specifies if the edge should be removed from its + * source or target terminal. + */ +mxCell.prototype.removeFromTerminal = function(isSource) +{ + var terminal = this.getTerminal(isSource); + + if (terminal != null) + { + terminal.removeEdge(this, isSource); + } +}; + +/** + * Function: getAttribute + * + * Returns the specified attribute from the user object if it is an XML + * node. + * + * Parameters: + * + * name - Name of the attribute whose value should be returned. + * defaultValue - Optional default value to use if the attribute has no + * value. + */ +mxCell.prototype.getAttribute = function(name, defaultValue) +{ + var userObject = this.getValue(); + + var val = (userObject != null && + userObject.nodeType == mxConstants.NODETYPE_ELEMENT) ? + userObject.getAttribute(name) : null; + + return val || defaultValue; +}; + +/** + * Function: setAttribute + * + * Sets the specified attribute on the user object if it is an XML node. + * + * Parameters: + * + * name - Name of the attribute whose value should be set. + * value - New value of the attribute. + */ +mxCell.prototype.setAttribute = function(name, value) +{ + var userObject = this.getValue(); + + if (userObject != null && + userObject.nodeType == mxConstants.NODETYPE_ELEMENT) + { + userObject.setAttribute(name, value); + } +}; + +/** + * Function: clone + * + * Returns a clone of the cell. Uses <cloneValue> to clone + * the user object. All fields in <mxTransient> are ignored + * during the cloning. + */ +mxCell.prototype.clone = function() +{ + var clone = mxUtils.clone(this, this.mxTransient); + clone.setValue(this.cloneValue()); + + return clone; +}; + +/** + * Function: cloneValue + * + * Returns a clone of the cell's user object. + */ +mxCell.prototype.cloneValue = function() +{ + var value = this.getValue(); + + if (value != null) + { + if (typeof(value.clone) == 'function') + { + value = value.clone(); + } + else if (!isNaN(value.nodeType)) + { + value = value.cloneNode(true); + } + } + + return value; +}; diff --git a/src/js/model/mxCellPath.js b/src/js/model/mxCellPath.js new file mode 100644 index 0000000..71a379e --- /dev/null +++ b/src/js/model/mxCellPath.js @@ -0,0 +1,163 @@ +/** + * $Id: mxCellPath.js,v 1.12 2010-01-02 09:45:15 gaudenz Exp $ + * Copyright (c) 2006-2010, JGraph Ltd + */ +var mxCellPath = +{ + + /** + * Class: mxCellPath + * + * Implements a mechanism for temporary cell Ids. + * + * Variable: PATH_SEPARATOR + * + * Defines the separator between the path components. Default is ".". + */ + PATH_SEPARATOR: '.', + + /** + * Function: create + * + * Creates the cell path for the given cell. The cell path is a + * concatenation of the indices of all ancestors on the (finite) path to + * the root, eg. "0.0.0.1". + * + * Parameters: + * + * cell - Cell whose path should be returned. + */ + create: function(cell) + { + var result = ''; + + if (cell != null) + { + var parent = cell.getParent(); + + while (parent != null) + { + var index = parent.getIndex(cell); + result = index + mxCellPath.PATH_SEPARATOR + result; + + cell = parent; + parent = cell.getParent(); + } + } + + // Removes trailing separator + var n = result.length; + + if (n > 1) + { + result = result.substring(0, n - 1); + } + + return result; + }, + + /** + * Function: getParentPath + * + * Returns the path for the parent of the cell represented by the given + * path. Returns null if the given path has no parent. + * + * Parameters: + * + * path - Path whose parent path should be returned. + */ + getParentPath: function(path) + { + if (path != null) + { + var index = path.lastIndexOf(mxCellPath.PATH_SEPARATOR); + + if (index >= 0) + { + return path.substring(0, index); + } + else if (path.length > 0) + { + return ''; + } + } + + return null; + }, + + /** + * Function: resolve + * + * Returns the cell for the specified cell path using the given root as the + * root of the path. + * + * Parameters: + * + * root - Root cell of the path to be resolved. + * path - String that defines the path. + */ + resolve: function(root, path) + { + var parent = root; + + if (path != null) + { + var tokens = path.split(mxCellPath.PATH_SEPARATOR); + + for (var i=0; i<tokens.length; i++) + { + parent = parent.getChildAt(parseInt(tokens[i])); + } + } + + return parent; + }, + + /** + * Function: compare + * + * Compares the given cell paths and returns -1 if p1 is smaller, 0 if + * p1 is equal and 1 if p1 is greater than p2. + */ + compare: function(p1, p2) + { + var min = Math.min(p1.length, p2.length); + var comp = 0; + + for (var i = 0; i < min; i++) + { + if (p1[i] != p2[i]) + { + if (p1[i].length == 0 || + p2[i].length == 0) + { + comp = (p1[i] == p2[i]) ? 0 : ((p1[i] > p2[i]) ? 1 : -1); + } + else + { + var t1 = parseInt(p1[i]); + var t2 = parseInt(p2[i]); + + comp = (t1 == t2) ? 0 : ((t1 > t2) ? 1 : -1); + } + + break; + } + } + + // Compares path length if both paths are equal to this point + if (comp == 0) + { + var t1 = p1.length; + var t2 = p2.length; + + if (t1 != t2) + { + comp = (t1 > t2) ? 1 : -1; + } + } + + return comp; + } + +}; diff --git a/src/js/model/mxGeometry.js b/src/js/model/mxGeometry.js new file mode 100644 index 0000000..51a7d3b --- /dev/null +++ b/src/js/model/mxGeometry.js @@ -0,0 +1,277 @@ +/** + * $Id: mxGeometry.js,v 1.26 2010-01-02 09:45:15 gaudenz Exp $ + * Copyright (c) 2006-2010, JGraph Ltd + */ +/** + * Class: mxGeometry + * + * Extends <mxRectangle> to represent the geometry of a cell. + * + * For vertices, the geometry consists of the x- and y-location, and the width + * and height. For edges, the geometry consists of the optional terminal- and + * control points. The terminal points are only required if an edge is + * unconnected, and are stored in the sourcePoint> and <targetPoint> + * variables, respectively. + * + * Example: + * + * If an edge is unconnected, that is, it has no source or target terminal, + * then a geometry with terminal points for a new edge can be defined as + * follows. + * + * (code) + * geometry.setTerminalPoint(new mxPoint(x1, y1), true); + * geometry.points = [new mxPoint(x2, y2)]; + * geometry.setTerminalPoint(new mxPoint(x3, y3), false); + * (end) + * + * Control points are used regardless of the connected state of an edge and may + * be ignored or interpreted differently depending on the edge's <mxEdgeStyle>. + * + * To disable automatic reset of control points after a cell has been moved or + * resized, the the <mxGraph.resizeEdgesOnMove> and + * <mxGraph.resetEdgesOnResize> may be used. + * + * Edge Labels: + * + * Using the x- and y-coordinates of a cell's geometry, it is possible to + * position the label on edges on a specific location on the actual edge shape + * as it appears on the screen. The x-coordinate of an edge's geometry is used + * to describe the distance from the center of the edge from -1 to 1 with 0 + * being the center of the edge and the default value. The y-coordinate of an + * edge's geometry is used to describe the absolute, orthogonal distance in + * pixels from that point. In addition, the <mxGeometry.offset> is used as an + * absolute offset vector from the resulting point. + * + * This coordinate system is applied if <relative> is true, otherwise the + * offset defines the absolute vector from the edge's center point to the + * label. + * + * Ports: + * + * The term "port" refers to a relatively positioned, connectable child cell, + * which is used to specify the connection between the parent and another cell + * in the graph. Ports are typically modeled as vertices with relative + * geometries. + * + * Offsets: + * + * The <offset> field is interpreted in 3 different ways, depending on the cell + * and the geometry. For edges, the offset defines the absolute offset for the + * edge label. For relative geometries, the offset defines the absolute offset + * for the origin (top, left corner) of the vertex, otherwise the offset + * defines the absolute offset for the label inside the vertex or group. + * + * Constructor: mxGeometry + * + * Constructs a new object to describe the size and location of a vertex or + * the control points of an edge. + */ +function mxGeometry(x, y, width, height) +{ + mxRectangle.call(this, x, y, width, height); +}; + +/** + * Extends mxRectangle. + */ +mxGeometry.prototype = new mxRectangle(); +mxGeometry.prototype.constructor = mxGeometry; + +/** + * Variable: TRANSLATE_CONTROL_POINTS + * + * Global switch to translate the points in translate. Default is true. + */ +mxGeometry.prototype.TRANSLATE_CONTROL_POINTS = true; + +/** + * Variable: alternateBounds + * + * Stores alternate values for x, y, width and height in a rectangle. See + * <swap> to exchange the values. Default is null. + */ +mxGeometry.prototype.alternateBounds = null; + +/** + * Variable: sourcePoint + * + * Defines the source <mxPoint> of the edge. This is used if the + * corresponding edge does not have a source vertex. Otherwise it is + * ignored. Default is null. + */ +mxGeometry.prototype.sourcePoint = null; + +/** + * Variable: targetPoint + * + * Defines the target <mxPoint> of the edge. This is used if the + * corresponding edge does not have a target vertex. Otherwise it is + * ignored. Default is null. + */ +mxGeometry.prototype.targetPoint = null; + +/** + * Variable: points + * + * Array of <mxPoints> which specifies the control points along the edge. + * These points are the intermediate points on the edge, for the endpoints + * use <targetPoint> and <sourcePoint> or set the terminals of the edge to + * a non-null value. Default is null. + */ +mxGeometry.prototype.points = null; + +/** + * Variable: offset + * + * For edges, this holds the offset (in pixels) from the position defined + * by <x> and <y> on the edge. For relative geometries (for vertices), this + * defines the absolute offset from the point defined by the relative + * coordinates. For absolute geometries (for vertices), this defines the + * offset for the label. Default is null. + */ +mxGeometry.prototype.offset = null; + +/** + * Variable: relative + * + * Specifies if the coordinates in the geometry are to be interpreted as + * relative coordinates. For edges, this is used to define the location of + * the edge label relative to the edge as rendered on the display. For + * vertices, this specifies the relative location inside the bounds of the + * parent cell. + * + * If this is false, then the coordinates are relative to the origin of the + * parent cell or, for edges, the edge label position is relative to the + * center of the edge as rendered on screen. + * + * Default is false. + */ +mxGeometry.prototype.relative = false; + +/** + * Function: swap + * + * Swaps the x, y, width and height with the values stored in + * <alternateBounds> and puts the previous values into <alternateBounds> as + * a rectangle. This operation is carried-out in-place, that is, using the + * existing geometry instance. If this operation is called during a graph + * model transactional change, then the geometry should be cloned before + * calling this method and setting the geometry of the cell using + * <mxGraphModel.setGeometry>. + */ +mxGeometry.prototype.swap = function() +{ + if (this.alternateBounds != null) + { + var old = new mxRectangle( + this.x, this.y, this.width, this.height); + + this.x = this.alternateBounds.x; + this.y = this.alternateBounds.y; + this.width = this.alternateBounds.width; + this.height = this.alternateBounds.height; + + this.alternateBounds = old; + } +}; + +/** + * Function: getTerminalPoint + * + * Returns the <mxPoint> representing the source or target point of this + * edge. This is only used if the edge has no source or target vertex. + * + * Parameters: + * + * isSource - Boolean that specifies if the source or target point + * should be returned. + */ +mxGeometry.prototype.getTerminalPoint = function(isSource) +{ + return (isSource) ? this.sourcePoint : this.targetPoint; +}; + +/** + * Function: setTerminalPoint + * + * Sets the <sourcePoint> or <targetPoint> to the given <mxPoint> and + * returns the new point. + * + * Parameters: + * + * point - Point to be used as the new source or target point. + * isSource - Boolean that specifies if the source or target point + * should be set. + */ +mxGeometry.prototype.setTerminalPoint = function(point, isSource) +{ + if (isSource) + { + this.sourcePoint = point; + } + else + { + this.targetPoint = point; + } + + return point; +}; + +/** + * Function: translate + * + * Translates the geometry by the specified amount. That is, <x> and <y> + * of the geometry, the <sourcePoint>, <targetPoint> and all elements of + * <points> are translated by the given amount. <x> and <y> are only + * translated if <relative> is false. If <TRANSLATE_CONTROL_POINTS> is + * false, then <points> are not modified by this function. + * + * Parameters: + * + * dx - Integer that specifies the x-coordinate of the translation. + * dy - Integer that specifies the y-coordinate of the translation. + */ +mxGeometry.prototype.translate = function(dx, dy) +{ + var clone = this.clone(); + + // Translates the geometry + if (!this.relative) + { + this.x += dx; + this.y += dy; + } + + // Translates the source point + if (this.sourcePoint != null) + { + this.sourcePoint.x += dx; + this.sourcePoint.y += dy; + } + + // Translates the target point + if (this.targetPoint != null) + { + this.targetPoint.x += dx; + this.targetPoint.y += dy; + } + + // Translate the control points + if (this.TRANSLATE_CONTROL_POINTS && + this.points != null) + { + var count = this.points.length; + + for (var i = 0; i < count; i++) + { + var pt = this.points[i]; + + if (pt != null) + { + pt.x += dx; + pt.y += dy; + } + } + } +}; diff --git a/src/js/model/mxGraphModel.js b/src/js/model/mxGraphModel.js new file mode 100644 index 0000000..c65c0e1 --- /dev/null +++ b/src/js/model/mxGraphModel.js @@ -0,0 +1,2622 @@ +/** + * $Id: mxGraphModel.js,v 1.125 2012-04-16 10:48:43 david Exp $ + * Copyright (c) 2006-2010, JGraph Ltd + */ +/** + * Class: mxGraphModel + * + * Extends <mxEventSource> to implement a graph model. The graph model acts as + * a wrapper around the cells which are in charge of storing the actual graph + * datastructure. The model acts as a transactional wrapper with event + * notification for all changes, whereas the cells contain the atomic + * operations for updating the actual datastructure. + * + * Layers: + * + * The cell hierarchy in the model must have a top-level root cell which + * contains the layers (typically one default layer), which in turn contain the + * top-level cells of the layers. This means each cell is contained in a layer. + * If no layers are required, then all new cells should be added to the default + * layer. + * + * Layers are useful for hiding and showing groups of cells, or for placing + * groups of cells on top of other cells in the display. To identify a layer, + * the <isLayer> function is used. It returns true if the parent of the given + * cell is the root of the model. + * + * Encoding the model: + * + * To encode a graph model, use the following code: + * + * (code) + * var enc = new mxCodec(); + * var node = enc.encode(graph.getModel()); + * (end) + * + * This will create an XML node that contains all the model information. + * + * Encoding and decoding changes: + * + * For the encoding of changes, a graph model listener is required that encodes + * each change from the given array of changes. + * + * (code) + * model.addListener(mxEvent.CHANGE, function(sender, evt) + * { + * var changes = evt.getProperty('edit').changes; + * var nodes = []; + * var codec = new mxCodec(); + * + * for (var i = 0; i < changes.length; i++) + * { + * nodes.push(codec.encode(changes[i])); + * } + * // do something with the nodes + * }); + * (end) + * + * For the decoding and execution of changes, the codec needs a lookup function + * that allows it to resolve cell IDs as follows: + * + * (code) + * var codec = new mxCodec(); + * codec.lookup = function(id) + * { + * return model.getCell(id); + * } + * (end) + * + * For each encoded change (represented by a node), the following code can be + * used to carry out the decoding and create a change object. + * + * (code) + * var changes = []; + * var change = codec.decode(node); + * change.model = model; + * change.execute(); + * changes.push(change); + * (end) + * + * The changes can then be dispatched using the model as follows. + * + * (code) + * var edit = new mxUndoableEdit(model, false); + * edit.changes = changes; + * + * edit.notify = function() + * { + * edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE, + * 'edit', edit, 'changes', edit.changes)); + * edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY, + * 'edit', edit, 'changes', edit.changes)); + * } + * + * model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit)); + * model.fireEvent(new mxEventObject(mxEvent.CHANGE, + * 'edit', edit, 'changes', changes)); + * (end) + * + * Event: mxEvent.CHANGE + * + * Fires when an undoable edit is dispatched. The <code>edit</code> property + * contains the <mxUndoableEdit>. The <code>changes</code> property contains + * the array of atomic changes inside the undoable edit. The changes property + * is <strong>deprecated</strong>, please use edit.changes instead. + * + * Example: + * + * For finding newly inserted cells, the following code can be used: + * + * (code) + * graph.model.addListener(mxEvent.CHANGE, function(sender, evt) + * { + * var changes = evt.getProperty('edit').changes; + * + * for (var i = 0; i < changes.length; i++) + * { + * var change = changes[i]; + * + * if (change instanceof mxChildChange && + * change.change.previous == null) + * { + * graph.startEditingAtCell(change.child); + * break; + * } + * } + * }); + * (end) + * + * + * Event: mxEvent.NOTIFY + * + * Same as <mxEvent.CHANGE>, this event can be used for classes that need to + * implement a sync mechanism between this model and, say, a remote model. In + * such a setup, only local changes should trigger a notify event and all + * changes should trigger a change event. + * + * Event: mxEvent.EXECUTE + * + * Fires between begin- and endUpdate and after an atomic change was executed + * in the model. The <code>change</code> property contains the atomic change + * that was executed. + * + * Event: mxEvent.BEGIN_UPDATE + * + * Fires after the <updateLevel> was incremented in <beginUpdate>. This event + * contains no properties. + * + * Event: mxEvent.END_UPDATE + * + * Fires after the <updateLevel> was decreased in <endUpdate> but before any + * notification or change dispatching. The <code>edit</code> property contains + * the <currentEdit>. + * + * Event: mxEvent.BEFORE_UNDO + * + * Fires before the change is dispatched after the update level has reached 0 + * in <endUpdate>. The <code>edit</code> property contains the <curreneEdit>. + * + * Event: mxEvent.UNDO + * + * Fires after the change was dispatched in <endUpdate>. The <code>edit</code> + * property contains the <currentEdit>. + * + * Constructor: mxGraphModel + * + * Constructs a new graph model. If no root is specified then a new root + * <mxCell> with a default layer is created. + * + * Parameters: + * + * root - <mxCell> that represents the root cell. + */ +function mxGraphModel(root) +{ + this.currentEdit = this.createUndoableEdit(); + + if (root != null) + { + this.setRoot(root); + } + else + { + this.clear(); + } +}; + +/** + * Extends mxEventSource. + */ +mxGraphModel.prototype = new mxEventSource(); +mxGraphModel.prototype.constructor = mxGraphModel; + +/** + * Variable: root + * + * Holds the root cell, which in turn contains the cells that represent the + * layers of the diagram as child cells. That is, the actual elements of the + * diagram are supposed to live in the third generation of cells and below. + */ +mxGraphModel.prototype.root = null; + +/** + * Variable: cells + * + * Maps from Ids to cells. + */ +mxGraphModel.prototype.cells = null; + +/** + * Variable: maintainEdgeParent + * + * Specifies if edges should automatically be moved into the nearest common + * ancestor of their terminals. Default is true. + */ +mxGraphModel.prototype.maintainEdgeParent = true; + +/** + * Variable: createIds + * + * Specifies if the model should automatically create Ids for new cells. + * Default is true. + */ +mxGraphModel.prototype.createIds = true; + +/** + * Variable: prefix + * + * Defines the prefix of new Ids. Default is an empty string. + */ +mxGraphModel.prototype.prefix = ''; + +/** + * Variable: postfix + * + * Defines the postfix of new Ids. Default is an empty string. + */ +mxGraphModel.prototype.postfix = ''; + +/** + * Variable: nextId + * + * Specifies the next Id to be created. Initial value is 0. + */ +mxGraphModel.prototype.nextId = 0; + +/** + * Variable: currentEdit + * + * Holds the changes for the current transaction. If the transaction is + * closed then a new object is created for this variable using + * <createUndoableEdit>. + */ +mxGraphModel.prototype.currentEdit = null; + +/** + * Variable: updateLevel + * + * Counter for the depth of nested transactions. Each call to <beginUpdate> + * will increment this number and each call to <endUpdate> will decrement + * it. When the counter reaches 0, the transaction is closed and the + * respective events are fired. Initial value is 0. + */ +mxGraphModel.prototype.updateLevel = 0; + +/** + * Variable: endingUpdate + * + * True if the program flow is currently inside endUpdate. + */ +mxGraphModel.prototype.endingUpdate = false; + +/** + * Function: clear + * + * Sets a new root using <createRoot>. + */ +mxGraphModel.prototype.clear = function() +{ + this.setRoot(this.createRoot()); +}; + +/** + * Function: isCreateIds + * + * Returns <createIds>. + */ +mxGraphModel.prototype.isCreateIds = function() +{ + return this.createIds; +}; + +/** + * Function: setCreateIds + * + * Sets <createIds>. + */ +mxGraphModel.prototype.setCreateIds = function(value) +{ + this.createIds = value; +}; + +/** + * Function: createRoot + * + * Creates a new root cell with a default layer (child 0). + */ +mxGraphModel.prototype.createRoot = function() +{ + var cell = new mxCell(); + cell.insert(new mxCell()); + + return cell; +}; + +/** + * Function: getCell + * + * Returns the <mxCell> for the specified Id or null if no cell can be + * found for the given Id. + * + * Parameters: + * + * id - A string representing the Id of the cell. + */ +mxGraphModel.prototype.getCell = function(id) +{ + return (this.cells != null) ? this.cells[id] : null; +}; + +/** + * Function: filterCells + * + * Returns the cells from the given array where the fiven filter function + * returns true. + */ +mxGraphModel.prototype.filterCells = function(cells, filter) +{ + var result = null; + + if (cells != null) + { + result = []; + + for (var i = 0; i < cells.length; i++) + { + if (filter(cells[i])) + { + result.push(cells[i]); + } + } + } + + return result; +}; + +/** + * Function: getDescendants + * + * Returns all descendants of the given cell and the cell itself in an array. + * + * Parameters: + * + * parent - <mxCell> whose descendants should be returned. + */ +mxGraphModel.prototype.getDescendants = function(parent) +{ + return this.filterDescendants(null, parent); +}; + +/** + * Function: filterDescendants + * + * Visits all cells recursively and applies the specified filter function + * to each cell. If the function returns true then the cell is added + * to the resulting array. The parent and result paramters are optional. + * If parent is not specified then the recursion starts at <root>. + * + * Example: + * The following example extracts all vertices from a given model: + * (code) + * var filter = function(cell) + * { + * return model.isVertex(cell); + * } + * var vertices = model.filterDescendants(filter); + * (code) + * + * Parameters: + * + * filter - JavaScript function that takes an <mxCell> as an argument + * and returns a boolean. + * parent - Optional <mxCell> that is used as the root of the recursion. + */ +mxGraphModel.prototype.filterDescendants = function(filter, parent) +{ + // Creates a new array for storing the result + var result = []; + + // Recursion starts at the root of the model + parent = parent || this.getRoot(); + + // Checks if the filter returns true for the cell + // and adds it to the result array + if (filter == null || filter(parent)) + { + result.push(parent); + } + + // Visits the children of the cell + var childCount = this.getChildCount(parent); + + for (var i = 0; i < childCount; i++) + { + var child = this.getChildAt(parent, i); + result = result.concat(this.filterDescendants(filter, child)); + } + + return result; +}; + +/** + * Function: getRoot + * + * Returns the root of the model or the topmost parent of the given cell. + * + * Parameters: + * + * cell - Optional <mxCell> that specifies the child. + */ +mxGraphModel.prototype.getRoot = function(cell) +{ + var root = cell || this.root; + + if (cell != null) + { + while (cell != null) + { + root = cell; + cell = this.getParent(cell); + } + } + + return root; +}; + +/** + * Function: setRoot + * + * Sets the <root> of the model using <mxRootChange> and adds the change to + * the current transaction. This resets all datastructures in the model and + * is the preferred way of clearing an existing model. Returns the new + * root. + * + * Example: + * + * (code) + * var root = new mxCell(); + * root.insert(new mxCell()); + * model.setRoot(root); + * (end) + * + * Parameters: + * + * root - <mxCell> that specifies the new root. + */ +mxGraphModel.prototype.setRoot = function(root) +{ + this.execute(new mxRootChange(this, root)); + + return root; +}; + +/** + * Function: rootChanged + * + * Inner callback to change the root of the model and update the internal + * datastructures, such as <cells> and <nextId>. Returns the previous root. + * + * Parameters: + * + * root - <mxCell> that specifies the new root. + */ +mxGraphModel.prototype.rootChanged = function(root) +{ + var oldRoot = this.root; + this.root = root; + + // Resets counters and datastructures + this.nextId = 0; + this.cells = null; + this.cellAdded(root); + + return oldRoot; +}; + +/** + * Function: isRoot + * + * Returns true if the given cell is the root of the model and a non-null + * value. + * + * Parameters: + * + * cell - <mxCell> that represents the possible root. + */ +mxGraphModel.prototype.isRoot = function(cell) +{ + return cell != null && this.root == cell; +}; + +/** + * Function: isLayer + * + * Returns true if <isRoot> returns true for the parent of the given cell. + * + * Parameters: + * + * cell - <mxCell> that represents the possible layer. + */ +mxGraphModel.prototype.isLayer = function(cell) +{ + return this.isRoot(this.getParent(cell)); +}; + +/** + * Function: isAncestor + * + * Returns true if the given parent is an ancestor of the given child. + * + * Parameters: + * + * parent - <mxCell> that specifies the parent. + * child - <mxCell> that specifies the child. + */ +mxGraphModel.prototype.isAncestor = function(parent, child) +{ + while (child != null && child != parent) + { + child = this.getParent(child); + } + + return child == parent; +}; + +/** + * Function: contains + * + * Returns true if the model contains the given <mxCell>. + * + * Parameters: + * + * cell - <mxCell> that specifies the cell. + */ +mxGraphModel.prototype.contains = function(cell) +{ + return this.isAncestor(this.root, cell); +}; + +/** + * Function: getParent + * + * Returns the parent of the given cell. + * + * Parameters: + * + * cell - <mxCell> whose parent should be returned. + */ +mxGraphModel.prototype.getParent = function(cell) +{ + return (cell != null) ? cell.getParent() : null; +}; + +/** + * Function: add + * + * Adds the specified child to the parent at the given index using + * <mxChildChange> and adds the change to the current transaction. If no + * index is specified then the child is appended to the parent's array of + * children. Returns the inserted child. + * + * Parameters: + * + * parent - <mxCell> that specifies the parent to contain the child. + * child - <mxCell> that specifies the child to be inserted. + * index - Optional integer that specifies the index of the child. + */ +mxGraphModel.prototype.add = function(parent, child, index) +{ + if (child != parent && parent != null && child != null) + { + // Appends the child if no index was specified + if (index == null) + { + index = this.getChildCount(parent); + } + + var parentChanged = parent != this.getParent(child); + this.execute(new mxChildChange(this, parent, child, index)); + + // Maintains the edges parents by moving the edges + // into the nearest common ancestor of its + // terminals + if (this.maintainEdgeParent && parentChanged) + { + this.updateEdgeParents(child); + } + } + + return child; +}; + +/** + * Function: cellAdded + * + * Inner callback to update <cells> when a cell has been added. This + * implementation resolves collisions by creating new Ids. To change the + * ID of a cell after it was inserted into the model, use the following + * code: + * + * (code + * delete model.cells[cell.getId()]; + * cell.setId(newId); + * model.cells[cell.getId()] = cell; + * (end) + * + * If the change of the ID should be part of the command history, then the + * cell should be removed from the model and a clone with the new ID should + * be reinserted into the model instead. + * + * Parameters: + * + * cell - <mxCell> that specifies the cell that has been added. + */ +mxGraphModel.prototype.cellAdded = function(cell) +{ + if (cell != null) + { + // Creates an Id for the cell if not Id exists + if (cell.getId() == null && this.createIds) + { + cell.setId(this.createId(cell)); + } + + if (cell.getId() != null) + { + var collision = this.getCell(cell.getId()); + + if (collision != cell) + { + // Creates new Id for the cell + // as long as there is a collision + while (collision != null) + { + cell.setId(this.createId(cell)); + collision = this.getCell(cell.getId()); + } + + // Lazily creates the cells dictionary + if (this.cells == null) + { + this.cells = new Object(); + } + + this.cells[cell.getId()] = cell; + } + } + + // Makes sure IDs of deleted cells are not reused + if (mxUtils.isNumeric(cell.getId())) + { + this.nextId = Math.max(this.nextId, cell.getId()); + } + + // Recursively processes child cells + var childCount = this.getChildCount(cell); + + for (var i=0; i<childCount; i++) + { + this.cellAdded(this.getChildAt(cell, i)); + } + } +}; + +/** + * Function: createId + * + * Hook method to create an Id for the specified cell. This implementation + * concatenates <prefix>, id and <postfix> to create the Id and increments + * <nextId>. The cell is ignored by this implementation, but can be used in + * overridden methods to prefix the Ids with eg. the cell type. + * + * Parameters: + * + * cell - <mxCell> to create the Id for. + */ +mxGraphModel.prototype.createId = function(cell) +{ + var id = this.nextId; + this.nextId++; + + return this.prefix + id + this.postfix; +}; + +/** + * Function: updateEdgeParents + * + * Updates the parent for all edges that are connected to cell or one of + * its descendants using <updateEdgeParent>. + */ +mxGraphModel.prototype.updateEdgeParents = function(cell, root) +{ + // Gets the topmost node of the hierarchy + root = root || this.getRoot(cell); + + // Updates edges on children first + var childCount = this.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + var child = this.getChildAt(cell, i); + this.updateEdgeParents(child, root); + } + + // Updates the parents of all connected edges + var edgeCount = this.getEdgeCount(cell); + var edges = []; + + for (var i = 0; i < edgeCount; i++) + { + edges.push(this.getEdgeAt(cell, i)); + } + + for (var i = 0; i < edges.length; i++) + { + var edge = edges[i]; + + // Updates edge parent if edge and child have + // a common root node (does not need to be the + // model root node) + if (this.isAncestor(root, edge)) + { + this.updateEdgeParent(edge, root); + } + } +}; + +/** + * Function: updateEdgeParent + * + * Inner callback to update the parent of the specified <mxCell> to the + * nearest-common-ancestor of its two terminals. + * + * Parameters: + * + * edge - <mxCell> that specifies the edge. + * root - <mxCell> that represents the current root of the model. + */ +mxGraphModel.prototype.updateEdgeParent = function(edge, root) +{ + var source = this.getTerminal(edge, true); + var target = this.getTerminal(edge, false); + var cell = null; + + // Uses the first non-relative descendants of the source terminal + while (source != null && !this.isEdge(source) && + source.geometry != null && source.geometry.relative) + { + source = this.getParent(source); + } + + // Uses the first non-relative descendants of the target terminal + while (target != null && !this.isEdge(target) && + target.geometry != null && target.geometry.relative) + { + target = this.getParent(target); + } + + if (this.isAncestor(root, source) && this.isAncestor(root, target)) + { + if (source == target) + { + cell = this.getParent(source); + } + else + { + cell = this.getNearestCommonAncestor(source, target); + } + + if (cell != null && (this.getParent(cell) != this.root || + this.isAncestor(cell, edge)) && this.getParent(edge) != cell) + { + var geo = this.getGeometry(edge); + + if (geo != null) + { + var origin1 = this.getOrigin(this.getParent(edge)); + var origin2 = this.getOrigin(cell); + + var dx = origin2.x - origin1.x; + var dy = origin2.y - origin1.y; + + geo = geo.clone(); + geo.translate(-dx, -dy); + this.setGeometry(edge, geo); + } + + this.add(cell, edge, this.getChildCount(cell)); + } + } +}; + +/** + * Function: getOrigin + * + * Returns the absolute, accumulated origin for the children inside the + * given parent as an <mxPoint>. + */ +mxGraphModel.prototype.getOrigin = function(cell) +{ + var result = null; + + if (cell != null) + { + result = this.getOrigin(this.getParent(cell)); + + if (!this.isEdge(cell)) + { + var geo = this.getGeometry(cell); + + if (geo != null) + { + result.x += geo.x; + result.y += geo.y; + } + } + } + else + { + result = new mxPoint(); + } + + return result; +}; + +/** + * Function: getNearestCommonAncestor + * + * Returns the nearest common ancestor for the specified cells. + * + * Parameters: + * + * cell1 - <mxCell> that specifies the first cell in the tree. + * cell2 - <mxCell> that specifies the second cell in the tree. + */ +mxGraphModel.prototype.getNearestCommonAncestor = function(cell1, cell2) +{ + if (cell1 != null && cell2 != null) + { + // Creates the cell path for the second cell + var path = mxCellPath.create(cell2); + + if (path != null && path.length > 0) + { + // Bubbles through the ancestors of the first + // cell to find the nearest common ancestor. + var cell = cell1; + var current = mxCellPath.create(cell); + + // Inverts arguments + if (path.length < current.length) + { + cell = cell2; + var tmp = current; + current = path; + path = tmp; + } + + while (cell != null) + { + var parent = this.getParent(cell); + + // Checks if the cell path is equal to the beginning of the given cell path + if (path.indexOf(current + mxCellPath.PATH_SEPARATOR) == 0 && parent != null) + { + return cell; + } + + current = mxCellPath.getParentPath(current); + cell = parent; + } + } + } + + return null; +}; + +/** + * Function: remove + * + * Removes the specified cell from the model using <mxChildChange> and adds + * the change to the current transaction. This operation will remove the + * cell and all of its children from the model. Returns the removed cell. + * + * Parameters: + * + * cell - <mxCell> that should be removed. + */ +mxGraphModel.prototype.remove = function(cell) +{ + if (cell == this.root) + { + this.setRoot(null); + } + else if (this.getParent(cell) != null) + { + this.execute(new mxChildChange(this, null, cell)); + } + + return cell; +}; + +/** + * Function: cellRemoved + * + * Inner callback to update <cells> when a cell has been removed. + * + * Parameters: + * + * cell - <mxCell> that specifies the cell that has been removed. + */ +mxGraphModel.prototype.cellRemoved = function(cell) +{ + if (cell != null && this.cells != null) + { + // Recursively processes child cells + var childCount = this.getChildCount(cell); + + for (var i = childCount - 1; i >= 0; i--) + { + this.cellRemoved(this.getChildAt(cell, i)); + } + + // Removes the dictionary entry for the cell + if (this.cells != null && cell.getId() != null) + { + delete this.cells[cell.getId()]; + } + } +}; + +/** + * Function: parentForCellChanged + * + * Inner callback to update the parent of a cell using <mxCell.insert> + * on the parent and return the previous parent. + * + * Parameters: + * + * cell - <mxCell> to update the parent for. + * parent - <mxCell> that specifies the new parent of the cell. + * index - Optional integer that defines the index of the child + * in the parent's child array. + */ +mxGraphModel.prototype.parentForCellChanged = function(cell, parent, index) +{ + var previous = this.getParent(cell); + + if (parent != null) + { + if (parent != previous || previous.getIndex(cell) != index) + { + parent.insert(cell, index); + } + } + else if (previous != null) + { + var oldIndex = previous.getIndex(cell); + previous.remove(oldIndex); + } + + // Checks if the previous parent was already in the + // model and avoids calling cellAdded if it was. + if (!this.contains(previous) && parent != null) + { + this.cellAdded(cell); + } + else if (parent == null) + { + this.cellRemoved(cell); + } + + return previous; +}; + +/** + * Function: getChildCount + * + * Returns the number of children in the given cell. + * + * Parameters: + * + * cell - <mxCell> whose number of children should be returned. + */ +mxGraphModel.prototype.getChildCount = function(cell) +{ + return (cell != null) ? cell.getChildCount() : 0; +}; + +/** + * Function: getChildAt + * + * Returns the child of the given <mxCell> at the given index. + * + * Parameters: + * + * cell - <mxCell> that represents the parent. + * index - Integer that specifies the index of the child to be returned. + */ +mxGraphModel.prototype.getChildAt = function(cell, index) +{ + return (cell != null) ? cell.getChildAt(index) : null; +}; + +/** + * Function: getChildren + * + * Returns all children of the given <mxCell> as an array of <mxCells>. The + * return value should be only be read. + * + * Parameters: + * + * cell - <mxCell> the represents the parent. + */ +mxGraphModel.prototype.getChildren = function(cell) +{ + return (cell != null) ? cell.children : null; +}; + +/** + * Function: getChildVertices + * + * Returns the child vertices of the given parent. + * + * Parameters: + * + * cell - <mxCell> whose child vertices should be returned. + */ +mxGraphModel.prototype.getChildVertices = function(parent) +{ + return this.getChildCells(parent, true, false); +}; + +/** + * Function: getChildEdges + * + * Returns the child edges of the given parent. + * + * Parameters: + * + * cell - <mxCell> whose child edges should be returned. + */ +mxGraphModel.prototype.getChildEdges = function(parent) +{ + return this.getChildCells(parent, false, true); +}; + +/** + * Function: getChildCells + * + * Returns the children of the given cell that are vertices and/or edges + * depending on the arguments. + * + * Parameters: + * + * cell - <mxCell> the represents the parent. + * vertices - Boolean indicating if child vertices should be returned. + * Default is false. + * edges - Boolean indicating if child edges should be returned. + * Default is false. + */ +mxGraphModel.prototype.getChildCells = function(parent, vertices, edges) +{ + vertices = (vertices != null) ? vertices : false; + edges = (edges != null) ? edges : false; + + var childCount = this.getChildCount(parent); + var result = []; + + for (var i = 0; i < childCount; i++) + { + var child = this.getChildAt(parent, i); + + if ((!edges && !vertices) || (edges && this.isEdge(child)) || + (vertices && this.isVertex(child))) + { + result.push(child); + } + } + + return result; +}; + +/** + * Function: getTerminal + * + * Returns the source or target <mxCell> of the given edge depending on the + * value of the boolean parameter. + * + * Parameters: + * + * edge - <mxCell> that specifies the edge. + * isSource - Boolean indicating which end of the edge should be returned. + */ +mxGraphModel.prototype.getTerminal = function(edge, isSource) +{ + return (edge != null) ? edge.getTerminal(isSource) : null; +}; + +/** + * Function: setTerminal + * + * Sets the source or target terminal of the given <mxCell> using + * <mxTerminalChange> and adds the change to the current transaction. + * This implementation updates the parent of the edge using <updateEdgeParent> + * if required. + * + * Parameters: + * + * edge - <mxCell> that specifies the edge. + * terminal - <mxCell> that specifies the new terminal. + * isSource - Boolean indicating if the terminal is the new source or + * target terminal of the edge. + */ +mxGraphModel.prototype.setTerminal = function(edge, terminal, isSource) +{ + var terminalChanged = terminal != this.getTerminal(edge, isSource); + this.execute(new mxTerminalChange(this, edge, terminal, isSource)); + + if (this.maintainEdgeParent && terminalChanged) + { + this.updateEdgeParent(edge, this.getRoot()); + } + + return terminal; +}; + +/** + * Function: setTerminals + * + * Sets the source and target <mxCell> of the given <mxCell> in a single + * transaction using <setTerminal> for each end of the edge. + * + * Parameters: + * + * edge - <mxCell> that specifies the edge. + * source - <mxCell> that specifies the new source terminal. + * target - <mxCell> that specifies the new target terminal. + */ +mxGraphModel.prototype.setTerminals = function(edge, source, target) +{ + this.beginUpdate(); + try + { + this.setTerminal(edge, source, true); + this.setTerminal(edge, target, false); + } + finally + { + this.endUpdate(); + } +}; + +/** + * Function: terminalForCellChanged + * + * Inner helper function to update the terminal of the edge using + * <mxCell.insertEdge> and return the previous terminal. + * + * Parameters: + * + * edge - <mxCell> that specifies the edge to be updated. + * terminal - <mxCell> that specifies the new terminal. + * isSource - Boolean indicating if the terminal is the new source or + * target terminal of the edge. + */ +mxGraphModel.prototype.terminalForCellChanged = function(edge, terminal, isSource) +{ + var previous = this.getTerminal(edge, isSource); + + if (terminal != null) + { + terminal.insertEdge(edge, isSource); + } + else if (previous != null) + { + previous.removeEdge(edge, isSource); + } + + return previous; +}; + +/** + * Function: getEdgeCount + * + * Returns the number of distinct edges connected to the given cell. + * + * Parameters: + * + * cell - <mxCell> that represents the vertex. + */ +mxGraphModel.prototype.getEdgeCount = function(cell) +{ + return (cell != null) ? cell.getEdgeCount() : 0; +}; + +/** + * Function: getEdgeAt + * + * Returns the edge of cell at the given index. + * + * Parameters: + * + * cell - <mxCell> that specifies the vertex. + * index - Integer that specifies the index of the edge + * to return. + */ +mxGraphModel.prototype.getEdgeAt = function(cell, index) +{ + return (cell != null) ? cell.getEdgeAt(index) : null; +}; + +/** + * Function: getDirectedEdgeCount + * + * Returns the number of incoming or outgoing edges, ignoring the given + * edge. + * + * Parameters: + * + * cell - <mxCell> whose edge count should be returned. + * outgoing - Boolean that specifies if the number of outgoing or + * incoming edges should be returned. + * ignoredEdge - <mxCell> that represents an edge to be ignored. + */ +mxGraphModel.prototype.getDirectedEdgeCount = function(cell, outgoing, ignoredEdge) +{ + var count = 0; + var edgeCount = this.getEdgeCount(cell); + + for (var i = 0; i < edgeCount; i++) + { + var edge = this.getEdgeAt(cell, i); + + if (edge != ignoredEdge && this.getTerminal(edge, outgoing) == cell) + { + count++; + } + } + + return count; +}; + +/** + * Function: getConnections + * + * Returns all edges of the given cell without loops. + * + * Parameters: + * + * cell - <mxCell> whose edges should be returned. + * + */ +mxGraphModel.prototype.getConnections = function(cell) +{ + return this.getEdges(cell, true, true, false); +}; + +/** + * Function: getIncomingEdges + * + * Returns the incoming edges of the given cell without loops. + * + * Parameters: + * + * cell - <mxCell> whose incoming edges should be returned. + * + */ +mxGraphModel.prototype.getIncomingEdges = function(cell) +{ + return this.getEdges(cell, true, false, false); +}; + +/** + * Function: getOutgoingEdges + * + * Returns the outgoing edges of the given cell without loops. + * + * Parameters: + * + * cell - <mxCell> whose outgoing edges should be returned. + * + */ +mxGraphModel.prototype.getOutgoingEdges = function(cell) +{ + return this.getEdges(cell, false, true, false); +}; + +/** + * Function: getEdges + * + * Returns all distinct edges connected to this cell as a new array of + * <mxCells>. If at least one of incoming or outgoing is true, then loops + * are ignored, otherwise if both are false, then all edges connected to + * the given cell are returned including loops. + * + * Parameters: + * + * cell - <mxCell> that specifies the cell. + * incoming - Optional boolean that specifies if incoming edges should be + * returned. Default is true. + * outgoing - Optional boolean that specifies if outgoing edges should be + * returned. Default is true. + * includeLoops - Optional boolean that specifies if loops should be returned. + * Default is true. + */ +mxGraphModel.prototype.getEdges = function(cell, incoming, outgoing, includeLoops) +{ + incoming = (incoming != null) ? incoming : true; + outgoing = (outgoing != null) ? outgoing : true; + includeLoops = (includeLoops != null) ? includeLoops : true; + + var edgeCount = this.getEdgeCount(cell); + var result = []; + + for (var i = 0; i < edgeCount; i++) + { + var edge = this.getEdgeAt(cell, i); + var source = this.getTerminal(edge, true); + var target = this.getTerminal(edge, false); + + if ((includeLoops && source == target) || ((source != target) && ((incoming && target == cell) || + (outgoing && source == cell)))) + { + result.push(edge); + } + } + + return result; +}; + +/** + * Function: getEdgesBetween + * + * Returns all edges between the given source and target pair. If directed + * is true, then only edges from the source to the target are returned, + * otherwise, all edges between the two cells are returned. + * + * Parameters: + * + * source - <mxCell> that defines the source terminal of the edge to be + * returned. + * target - <mxCell> that defines the target terminal of the edge to be + * returned. + * directed - Optional boolean that specifies if the direction of the + * edge should be taken into account. Default is false. + */ +mxGraphModel.prototype.getEdgesBetween = function(source, target, directed) +{ + directed = (directed != null) ? directed : false; + + var tmp1 = this.getEdgeCount(source); + var tmp2 = this.getEdgeCount(target); + + // Assumes the source has less connected edges + var terminal = source; + var edgeCount = tmp1; + + // Uses the smaller array of connected edges + // for searching the edge + if (tmp2 < tmp1) + { + edgeCount = tmp2; + terminal = target; + } + + var result = []; + + // Checks if the edge is connected to the correct + // cell and returns the first match + for (var i = 0; i < edgeCount; i++) + { + var edge = this.getEdgeAt(terminal, i); + var src = this.getTerminal(edge, true); + var trg = this.getTerminal(edge, false); + var directedMatch = (src == source) && (trg == target); + var oppositeMatch = (trg == source) && (src == target); + + if (directedMatch || (!directed && oppositeMatch)) + { + result.push(edge); + } + } + + return result; +}; + +/** + * Function: getOpposites + * + * Returns all opposite vertices wrt terminal for the given edges, only + * returning sources and/or targets as specified. The result is returned + * as an array of <mxCells>. + * + * Parameters: + * + * edges - Array of <mxCells> that contain the edges to be examined. + * terminal - <mxCell> that specifies the known end of the edges. + * sources - Boolean that specifies if source terminals should be contained + * in the result. Default is true. + * targets - Boolean that specifies if target terminals should be contained + * in the result. Default is true. + */ +mxGraphModel.prototype.getOpposites = function(edges, terminal, sources, targets) +{ + sources = (sources != null) ? sources : true; + targets = (targets != null) ? targets : true; + + var terminals = []; + + if (edges != null) + { + for (var i = 0; i < edges.length; i++) + { + var source = this.getTerminal(edges[i], true); + var target = this.getTerminal(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) + { + 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) + { + terminals.push(source); + } + } + } + + return terminals; +}; + +/** + * Function: getTopmostCells + * + * Returns the topmost cells of the hierarchy in an array that contains no + * descendants for each <mxCell> that it contains. Duplicates should be + * removed in the cells array to improve performance. + * + * Parameters: + * + * cells - Array of <mxCells> whose topmost ancestors should be returned. + */ +mxGraphModel.prototype.getTopmostCells = function(cells) +{ + var tmp = []; + + for (var i = 0; i < cells.length; i++) + { + var cell = cells[i]; + var topmost = true; + var parent = this.getParent(cell); + + while (parent != null) + { + if (mxUtils.indexOf(cells, parent) >= 0) + { + topmost = false; + break; + } + + parent = this.getParent(parent); + } + + if (topmost) + { + tmp.push(cell); + } + } + + return tmp; +}; + +/** + * Function: isVertex + * + * Returns true if the given cell is a vertex. + * + * Parameters: + * + * cell - <mxCell> that represents the possible vertex. + */ +mxGraphModel.prototype.isVertex = function(cell) +{ + return (cell != null) ? cell.isVertex() : false; +}; + +/** + * Function: isEdge + * + * Returns true if the given cell is an edge. + * + * Parameters: + * + * cell - <mxCell> that represents the possible edge. + */ +mxGraphModel.prototype.isEdge = function(cell) +{ + return (cell != null) ? cell.isEdge() : false; +}; + +/** + * Function: isConnectable + * + * Returns true if the given <mxCell> is connectable. If <edgesConnectable> + * is false, then this function returns false for all edges else it returns + * the return value of <mxCell.isConnectable>. + * + * Parameters: + * + * cell - <mxCell> whose connectable state should be returned. + */ +mxGraphModel.prototype.isConnectable = function(cell) +{ + return (cell != null) ? cell.isConnectable() : false; +}; + +/** + * Function: getValue + * + * Returns the user object of the given <mxCell> using <mxCell.getValue>. + * + * Parameters: + * + * cell - <mxCell> whose user object should be returned. + */ +mxGraphModel.prototype.getValue = function(cell) +{ + return (cell != null) ? cell.getValue() : null; +}; + +/** + * Function: setValue + * + * Sets the user object of then given <mxCell> using <mxValueChange> + * and adds the change to the current transaction. + * + * Parameters: + * + * cell - <mxCell> whose user object should be changed. + * value - Object that defines the new user object. + */ +mxGraphModel.prototype.setValue = function(cell, value) +{ + this.execute(new mxValueChange(this, cell, value)); + + return value; +}; + +/** + * Function: valueForCellChanged + * + * Inner callback to update the user object of the given <mxCell> + * using <mxCell.valueChanged> and return the previous value, + * that is, the return value of <mxCell.valueChanged>. + * + * To change a specific attribute in an XML node, the following code can be + * used. + * + * (code) + * graph.getModel().valueForCellChanged = function(cell, value) + * { + * var previous = cell.value.getAttribute('label'); + * cell.value.setAttribute('label', value); + * + * return previous; + * }; + * (end) + */ +mxGraphModel.prototype.valueForCellChanged = function(cell, value) +{ + return cell.valueChanged(value); +}; + +/** + * Function: getGeometry + * + * Returns the <mxGeometry> of the given <mxCell>. + * + * Parameters: + * + * cell - <mxCell> whose geometry should be returned. + */ +mxGraphModel.prototype.getGeometry = function(cell, geometry) +{ + return (cell != null) ? cell.getGeometry() : null; +}; + +/** + * Function: setGeometry + * + * Sets the <mxGeometry> of the given <mxCell>. The actual update + * of the cell is carried out in <geometryForCellChanged>. The + * <mxGeometryChange> action is used to encapsulate the change. + * + * Parameters: + * + * cell - <mxCell> whose geometry should be changed. + * geometry - <mxGeometry> that defines the new geometry. + */ +mxGraphModel.prototype.setGeometry = function(cell, geometry) +{ + if (geometry != this.getGeometry(cell)) + { + this.execute(new mxGeometryChange(this, cell, geometry)); + } + + return geometry; +}; + +/** + * Function: geometryForCellChanged + * + * Inner callback to update the <mxGeometry> of the given <mxCell> using + * <mxCell.setGeometry> and return the previous <mxGeometry>. + */ +mxGraphModel.prototype.geometryForCellChanged = function(cell, geometry) +{ + var previous = this.getGeometry(cell); + cell.setGeometry(geometry); + + return previous; +}; + +/** + * Function: getStyle + * + * Returns the style of the given <mxCell>. + * + * Parameters: + * + * cell - <mxCell> whose style should be returned. + */ +mxGraphModel.prototype.getStyle = function(cell) +{ + return (cell != null) ? cell.getStyle() : null; +}; + +/** + * Function: setStyle + * + * Sets the style of the given <mxCell> using <mxStyleChange> and + * adds the change to the current transaction. + * + * Parameters: + * + * cell - <mxCell> whose style should be changed. + * style - String of the form [stylename;|key=value;] to specify + * the new cell style. + */ +mxGraphModel.prototype.setStyle = function(cell, style) +{ + if (style != this.getStyle(cell)) + { + this.execute(new mxStyleChange(this, cell, style)); + } + + return style; +}; + +/** + * Function: styleForCellChanged + * + * Inner callback to update the style of the given <mxCell> + * using <mxCell.setStyle> and return the previous style. + * + * Parameters: + * + * cell - <mxCell> that specifies the cell to be updated. + * style - String of the form [stylename;|key=value;] to specify + * the new cell style. + */ +mxGraphModel.prototype.styleForCellChanged = function(cell, style) +{ + var previous = this.getStyle(cell); + cell.setStyle(style); + + return previous; +}; + +/** + * Function: isCollapsed + * + * Returns true if the given <mxCell> is collapsed. + * + * Parameters: + * + * cell - <mxCell> whose collapsed state should be returned. + */ +mxGraphModel.prototype.isCollapsed = function(cell) +{ + return (cell != null) ? cell.isCollapsed() : false; +}; + +/** + * Function: setCollapsed + * + * Sets the collapsed state of the given <mxCell> using <mxCollapseChange> + * and adds the change to the current transaction. + * + * Parameters: + * + * cell - <mxCell> whose collapsed state should be changed. + * collapsed - Boolean that specifies the new collpased state. + */ +mxGraphModel.prototype.setCollapsed = function(cell, collapsed) +{ + if (collapsed != this.isCollapsed(cell)) + { + this.execute(new mxCollapseChange(this, cell, collapsed)); + } + + return collapsed; +}; + +/** + * Function: collapsedStateForCellChanged + * + * Inner callback to update the collapsed state of the + * given <mxCell> using <mxCell.setCollapsed> and return + * the previous collapsed state. + * + * Parameters: + * + * cell - <mxCell> that specifies the cell to be updated. + * collapsed - Boolean that specifies the new collpased state. + */ +mxGraphModel.prototype.collapsedStateForCellChanged = function(cell, collapsed) +{ + var previous = this.isCollapsed(cell); + cell.setCollapsed(collapsed); + + return previous; +}; + +/** + * Function: isVisible + * + * Returns true if the given <mxCell> is visible. + * + * Parameters: + * + * cell - <mxCell> whose visible state should be returned. + */ +mxGraphModel.prototype.isVisible = function(cell) +{ + return (cell != null) ? cell.isVisible() : false; +}; + +/** + * Function: setVisible + * + * Sets the visible state of the given <mxCell> using <mxVisibleChange> and + * adds the change to the current transaction. + * + * Parameters: + * + * cell - <mxCell> whose visible state should be changed. + * visible - Boolean that specifies the new visible state. + */ +mxGraphModel.prototype.setVisible = function(cell, visible) +{ + if (visible != this.isVisible(cell)) + { + this.execute(new mxVisibleChange(this, cell, visible)); + } + + return visible; +}; + +/** + * Function: visibleStateForCellChanged + * + * Inner callback to update the visible state of the + * given <mxCell> using <mxCell.setCollapsed> and return + * the previous visible state. + * + * Parameters: + * + * cell - <mxCell> that specifies the cell to be updated. + * visible - Boolean that specifies the new visible state. + */ +mxGraphModel.prototype.visibleStateForCellChanged = function(cell, visible) +{ + var previous = this.isVisible(cell); + cell.setVisible(visible); + + return previous; +}; + +/** + * Function: execute + * + * Executes the given edit and fires events if required. The edit object + * requires an execute function which is invoked. The edit is added to the + * <currentEdit> between <beginUpdate> and <endUpdate> calls, so that + * events will be fired if this execute is an individual transaction, that + * is, if no previous <beginUpdate> calls have been made without calling + * <endUpdate>. This implementation fires an <execute> event before + * executing the given change. + * + * Parameters: + * + * change - Object that described the change. + */ +mxGraphModel.prototype.execute = function(change) +{ + change.execute(); + this.beginUpdate(); + this.currentEdit.add(change); + this.fireEvent(new mxEventObject(mxEvent.EXECUTE, 'change', change)); + this.endUpdate(); +}; + +/** + * Function: beginUpdate + * + * Increments the <updateLevel> by one. The event notification + * is queued until <updateLevel> reaches 0 by use of + * <endUpdate>. + * + * All changes on <mxGraphModel> are transactional, + * that is, they are executed in a single undoable change + * on the model (without transaction isolation). + * Therefore, if you want to combine any + * number of changes into a single undoable change, + * you should group any two or more API calls that + * modify the graph model between <beginUpdate> + * and <endUpdate> calls as shown here: + * + * (code) + * var model = graph.getModel(); + * var parent = graph.getDefaultParent(); + * var index = model.getChildCount(parent); + * model.beginUpdate(); + * try + * { + * model.add(parent, v1, index); + * model.add(parent, v2, index+1); + * } + * finally + * { + * model.endUpdate(); + * } + * (end) + * + * Of course there is a shortcut for appending a + * sequence of cells into the default parent: + * + * (code) + * graph.addCells([v1, v2]). + * (end) + */ +mxGraphModel.prototype.beginUpdate = function() +{ + this.updateLevel++; + this.fireEvent(new mxEventObject(mxEvent.BEGIN_UPDATE)); +}; + +/** + * Function: endUpdate + * + * Decrements the <updateLevel> by one and fires an <undo> + * event if the <updateLevel> reaches 0. This function + * indirectly fires a <change> event by invoking the notify + * function on the <currentEdit> und then creates a new + * <currentEdit> using <createUndoableEdit>. + * + * The <undo> event is fired only once per edit, whereas + * the <change> event is fired whenever the notify + * function is invoked, that is, on undo and redo of + * the edit. + */ +mxGraphModel.prototype.endUpdate = function() +{ + this.updateLevel--; + + if (!this.endingUpdate) + { + this.endingUpdate = this.updateLevel == 0; + this.fireEvent(new mxEventObject(mxEvent.END_UPDATE, 'edit', this.currentEdit)); + + try + { + if (this.endingUpdate && !this.currentEdit.isEmpty()) + { + this.fireEvent(new mxEventObject(mxEvent.BEFORE_UNDO, 'edit', this.currentEdit)); + var tmp = this.currentEdit; + this.currentEdit = this.createUndoableEdit(); + tmp.notify(); + this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', tmp)); + } + } + finally + { + this.endingUpdate = false; + } + } +}; + +/** + * Function: createUndoableEdit + * + * Creates a new <mxUndoableEdit> that implements the + * notify function to fire a <change> and <notify> event + * through the <mxUndoableEdit>'s source. + */ +mxGraphModel.prototype.createUndoableEdit = function() +{ + var edit = new mxUndoableEdit(this, true); + + edit.notify = function() + { + // LATER: Remove changes property (deprecated) + edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE, + 'edit', edit, 'changes', edit.changes)); + edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY, + 'edit', edit, 'changes', edit.changes)); + }; + + return edit; +}; + +/** + * Function: mergeChildren + * + * Merges the children of the given cell into the given target cell inside + * this model. All cells are cloned unless there is a corresponding cell in + * the model with the same id, in which case the source cell is ignored and + * all edges are connected to the corresponding cell in this model. Edges + * are considered to have no identity and are always cloned unless the + * cloneAllEdges flag is set to false, in which case edges with the same + * id in the target model are reconnected to reflect the terminals of the + * source edges. + */ +mxGraphModel.prototype.mergeChildren = function(from, to, cloneAllEdges) +{ + cloneAllEdges = (cloneAllEdges != null) ? cloneAllEdges : true; + + this.beginUpdate(); + try + { + var mapping = new Object(); + this.mergeChildrenImpl(from, to, cloneAllEdges, mapping); + + // Post-processes all edges in the mapping and + // reconnects the terminals to the corresponding + // cells in the target model + for (var key in mapping) + { + var cell = mapping[key]; + var terminal = this.getTerminal(cell, true); + + if (terminal != null) + { + terminal = mapping[mxCellPath.create(terminal)]; + this.setTerminal(cell, terminal, true); + } + + terminal = this.getTerminal(cell, false); + + if (terminal != null) + { + terminal = mapping[mxCellPath.create(terminal)]; + this.setTerminal(cell, terminal, false); + } + } + } + finally + { + this.endUpdate(); + } +}; + +/** + * Function: mergeChildren + * + * Clones the children of the source cell into the given target cell in + * this model and adds an entry to the mapping that maps from the source + * cell to the target cell with the same id or the clone of the source cell + * that was inserted into this model. + */ +mxGraphModel.prototype.mergeChildrenImpl = function(from, to, cloneAllEdges, mapping) +{ + this.beginUpdate(); + try + { + var childCount = from.getChildCount(); + + for (var i = 0; i < childCount; i++) + { + var cell = from.getChildAt(i); + + if (typeof(cell.getId) == 'function') + { + var id = cell.getId(); + var target = (id != null && (!this.isEdge(cell) || !cloneAllEdges)) ? + this.getCell(id) : null; + + // Clones and adds the child if no cell exists for the id + if (target == null) + { + var clone = cell.clone(); + clone.setId(id); + + // Sets the terminals from the original cell to the clone + // because the lookup uses strings not cells in JS + clone.setTerminal(cell.getTerminal(true), true); + clone.setTerminal(cell.getTerminal(false), false); + + // Do *NOT* use model.add as this will move the edge away + // from the parent in updateEdgeParent if maintainEdgeParent + // is enabled in the target model + target = to.insert(clone); + this.cellAdded(target); + } + + // Stores the mapping for later reconnecting edges + mapping[mxCellPath.create(cell)] = target; + + // Recurses + this.mergeChildrenImpl(cell, target, cloneAllEdges, mapping); + } + } + } + finally + { + this.endUpdate(); + } +}; + +/** + * Function: getParents + * + * Returns an array that represents the set (no duplicates) of all parents + * for the given array of cells. + * + * Parameters: + * + * cells - Array of cells whose parents should be returned. + */ +mxGraphModel.prototype.getParents = function(cells) +{ + var parents = []; + + if (cells != null) + { + var hash = new Object(); + + for (var i = 0; i < cells.length; i++) + { + var parent = this.getParent(cells[i]); + + if (parent != null) + { + var id = mxCellPath.create(parent); + + if (hash[id] == null) + { + hash[id] = parent; + parents.push(parent); + } + } + } + } + + return parents; +}; + +// +// Cell Cloning +// + +/** + * Function: cloneCell + * + * Returns a deep clone of the given <mxCell> (including + * the children) which is created using <cloneCells>. + * + * Parameters: + * + * cell - <mxCell> to be cloned. + */ +mxGraphModel.prototype.cloneCell = function(cell) +{ + if (cell != null) + { + return this.cloneCells([cell], true)[0]; + } + + return null; +}; + +/** + * Function: cloneCells + * + * Returns an array of clones for the given array of <mxCells>. + * Depending on the value of includeChildren, a deep clone is created for + * each cell. Connections are restored based if the corresponding + * cell is contained in the passed in array. + * + * Parameters: + * + * cells - Array of <mxCell> to be cloned. + * includeChildren - Boolean indicating if the cells should be cloned + * with all descendants. + */ +mxGraphModel.prototype.cloneCells = function(cells, includeChildren) +{ + var mapping = new Object(); + var clones = []; + + for (var i = 0; i < cells.length; i++) + { + if (cells[i] != null) + { + clones.push(this.cloneCellImpl(cells[i], mapping, includeChildren)); + } + else + { + clones.push(null); + } + } + + for (var i = 0; i < clones.length; i++) + { + if (clones[i] != null) + { + this.restoreClone(clones[i], cells[i], mapping); + } + } + + return clones; +}; + +/** + * Function: cloneCellImpl + * + * Inner helper method for cloning cells recursively. + */ +mxGraphModel.prototype.cloneCellImpl = function(cell, mapping, includeChildren) +{ + var clone = this.cellCloned(cell); + + // Stores the clone in the lookup under the + // cell path for the original cell + mapping[mxObjectIdentity.get(cell)] = clone; + + if (includeChildren) + { + var childCount = this.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + var cloneChild = this.cloneCellImpl( + this.getChildAt(cell, i), mapping, true); + clone.insert(cloneChild); + } + } + + return clone; +}; + +/** + * Function: cellCloned + * + * Hook for cloning the cell. This returns cell.clone() or + * any possible exceptions. + */ +mxGraphModel.prototype.cellCloned = function(cell) +{ + return cell.clone(); +}; + +/** + * Function: restoreClone + * + * Inner helper method for restoring the connections in + * a network of cloned cells. + */ +mxGraphModel.prototype.restoreClone = function(clone, cell, mapping) +{ + var source = this.getTerminal(cell, true); + + if (source != null) + { + var tmp = mapping[mxObjectIdentity.get(source)]; + + if (tmp != null) + { + tmp.insertEdge(clone, true); + } + } + + var target = this.getTerminal(cell, false); + + if (target != null) + { + var tmp = mapping[mxObjectIdentity.get(target)]; + + if (tmp != null) + { + tmp.insertEdge(clone, false); + } + } + + var childCount = this.getChildCount(clone); + + for (var i = 0; i < childCount; i++) + { + this.restoreClone(this.getChildAt(clone, i), + this.getChildAt(cell, i), mapping); + } +}; + +// +// Atomic changes +// + +/** + * Class: mxRootChange + * + * Action to change the root in a model. + * + * Constructor: mxRootChange + * + * Constructs a change of the root in the + * specified model. + */ +function mxRootChange(model, root) +{ + this.model = model; + this.root = root; + this.previous = root; +}; + +/** + * Function: execute + * + * Carries out a change of the root using + * <mxGraphModel.rootChanged>. + */ +mxRootChange.prototype.execute = function() +{ + this.root = this.previous; + this.previous = this.model.rootChanged(this.previous); +}; + +/** + * Class: mxChildChange + * + * Action to add or remove a child in a model. + * + * Constructor: mxChildChange + * + * Constructs a change of a child in the + * specified model. + */ +function mxChildChange(model, parent, child, index) +{ + this.model = model; + this.parent = parent; + this.previous = parent; + this.child = child; + this.index = index; + this.previousIndex = index; +}; + +/** + * Function: execute + * + * Changes the parent of <child> using + * <mxGraphModel.parentForCellChanged> and + * removes or restores the cell's + * connections. + */ +mxChildChange.prototype.execute = function() +{ + var tmp = this.model.getParent(this.child); + var tmp2 = (tmp != null) ? tmp.getIndex(this.child) : 0; + + if (this.previous == null) + { + this.connect(this.child, false); + } + + tmp = this.model.parentForCellChanged( + this.child, this.previous, this.previousIndex); + + if (this.previous != null) + { + this.connect(this.child, true); + } + + this.parent = this.previous; + this.previous = tmp; + this.index = this.previousIndex; + this.previousIndex = tmp2; +}; + +/** + * Function: disconnect + * + * Disconnects the given cell recursively from its + * terminals and stores the previous terminal in the + * cell's terminals. + */ +mxChildChange.prototype.connect = function(cell, isConnect) +{ + isConnect = (isConnect != null) ? isConnect : true; + + var source = cell.getTerminal(true); + var target = cell.getTerminal(false); + + if (source != null) + { + if (isConnect) + { + this.model.terminalForCellChanged(cell, source, true); + } + else + { + this.model.terminalForCellChanged(cell, null, true); + } + } + + if (target != null) + { + if (isConnect) + { + this.model.terminalForCellChanged(cell, target, false); + } + else + { + this.model.terminalForCellChanged(cell, null, false); + } + } + + cell.setTerminal(source, true); + cell.setTerminal(target, false); + + var childCount = this.model.getChildCount(cell); + + for (var i=0; i<childCount; i++) + { + this.connect(this.model.getChildAt(cell, i), isConnect); + } +}; + +/** + * Class: mxTerminalChange + * + * Action to change a terminal in a model. + * + * Constructor: mxTerminalChange + * + * Constructs a change of a terminal in the + * specified model. + */ +function mxTerminalChange(model, cell, terminal, source) +{ + this.model = model; + this.cell = cell; + this.terminal = terminal; + this.previous = terminal; + this.source = source; +}; + +/** + * Function: execute + * + * Changes the terminal of <cell> to <previous> using + * <mxGraphModel.terminalForCellChanged>. + */ +mxTerminalChange.prototype.execute = function() +{ + this.terminal = this.previous; + this.previous = this.model.terminalForCellChanged( + this.cell, this.previous, this.source); +}; + +/** + * Class: mxValueChange + * + * Action to change a user object in a model. + * + * Constructor: mxValueChange + * + * Constructs a change of a user object in the + * specified model. + */ +function mxValueChange(model, cell, value) +{ + this.model = model; + this.cell = cell; + this.value = value; + this.previous = value; +}; + +/** + * Function: execute + * + * Changes the value of <cell> to <previous> using + * <mxGraphModel.valueForCellChanged>. + */ +mxValueChange.prototype.execute = function() +{ + this.value = this.previous; + this.previous = this.model.valueForCellChanged( + this.cell, this.previous); +}; + +/** + * Class: mxStyleChange + * + * Action to change a cell's style in a model. + * + * Constructor: mxStyleChange + * + * Constructs a change of a style in the + * specified model. + */ +function mxStyleChange(model, cell, style) +{ + this.model = model; + this.cell = cell; + this.style = style; + this.previous = style; +}; + +/** + * Function: execute + * + * Changes the style of <cell> to <previous> using + * <mxGraphModel.styleForCellChanged>. + */ +mxStyleChange.prototype.execute = function() +{ + this.style = this.previous; + this.previous = this.model.styleForCellChanged( + this.cell, this.previous); +}; + +/** + * Class: mxGeometryChange + * + * Action to change a cell's geometry in a model. + * + * Constructor: mxGeometryChange + * + * Constructs a change of a geometry in the + * specified model. + */ +function mxGeometryChange(model, cell, geometry) +{ + this.model = model; + this.cell = cell; + this.geometry = geometry; + this.previous = geometry; +}; + +/** + * Function: execute + * + * Changes the geometry of <cell> ro <previous> using + * <mxGraphModel.geometryForCellChanged>. + */ +mxGeometryChange.prototype.execute = function() +{ + this.geometry = this.previous; + this.previous = this.model.geometryForCellChanged( + this.cell, this.previous); +}; + +/** + * Class: mxCollapseChange + * + * Action to change a cell's collapsed state in a model. + * + * Constructor: mxCollapseChange + * + * Constructs a change of a collapsed state in the + * specified model. + */ +function mxCollapseChange(model, cell, collapsed) +{ + this.model = model; + this.cell = cell; + this.collapsed = collapsed; + this.previous = collapsed; +}; + +/** + * Function: execute + * + * Changes the collapsed state of <cell> to <previous> using + * <mxGraphModel.collapsedStateForCellChanged>. + */ +mxCollapseChange.prototype.execute = function() +{ + this.collapsed = this.previous; + this.previous = this.model.collapsedStateForCellChanged( + this.cell, this.previous); +}; + +/** + * Class: mxVisibleChange + * + * Action to change a cell's visible state in a model. + * + * Constructor: mxVisibleChange + * + * Constructs a change of a visible state in the + * specified model. + */ +function mxVisibleChange(model, cell, visible) +{ + this.model = model; + this.cell = cell; + this.visible = visible; + this.previous = visible; +}; + +/** + * Function: execute + * + * Changes the visible state of <cell> to <previous> using + * <mxGraphModel.visibleStateForCellChanged>. + */ +mxVisibleChange.prototype.execute = function() +{ + this.visible = this.previous; + this.previous = this.model.visibleStateForCellChanged( + this.cell, this.previous); +}; + +/** + * Class: mxCellAttributeChange + * + * Action to change the attribute of a cell's user object. + * There is no method on the graph model that uses this + * action. To use the action, you can use the code shown + * in the example below. + * + * Example: + * + * To change the attributeName in the cell's user object + * to attributeValue, use the following code: + * + * (code) + * model.beginUpdate(); + * try + * { + * var edit = new mxCellAttributeChange( + * cell, attributeName, attributeValue); + * model.execute(edit); + * } + * finally + * { + * model.endUpdate(); + * } + * (end) + * + * Constructor: mxCellAttributeChange + * + * Constructs a change of a attribute of the DOM node + * stored as the value of the given <mxCell>. + */ +function mxCellAttributeChange(cell, attribute, value) +{ + this.cell = cell; + this.attribute = attribute; + this.value = value; + this.previous = value; +}; + +/** + * Function: execute + * + * Changes the attribute of the cell's user object by + * using <mxCell.setAttribute>. + */ +mxCellAttributeChange.prototype.execute = function() +{ + var tmp = this.cell.getAttribute(this.attribute); + + if (this.previous == null) + { + this.cell.value.removeAttribute(this.attribute); + } + else + { + this.cell.setAttribute(this.attribute, this.previous); + } + + this.previous = tmp; +}; |