sch_test/mxclient/js/view/mxLayoutManager.js

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);
};