501 lines
11 KiB
JavaScript
501 lines
11 KiB
JavaScript
/**
|
|
* 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, eventName)
|
|
* {
|
|
* return layout;
|
|
* };
|
|
* (end)
|
|
*
|
|
* See <getLayout> for a description of the possible eventNames.
|
|
*
|
|
* 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'));
|
|
}
|
|
});
|
|
|
|
// Notifies the layout of a move operation inside a parent
|
|
this.resizeHandler = mxUtils.bind(this, function(sender, evt)
|
|
{
|
|
if (this.isEnabled())
|
|
{
|
|
this.cellsResized(evt.getProperty('cells'), evt.getProperty('bounds'),
|
|
evt.getProperty('previous'));
|
|
}
|
|
});
|
|
|
|
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: undoHandler
|
|
*
|
|
* Holds the function that handles the endUpdate event.
|
|
*/
|
|
mxLayoutManager.prototype.undoHandler = null;
|
|
|
|
/**
|
|
* Variable: moveHandler
|
|
*
|
|
* Holds the function that handles the move event.
|
|
*/
|
|
mxLayoutManager.prototype.moveHandler = null;
|
|
|
|
/**
|
|
* Variable: resizeHandler
|
|
*
|
|
* Holds the function that handles the resize event.
|
|
*/
|
|
mxLayoutManager.prototype.resizeHandler = 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.removeListener(this.resizeHandler);
|
|
}
|
|
|
|
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);
|
|
this.graph.addListener(mxEvent.RESIZE_CELLS, this.resizeHandler);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: hasLayout
|
|
*
|
|
* Returns true if the given cell has a layout. This implementation invokes
|
|
* <getLayout> with <mxEvent.LAYOUT_CELLS> as the eventName. Override this
|
|
* if creating layouts in <getLayout> is expensive and return true if
|
|
* <getLayout> will return a layout for the given cell for
|
|
* <mxEvent.BEGIN_UPDATE> or <mxEvent.END_UPDATE>.
|
|
*/
|
|
mxLayoutManager.prototype.hasLayout = function(cell)
|
|
{
|
|
return this.getLayout(cell, mxEvent.LAYOUT_CELLS);
|
|
};
|
|
|
|
/**
|
|
* Function: getLayout
|
|
*
|
|
* Returns the layout for the given cell and eventName. Possible
|
|
* event names are <mxEvent.MOVE_CELLS> and <mxEvent.RESIZE_CELLS>
|
|
* when cells are moved or resized and <mxEvent.BEGIN_UPDATE> or
|
|
* <mxEvent.END_UPDATE> for the bottom up and top down phases after
|
|
* changes to the graph model. <mxEvent.LAYOUT_CELLS> is used to
|
|
* check if a layout exists for the given cell. This is called
|
|
* from <hasLayout>.
|
|
*/
|
|
mxLayoutManager.prototype.getLayout = function(cell, eventName)
|
|
{
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Function: beforeUndo
|
|
*
|
|
* Called from <undoHandler>.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* cell - Array of <mxCells> that have been moved.
|
|
* evt - Mouse event that represents the mousedown.
|
|
*/
|
|
mxLayoutManager.prototype.beforeUndo = function(undoableEdit)
|
|
{
|
|
this.executeLayoutForCells(this.getCellsForChanges(undoableEdit.changes));
|
|
};
|
|
|
|
/**
|
|
* Function: cellsMoved
|
|
*
|
|
* Called from <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();
|
|
|
|
for (var i = 0; i < cells.length; i++)
|
|
{
|
|
var layout = this.getLayout(model.getParent(cells[i]), mxEvent.MOVE_CELLS);
|
|
|
|
if (layout != null)
|
|
{
|
|
layout.moveCell(cells[i], point.x, point.y);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: cellsResized
|
|
*
|
|
* Called from <resizeHandler>.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* cell - Array of <mxCells> that have been resized.
|
|
* bounds - <mxRectangle> taht represents the new bounds.
|
|
*/
|
|
mxLayoutManager.prototype.cellsResized = function(cells, bounds, prev)
|
|
{
|
|
if (cells != null && bounds != null)
|
|
{
|
|
var model = this.getGraph().getModel();
|
|
|
|
for (var i = 0; i < cells.length; i++)
|
|
{
|
|
var layout = this.getLayout(model.getParent(cells[i]), mxEvent.RESIZE_CELLS);
|
|
|
|
if (layout != null)
|
|
{
|
|
layout.resizeCell(cells[i], bounds[i], prev[i]);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: getCellsForChanges
|
|
*
|
|
* Returns the cells for which a layout should be executed.
|
|
*/
|
|
mxLayoutManager.prototype.getCellsForChanges = function(changes)
|
|
{
|
|
var result = [];
|
|
|
|
for (var i = 0; i < changes.length; i++)
|
|
{
|
|
var change = changes[i];
|
|
|
|
if (change instanceof mxRootChange)
|
|
{
|
|
return [];
|
|
}
|
|
else
|
|
{
|
|
result = result.concat(this.getCellsForChange(change));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Function: getCellsForChange
|
|
*
|
|
* Executes all layouts which have been scheduled during the
|
|
* changes.
|
|
*/
|
|
mxLayoutManager.prototype.getCellsForChange = function(change)
|
|
{
|
|
if (change instanceof mxChildChange)
|
|
{
|
|
return this.addCellsWithLayout(change.child,
|
|
this.addCellsWithLayout(change.previous));
|
|
}
|
|
else if (change instanceof mxTerminalChange ||
|
|
change instanceof mxGeometryChange)
|
|
{
|
|
return this.addCellsWithLayout(change.cell);
|
|
}
|
|
else if (change instanceof mxVisibleChange ||
|
|
change instanceof mxStyleChange)
|
|
{
|
|
return this.addCellsWithLayout(change.cell);
|
|
}
|
|
|
|
return [];
|
|
};
|
|
|
|
/**
|
|
* Function: addCellsWithLayout
|
|
*
|
|
* Adds all ancestors of the given cell that have a layout.
|
|
*/
|
|
mxLayoutManager.prototype.addCellsWithLayout = function(cell, result)
|
|
{
|
|
return this.addDescendantsWithLayout(cell,
|
|
this.addAncestorsWithLayout(cell, result));
|
|
};
|
|
|
|
/**
|
|
* Function: addAncestorsWithLayout
|
|
*
|
|
* Adds all ancestors of the given cell that have a layout.
|
|
*/
|
|
mxLayoutManager.prototype.addAncestorsWithLayout = function(cell, result)
|
|
{
|
|
result = (result != null) ? result : [];
|
|
|
|
if (cell != null)
|
|
{
|
|
var layout = this.hasLayout(cell);
|
|
|
|
if (layout != null)
|
|
{
|
|
result.push(cell);
|
|
}
|
|
|
|
if (this.isBubbling())
|
|
{
|
|
var model = this.getGraph().getModel();
|
|
this.addAncestorsWithLayout(
|
|
model.getParent(cell), result);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Function: addDescendantsWithLayout
|
|
*
|
|
* Adds all descendants of the given cell that have a layout.
|
|
*/
|
|
mxLayoutManager.prototype.addDescendantsWithLayout = function(cell, result)
|
|
{
|
|
result = (result != null) ? result : [];
|
|
|
|
if (cell != null && this.hasLayout(cell))
|
|
{
|
|
var model = this.getGraph().getModel();
|
|
|
|
for (var i = 0; i < model.getChildCount(cell); i++)
|
|
{
|
|
var child = model.getChildAt(cell, i);
|
|
|
|
if (this.hasLayout(child))
|
|
{
|
|
result.push(child);
|
|
this.addDescendantsWithLayout(child, result);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Function: executeLayoutForCells
|
|
*
|
|
* Executes all layouts for the given cells in two phases: In the first phase
|
|
* layouts for child cells are executed before layouts for parent cells with
|
|
* <mxEvent.BEGIN_UPDATE>, in the second phase layouts for parent cells are
|
|
* executed before layouts for child cells with <mxEvent.END_UPDATE>.
|
|
*/
|
|
mxLayoutManager.prototype.executeLayoutForCells = function(cells)
|
|
{
|
|
var sorted = mxUtils.sortCells(cells, false);
|
|
this.layoutCells(sorted, true);
|
|
this.layoutCells(sorted.reverse(), false);
|
|
};
|
|
|
|
/**
|
|
* Function: layoutCells
|
|
*
|
|
* Executes all layouts which have been scheduled during the changes.
|
|
*/
|
|
mxLayoutManager.prototype.layoutCells = function(cells, bubble)
|
|
{
|
|
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)
|
|
{
|
|
this.executeLayout(cells[i], bubble);
|
|
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(cell, bubble)
|
|
{
|
|
var layout = this.getLayout(cell, (bubble) ?
|
|
mxEvent.BEGIN_UPDATE : mxEvent.END_UPDATE);
|
|
|
|
if (layout != null)
|
|
{
|
|
layout.execute(cell);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: destroy
|
|
*
|
|
* Removes all handlers from the <graph> and deletes the reference to it.
|
|
*/
|
|
mxLayoutManager.prototype.destroy = function()
|
|
{
|
|
this.setGraph(null);
|
|
};
|