package com.mxgraph.view; import java.awt.Point; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import com.mxgraph.layout.mxIGraphLayout; import com.mxgraph.model.mxGraphModel; import com.mxgraph.model.mxIGraphModel; import com.mxgraph.model.mxGraphModel.mxChildChange; import com.mxgraph.model.mxGraphModel.mxGeometryChange; import com.mxgraph.model.mxGraphModel.mxRootChange; import com.mxgraph.model.mxGraphModel.mxTerminalChange; import com.mxgraph.util.mxEvent; import com.mxgraph.util.mxEventObject; import com.mxgraph.util.mxEventSource; import com.mxgraph.util.mxUndoableEdit; import com.mxgraph.util.mxUtils; import com.mxgraph.util.mxUndoableEdit.mxUndoableChange; /** * Implements a layout manager that updates the layout for a given transaction. * The following example installs an automatic tree layout in a graph: * * <code> * new mxLayoutManager(graph) { * * mxCompactTreeLayout layout = new mxCompactTreeLayout(graph); * * public mxIGraphLayout getLayout(Object parent) * { * if (graph.getModel().getChildCount(parent) > 0) { * return layout; * } * return null; * } * }; * </code> * * This class fires the following 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. */ public class mxLayoutManager extends mxEventSource { /** * Defines the type of the source or target terminal. The type is a string * passed to mxCell.is to check if the rule applies to a cell. */ protected mxGraph graph; /** * Optional string that specifies the value of the attribute to be passed * to mxCell.is to check if the rule applies to a cell. Default is true. */ protected boolean enabled = true; /** * Optional string that specifies the attributename to be passed to * mxCell.is to check if the rule applies to a cell. Default is true. */ protected boolean bubbling = true; /** * */ protected mxIEventListener undoHandler = new mxIEventListener() { public void invoke(Object source, mxEventObject evt) { if (isEnabled()) { beforeUndo((mxUndoableEdit) evt.getProperty("edit")); } } }; /** * */ protected mxIEventListener moveHandler = new mxIEventListener() { public void invoke(Object source, mxEventObject evt) { if (isEnabled()) { cellsMoved((Object[]) evt.getProperty("cells"), (Point) evt .getProperty("location")); } } }; /** * */ public mxLayoutManager(mxGraph graph) { setGraph(graph); } /** * @return the enabled */ public boolean isEnabled() { return enabled; } /** * @param value the enabled to set */ public void setEnabled(boolean value) { enabled = value; } /** * @return the bubbling */ public boolean isBubbling() { return bubbling; } /** * @param value the bubbling to set */ public void setBubbling(boolean value) { bubbling = value; } /** * @return the graph */ public mxGraph getGraph() { return graph; } /** * @param value the graph to set */ public void setGraph(mxGraph value) { if (graph != null) { mxIGraphModel model = graph.getModel(); model.removeListener(undoHandler); graph.removeListener(moveHandler); } graph = value; if (graph != null) { mxIGraphModel model = graph.getModel(); model.addListener(mxEvent.BEFORE_UNDO, undoHandler); graph.addListener(mxEvent.MOVE_CELLS, moveHandler); } } /** * */ protected mxIGraphLayout getLayout(Object parent) { return null; } /** * */ protected void cellsMoved(Object[] cells, Point location) { if (cells != null && location != null) { mxIGraphModel model = getGraph().getModel(); // Checks if a layout exists to take care of the moving for (int i = 0; i < cells.length; i++) { mxIGraphLayout layout = getLayout(model.getParent(cells[i])); if (layout != null) { layout.moveCell(cells[i], location.x, location.y); } } } } /** * */ protected void beforeUndo(mxUndoableEdit edit) { Collection<Object> cells = getCellsForChanges(edit.getChanges()); mxIGraphModel model = getGraph().getModel(); if (isBubbling()) { Object[] tmp = mxGraphModel.getParents(model, cells.toArray()); while (tmp.length > 0) { cells.addAll(Arrays.asList(tmp)); tmp = mxGraphModel.getParents(model, tmp); } } layoutCells(mxUtils.sortCells(cells, false).toArray()); } /** * */ protected Collection<Object> getCellsForChanges( List<mxUndoableChange> changes) { Set<Object> result = new HashSet<Object>(); Iterator<mxUndoableChange> it = changes.iterator(); while (it.hasNext()) { mxUndoableChange change = it.next(); if (change instanceof mxRootChange) { return new HashSet<Object>(); } else { result.addAll(getCellsForChange(change)); } } return result; } /** * */ protected Collection<Object> getCellsForChange(mxUndoableChange change) { mxIGraphModel model = getGraph().getModel(); Set<Object> result = new HashSet<Object>(); if (change instanceof mxChildChange) { mxChildChange cc = (mxChildChange) change; Object parent = model.getParent(cc.getChild()); if (cc.getChild() != null) { result.add(cc.getChild()); } if (parent != null) { result.add(parent); } if (cc.getPrevious() != null) { result.add(cc.getPrevious()); } } else if (change instanceof mxTerminalChange || change instanceof mxGeometryChange) { Object cell = (change instanceof mxTerminalChange) ? ((mxTerminalChange) change) .getCell() : ((mxGeometryChange) change).getCell(); if (cell != null) { result.add(cell); Object parent = model.getParent(cell); if (parent != null) { result.add(parent); } } } return result; } /** * */ protected void layoutCells(Object[] cells) { if (cells.length > 0) { // Invokes the layouts while removing duplicates mxIGraphModel model = getGraph().getModel(); model.beginUpdate(); try { for (int i = 0; i < cells.length; i++) { if (cells[i] != model.getRoot()) { executeLayout(getLayout(cells[i]), cells[i]); } } fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, "cells", cells)); } finally { model.endUpdate(); } } } /** * */ protected void executeLayout(mxIGraphLayout layout, Object parent) { if (layout != null && parent != null) { layout.execute(parent); } } /** * */ public void destroy() { setGraph(null); } }