| /** |
| * Copyright (c) 2006-2015, JGraph Ltd |
| * Copyright (c) 2006-2015, Gaudenz Alder |
| */ |
| /** |
| * 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. |
| * |
| * Events: |
| * |
| * See events section for more details. There is a new set of events for |
| * tracking transactional changes as they happen. The events are called |
| * startEdit for the initial beginUpdate, executed for each executed change |
| * and endEdit for the terminal endUpdate. The executed event contains a |
| * property called change which represents the change after execution. |
| * |
| * 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.EXECUTED |
| * |
| * Fires between START_EDIT and END_EDIT after an atomic change was executed. |
| * The <code>change</code> property contains the change that was executed. |
| * |
| * Event: mxEvent.BEGIN_UPDATE |
| * |
| * Fires after the <updateLevel> was incremented in <beginUpdate>. This event |
| * contains no properties. |
| * |
| * Event: mxEvent.START_EDIT |
| * |
| * Fires after the <updateLevel> was changed from 0 to 1. 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.END_EDIT |
| * |
| * Fires after the <updateLevel> was changed from 1 to 0. This event |
| * contains no properties. |
| * |
| * 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: ignoreRelativeEdgeParent |
| * |
| * Specifies if relative edge parents should be ignored for finding the nearest |
| * common ancestors of an edge's terminals. Default is true. |
| */ |
| mxGraphModel.prototype.ignoreRelativeEdgeParent = 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 given 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); |
| * (end) |
| * |
| * 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.ignoreRelativeEdgeParent && |
| !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 dict = new mxDictionary(); |
| var tmp = []; |
| |
| for (var i = 0; i < cells.length; i++) |
| { |
| dict.put(cells[i], true); |
| } |
| |
| for (var i = 0; i < cells.length; i++) |
| { |
| var cell = cells[i]; |
| var topmost = true; |
| var parent = this.getParent(cell); |
| |
| while (parent != null) |
| { |
| if (dict.get(parent)) |
| { |
| 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) |
| { |
| 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)); |
| // New global executed event |
| this.fireEvent(new mxEventObject(mxEvent.EXECUTED, '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)); |
| |
| if (this.updateLevel == 1) |
| { |
| this.fireEvent(new mxEventObject(mxEvent.START_EDIT)); |
| } |
| }; |
| |
| /** |
| * 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.updateLevel == 0) |
| { |
| this.fireEvent(new mxEventObject(mxEvent.END_EDIT)); |
| } |
| |
| 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 dict = new mxDictionary(); |
| |
| for (var i = 0; i < cells.length; i++) |
| { |
| var parent = this.getParent(cells[i]); |
| |
| if (parent != null && !dict.get(parent)) |
| { |
| dict.put(parent, true); |
| 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. |
| * mapping - Optional mapping for existing clones. |
| */ |
| mxGraphModel.prototype.cloneCells = function(cells, includeChildren, mapping) |
| { |
| mapping = (mapping != null) ? 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 table |
| 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; |
| }; |