| /** |
| * Copyright (c) 2006-2015, JGraph Ltd |
| * Copyright (c) 2006-2015, Gaudenz Alder |
| */ |
| /** |
| * Class: mxLayoutManager |
| * |
| * Implements a layout manager that runs a given layout after any changes to the graph: |
| * |
| * Example: |
| * |
| * (code) |
| * var layoutMgr = new mxLayoutManager(graph); |
| * layoutMgr.getLayout = function(cell) |
| * { |
| * return layout; |
| * }; |
| * (end) |
| * |
| * Event: mxEvent.LAYOUT_CELLS |
| * |
| * Fires between begin- and endUpdate after all cells have been layouted in |
| * <layoutCells>. The <code>cells</code> property contains all cells that have |
| * been passed to <layoutCells>. |
| * |
| * Constructor: mxLayoutManager |
| * |
| * Constructs a new automatic layout for the given graph. |
| * |
| * Arguments: |
| * |
| * graph - Reference to the enclosing graph. |
| */ |
| function mxLayoutManager(graph) |
| { |
| // Executes the layout before the changes are dispatched |
| this.undoHandler = mxUtils.bind(this, function(sender, evt) |
| { |
| if (this.isEnabled()) |
| { |
| this.beforeUndo(evt.getProperty('edit')); |
| } |
| }); |
| |
| // Notifies the layout of a move operation inside a parent |
| this.moveHandler = mxUtils.bind(this, function(sender, evt) |
| { |
| if (this.isEnabled()) |
| { |
| this.cellsMoved(evt.getProperty('cells'), evt.getProperty('event')); |
| } |
| }); |
| |
| this.setGraph(graph); |
| }; |
| |
| /** |
| * Extends mxEventSource. |
| */ |
| mxLayoutManager.prototype = new mxEventSource(); |
| mxLayoutManager.prototype.constructor = mxLayoutManager; |
| |
| /** |
| * Variable: graph |
| * |
| * Reference to the enclosing <mxGraph>. |
| */ |
| mxLayoutManager.prototype.graph = null; |
| |
| /** |
| * Variable: bubbling |
| * |
| * Specifies if the layout should bubble along |
| * the cell hierarchy. Default is true. |
| */ |
| mxLayoutManager.prototype.bubbling = true; |
| |
| /** |
| * Variable: enabled |
| * |
| * Specifies if event handling is enabled. Default is true. |
| */ |
| mxLayoutManager.prototype.enabled = true; |
| |
| /** |
| * Variable: updateHandler |
| * |
| * Holds the function that handles the endUpdate event. |
| */ |
| mxLayoutManager.prototype.updateHandler = null; |
| |
| /** |
| * Variable: moveHandler |
| * |
| * Holds the function that handles the move event. |
| */ |
| mxLayoutManager.prototype.moveHandler = null; |
| |
| /** |
| * Function: isEnabled |
| * |
| * Returns true if events are handled. This implementation |
| * returns <enabled>. |
| */ |
| mxLayoutManager.prototype.isEnabled = function() |
| { |
| return this.enabled; |
| }; |
| |
| /** |
| * Function: setEnabled |
| * |
| * Enables or disables event handling. This implementation |
| * updates <enabled>. |
| * |
| * Parameters: |
| * |
| * enabled - Boolean that specifies the new enabled state. |
| */ |
| mxLayoutManager.prototype.setEnabled = function(enabled) |
| { |
| this.enabled = enabled; |
| }; |
| |
| /** |
| * Function: isBubbling |
| * |
| * Returns true if a layout should bubble, that is, if the parent layout |
| * should be executed whenever a cell layout (layout of the children of |
| * a cell) has been executed. This implementation returns <bubbling>. |
| */ |
| mxLayoutManager.prototype.isBubbling = function() |
| { |
| return this.bubbling; |
| }; |
| |
| /** |
| * Function: setBubbling |
| * |
| * Sets <bubbling>. |
| */ |
| mxLayoutManager.prototype.setBubbling = function(value) |
| { |
| this.bubbling = value; |
| }; |
| |
| /** |
| * Function: getGraph |
| * |
| * Returns the graph that this layout operates on. |
| */ |
| mxLayoutManager.prototype.getGraph = function() |
| { |
| return this.graph; |
| }; |
| |
| /** |
| * Function: setGraph |
| * |
| * Sets the graph that the layouts operate on. |
| */ |
| mxLayoutManager.prototype.setGraph = function(graph) |
| { |
| if (this.graph != null) |
| { |
| var model = this.graph.getModel(); |
| model.removeListener(this.undoHandler); |
| this.graph.removeListener(this.moveHandler); |
| } |
| |
| this.graph = graph; |
| |
| if (this.graph != null) |
| { |
| var model = this.graph.getModel(); |
| model.addListener(mxEvent.BEFORE_UNDO, this.undoHandler); |
| this.graph.addListener(mxEvent.MOVE_CELLS, this.moveHandler); |
| } |
| }; |
| |
| /** |
| * Function: getLayout |
| * |
| * Returns the layout to be executed for the given graph and parent. |
| */ |
| mxLayoutManager.prototype.getLayout = function(parent) |
| { |
| return null; |
| }; |
| |
| /** |
| * Function: beforeUndo |
| * |
| * Called from the undoHandler. |
| * |
| * Parameters: |
| * |
| * cell - Array of <mxCells> that have been moved. |
| * evt - Mouse event that represents the mousedown. |
| */ |
| mxLayoutManager.prototype.beforeUndo = function(undoableEdit) |
| { |
| var cells = this.getCellsForChanges(undoableEdit.changes); |
| var model = this.getGraph().getModel(); |
| |
| // Adds all descendants |
| var tmp = []; |
| |
| for (var i = 0; i < cells.length; i++) |
| { |
| tmp = tmp.concat(model.getDescendants(cells[i])); |
| } |
| |
| cells = tmp; |
| |
| // Adds all parent ancestors |
| if (this.isBubbling()) |
| { |
| tmp = model.getParents(cells); |
| |
| while (tmp.length > 0) |
| { |
| cells = cells.concat(tmp); |
| tmp = model.getParents(tmp); |
| } |
| } |
| |
| this.executeLayoutForCells(cells); |
| }; |
| |
| /** |
| * Function: executeLayout |
| * |
| * Executes the given layout on the given parent. |
| */ |
| mxLayoutManager.prototype.executeLayoutForCells = function(cells) |
| { |
| // Adds reverse to this array to avoid duplicate execution of leafes |
| // Works like capture/bubble for events, first executes all layout |
| // from top to bottom and in reverse order and removes duplicates. |
| var sorted = mxUtils.sortCells(cells, true); |
| sorted = sorted.concat(sorted.slice().reverse()); |
| this.layoutCells(sorted); |
| }; |
| |
| /** |
| * Function: cellsMoved |
| * |
| * Called from the moveHandler. |
| * |
| * Parameters: |
| * |
| * cell - Array of <mxCells> that have been moved. |
| * evt - Mouse event that represents the mousedown. |
| */ |
| mxLayoutManager.prototype.cellsMoved = function(cells, evt) |
| { |
| if (cells != null && evt != null) |
| { |
| var point = mxUtils.convertPoint(this.getGraph().container, |
| mxEvent.getClientX(evt), mxEvent.getClientY(evt)); |
| var model = this.getGraph().getModel(); |
| |
| // Checks if a layout exists to take care of the moving if the |
| // parent itself is not being moved |
| for (var i = 0; i < cells.length; i++) |
| { |
| var parent = model.getParent(cells[i]); |
| |
| if (mxUtils.indexOf(cells, parent) < 0) |
| { |
| var layout = this.getLayout(parent); |
| |
| if (layout != null) |
| { |
| layout.moveCell(cells[i], point.x, point.y); |
| } |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Function: getCellsForEdit |
| * |
| * Returns the cells to be layouted for the given sequence of changes. |
| */ |
| mxLayoutManager.prototype.getCellsForChanges = function(changes) |
| { |
| var dict = new mxDictionary(); |
| var result = []; |
| |
| for (var i = 0; i < changes.length; i++) |
| { |
| var change = changes[i]; |
| |
| if (change instanceof mxRootChange) |
| { |
| return []; |
| } |
| else |
| { |
| var cells = this.getCellsForChange(change); |
| |
| for (var j = 0; j < cells.length; j++) |
| { |
| if (cells[j] != null && !dict.get(cells[j])) |
| { |
| dict.put(cells[j], true); |
| result.push(cells[j]); |
| } |
| } |
| } |
| } |
| |
| return result; |
| }; |
| |
| /** |
| * Function: getCellsForChange |
| * |
| * Executes all layouts which have been scheduled during the |
| * changes. |
| */ |
| mxLayoutManager.prototype.getCellsForChange = function(change) |
| { |
| var model = this.getGraph().getModel(); |
| |
| if (change instanceof mxChildChange) |
| { |
| return [change.child, change.previous, model.getParent(change.child)]; |
| } |
| else if (change instanceof mxTerminalChange || change instanceof mxGeometryChange) |
| { |
| return [change.cell, model.getParent(change.cell)]; |
| } |
| else if (change instanceof mxVisibleChange || change instanceof mxStyleChange) |
| { |
| return [change.cell]; |
| } |
| |
| return []; |
| }; |
| |
| /** |
| * Function: layoutCells |
| * |
| * Executes all layouts which have been scheduled during the |
| * changes. |
| */ |
| mxLayoutManager.prototype.layoutCells = function(cells) |
| { |
| if (cells.length > 0) |
| { |
| // Invokes the layouts while removing duplicates |
| var model = this.getGraph().getModel(); |
| |
| model.beginUpdate(); |
| try |
| { |
| var last = null; |
| |
| for (var i = 0; i < cells.length; i++) |
| { |
| if (cells[i] != model.getRoot() && cells[i] != last) |
| { |
| if (this.executeLayout(this.getLayout(cells[i]), cells[i])) |
| { |
| last = cells[i]; |
| } |
| } |
| } |
| |
| this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, 'cells', cells)); |
| } |
| finally |
| { |
| model.endUpdate(); |
| } |
| } |
| }; |
| |
| /** |
| * Function: executeLayout |
| * |
| * Executes the given layout on the given parent. |
| */ |
| mxLayoutManager.prototype.executeLayout = function(layout, parent) |
| { |
| var result = false; |
| |
| if (layout != null && parent != null) |
| { |
| layout.execute(parent); |
| result = true; |
| } |
| |
| return result; |
| }; |
| |
| /** |
| * Function: destroy |
| * |
| * Removes all handlers from the <graph> and deletes the reference to it. |
| */ |
| mxLayoutManager.prototype.destroy = function() |
| { |
| this.setGraph(null); |
| }; |