/**
 * $Id: mxGraph.java,v 1.303 2010/07/21 09:44:12 gaudenz Exp $
 * Copyright (c) 2007, Gaudenz Alder
 */
package com.mxgraph.view;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.w3c.dom.Element;

import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.canvas.mxICanvas;
import com.mxgraph.canvas.mxImageCanvas;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.model.mxICell;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.model.mxGraphModel.Filter;
import com.mxgraph.model.mxGraphModel.mxChildChange;
import com.mxgraph.model.mxGraphModel.mxCollapseChange;
import com.mxgraph.model.mxGraphModel.mxGeometryChange;
import com.mxgraph.model.mxGraphModel.mxRootChange;
import com.mxgraph.model.mxGraphModel.mxStyleChange;
import com.mxgraph.model.mxGraphModel.mxTerminalChange;
import com.mxgraph.model.mxGraphModel.mxValueChange;
import com.mxgraph.model.mxGraphModel.mxVisibleChange;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxResources;
import com.mxgraph.util.mxUtils;
import com.mxgraph.util.mxUndoableEdit.mxUndoableChange;

/**
 * Implements a graph object that allows to create diagrams from a graph model
 * and stylesheet.
 * 
 * <h3>Images</h3>
 * To create an image from a graph, use the following code for a given
 * XML document (doc) and File (file):
 * 
 * <code>
 * Image img = mxCellRenderer.createBufferedImage(
 * 		graph, null, 1, Color.WHITE, false, null);
 * ImageIO.write(img, "png", file);
 * </code>
 * 
 * If the XML is given as a string rather than a document, the document can
 * be obtained using mxUtils.parse.
 * 
 * This class fires the following events:
 * 
 * mxEvent.ROOT fires if the root in the model has changed. This event has no
 * properties.
 * 
 * mxEvent.ALIGN_CELLS fires between begin- and endUpdate in alignCells. The
 * <code>cells</code> and <code>align</code> properties contain the respective
 * arguments that were passed to alignCells.
 * 
 * mxEvent.FLIP_EDGE fires between begin- and endUpdate in flipEdge. The
 * <code>edge</code> property contains the edge passed to flipEdge.
 * 
 * mxEvent.ORDER_CELLS fires between begin- and endUpdate in orderCells. The
 * <code>cells</code> and <code>back</code> properties contain the respective
 * arguments that were passed to orderCells.
 *
 * mxEvent.CELLS_ORDERED fires between begin- and endUpdate in cellsOrdered.
 * The <code>cells</code> and <code>back</code> arguments contain the
 * respective arguments that were passed to cellsOrdered.
 * 
 * mxEvent.GROUP_CELLS fires between begin- and endUpdate in groupCells. The
 * <code>group</code>, <code>cells</code> and <code>border</code> arguments
 * contain the respective arguments that were passed to groupCells.
 * 
 * mxEvent.UNGROUP_CELLS fires between begin- and endUpdate in ungroupCells.
 * The <code>cells</code> property contains the array of cells that was passed
 * to ungroupCells.
 * 
 * mxEvent.REMOVE_CELLS_FROM_PARENT fires between begin- and endUpdate in
 * removeCellsFromParent. The <code>cells</code> property contains the array of
 * cells that was passed to removeCellsFromParent.
 * 
 * mxEvent.ADD_CELLS fires between begin- and endUpdate in addCells. The
 * <code>cells</code>, <code>parent</code>, <code>index</code>,
 * <code>source</code> and <code>target</code> properties contain the
 * respective arguments that were passed to addCells.
 * 
 * mxEvent.CELLS_ADDED fires between begin- and endUpdate in cellsAdded. The
 * <code>cells</code>, <code>parent</code>, <code>index</code>,
 * <code>source</code>, <code>target</code> and <code>absolute</code>
 * properties contain the respective arguments that were passed to cellsAdded.
 * 
 * mxEvent.REMOVE_CELLS fires between begin- and endUpdate in removeCells. The
 * <code>cells</code> and <code>includeEdges</code> arguments contain the
 * respective arguments that were passed to removeCells.
 * 
 * mxEvent.CELLS_REMOVED fires between begin- and endUpdate in cellsRemoved.
 * The <code>cells</code> argument contains the array of cells that was
 * removed.
 * 
 * mxEvent.SPLIT_EDGE fires between begin- and endUpdate in splitEdge. The
 * <code>edge</code> property contains the edge to be splitted, the
 * <code>cells</code>, <code>newEdge</code>, <code>dx</code> and
 * <code>dy</code> properties contain the respective arguments that were passed
 * to splitEdge.
 * 
 * mxEvent.TOGGLE_CELLS fires between begin- and endUpdate in toggleCells. The
 * <code>show</code>, <code>cells</code> and <code>includeEdges</code>
 * properties contain the respective arguments that were passed to toggleCells.
 * 
 * mxEvent.FOLD_CELLS fires between begin- and endUpdate in foldCells. The
 * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
 * properties contain the respective arguments that were passed to foldCells.
 * 
 * mxEvent.CELLS_FOLDED fires between begin- and endUpdate in cellsFolded. The
 * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
 * properties contain the respective arguments that were passed to cellsFolded.
 * 
 * mxEvent.UPDATE_CELL_SIZE fires between begin- and endUpdate in
 * updateCellSize. The <code>cell</code> and <code>ignoreChildren</code>
 * properties contain the respective arguments that were passed to
 * updateCellSize.
 * 
 * mxEvent.RESIZE_CELLS fires between begin- and endUpdate in resizeCells. The
 * <code>cells</code> and <code>bounds</code> properties contain the respective
 * arguments that were passed to resizeCells.
 * 
 * mxEvent.CELLS_RESIZED fires between begin- and endUpdate in cellsResized.
 * The <code>cells</code> and <code>bounds</code> properties contain the
 * respective arguments that were passed to cellsResized.
 * 
 * mxEvent.MOVE_CELLS fires between begin- and endUpdate in moveCells. The
 * <code>cells</code>, <code>dx</code>, <code>dy</code>, <code>clone</code>,
 * <code>target</code> and <code>location</code> properties contain the
 * respective arguments that were passed to moveCells.
 * 
 * mxEvent.CELLS_MOVED fires between begin- and endUpdate in cellsMoved. The
 * <code>cells</code>, <code>dx</code>, <code>dy</code> and
 * <code>disconnect</code> properties contain the respective arguments that
 * were passed to cellsMoved.
 * 
 * mxEvent.CONNECT_CELL fires between begin- and endUpdate in connectCell. The
 * <code>edge</code>, <code>terminal</code> and <code>source</code> properties
 * contain the respective arguments that were passed to connectCell.
 * 
 * mxEvent.CELL_CONNECTED fires between begin- and endUpdate in cellConnected.
 * The <code>edge</code>, <code>terminal</code> and <code>source</code>
 * properties contain the respective arguments that were passed to
 * cellConnected.
 * 
 * mxEvent.REPAINT fires if a repaint was requested by calling repaint. The
 * <code>region</code> property contains the optional mxRectangle that was
 * passed to repaint to define the dirty region.
 */
public class mxGraph extends mxEventSource
{

	/**
	 * Adds required resources.
	 */
	static
	{
		try
		{
			mxResources.add("com.mxgraph.resources.graph");
		}
		catch (Exception e)
		{
			// ignore
		}
	}

	/**
	 * Holds the version number of this release. Current version
	 * is 1.4.0.9.
	 */
	public static final String VERSION = "1.4.0.9";

	/**
	 * 
	 */
	public interface mxICellVisitor
	{

		/**
		 * 
		 * @param vertex
		 * @param edge
		 */
		boolean visit(Object vertex, Object edge);

	}

	/**
	 * Property change event handling.
	 */
	protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
			this);

	/**
	 * Holds the model that contains the cells to be displayed.
	 */
	protected mxIGraphModel model;

	/**
	 * Holds the view that caches the cell states.
	 */
	protected mxGraphView view;

	/**
	 * Holds the stylesheet that defines the appearance of the cells.
	 */
	protected mxStylesheet stylesheet;

	/**
	 * Holds the <mxGraphSelection> that models the current selection.
	 */
	protected mxGraphSelectionModel selectionModel;

	/**
	 * Specifies the grid size. Default is 10.
	 */
	protected int gridSize = 10;

	/**
	 * Specifies if the grid is enabled. Default is true.
	 */
	protected boolean gridEnabled = true;

	/**
	 * Value returned by getOverlap if isAllowOverlapParent returns
	 * true for the given cell. getOverlap is used in keepInside if
	 * isKeepInsideParentOnMove returns true. The value specifies the
	 * portion of the child which is allowed to overlap the parent.
	 */
	protected double defaultOverlap = 0.5;

	/**
	 * Specifies the default parent to be used to insert new cells.
	 * This is used in getDefaultParent. Default is null.
	 */
	protected Object defaultParent;

	/**
	 * Specifies the alternate edge style to be used if the main control point
	 * on an edge is being doubleclicked. Default is null.
	 */
	protected String alternateEdgeStyle;

	/**
	 * Specifies the return value for isEnabled. Default is true.
	 */
	protected boolean enabled = true;

	/**
	 * Specifies the return value for isCell(s)Locked. Default is false.
	 */
	protected boolean cellsLocked = false;

	/**
	 * Specifies the return value for isCell(s)Editable. Default is true.
	 */
	protected boolean cellsEditable = true;

	/**
	 * Specifies the return value for isCell(s)Sizable. Default is true.
	 */
	protected boolean cellsResizable = true;

	/**
	 * Specifies the return value for isCell(s)Movable. Default is true.
	 */
	protected boolean cellsMovable = true;

	/**
	 * Specifies the return value for isCell(s)Bendable. Default is true.
	 */
	protected boolean cellsBendable = true;

	/**
	 * Specifies the return value for isCell(s)Selectable. Default is true.
	 */
	protected boolean cellsSelectable = true;

	/**
	 * Specifies the return value for isCell(s)Deletable. Default is true.
	 */
	protected boolean cellsDeletable = true;

	/**
	 * Specifies the return value for isCell(s)Cloneable. Default is true.
	 */
	protected boolean cellsCloneable = true;

	/**
	 * Specifies the return value for isCellDisconntableFromTerminal. Default
	 * is true.
	 */
	protected boolean cellsDisconnectable = true;

	/**
	 * Specifies the return value for isLabel(s)Clipped. Default is false.
	 */
	protected boolean labelsClipped = false;

	/**
	 * Specifies the return value for edges in isLabelMovable. Default is true.
	 */
	protected boolean edgeLabelsMovable = true;

	/**
	 * Specifies the return value for vertices in isLabelMovable. Default is false.
	 */
	protected boolean vertexLabelsMovable = false;

	/**
	 * Specifies the return value for isDropEnabled. Default is true.
	 */
	protected boolean dropEnabled = true;

	/**
	 * Specifies if dropping onto edges should be enabled. Default is true.
	 */
	protected boolean splitEnabled = true;

	/**
	 * Specifies if the graph should automatically update the cell size
	 * after an edit. This is used in isAutoSizeCell. Default is false.
	 */
	protected boolean autoSizeCells = false;

	/**
	 * <mxRectangle> that specifies the area in which all cells in the
	 * diagram should be placed. Uses in getMaximumGraphBounds. Use a width
	 * or height of 0 if you only want to give a upper, left corner.
	 */
	protected mxRectangle maximumGraphBounds = null;

	/**
	 * mxRectangle that specifies the minimum size of the graph canvas inside
	 * the scrollpane.
	 */
	protected mxRectangle minimumGraphSize = null;

	/**
	 * Border to be added to the bottom and right side when the container is
	 * being resized after the graph has been changed. Default is 0.
	 */
	protected int border = 0;

	/**
	 * Specifies if edges should appear in the foreground regardless of their
	 * order in the model. This has precendence over keepEdgeInBackground
	 * Default is false.
	 */
	protected boolean keepEdgesInForeground = false;

	/**
	 * Specifies if edges should appear in the background regardless of their
	 * order in the model. Default is false.
	 */
	protected boolean keepEdgesInBackground = false;

	/**
	 * Specifies if the cell size should be changed to the preferred size when
	 * a cell is first collapsed. Default is true.
	 */
	protected boolean collapseToPreferredSize = true;

	/**
	 * Specifies if negative coordinates for vertices are allowed. Default is true.
	 */
	protected boolean allowNegativeCoordinates = true;

	/**
	 * Specifies the return value for isConstrainChildren. Default is true.
	 */
	protected boolean constrainChildren = true;

	/**
	 * Specifies if a parent should contain the child bounds after a resize of
	 * the child. Default is true.
	 */
	protected boolean extendParents = true;

	/**
	 * Specifies if parents should be extended according to the <extendParents>
	 * switch if cells are added. Default is true.
	 */
	protected boolean extendParentsOnAdd = true;

	/**
	 * Specifies if the scale and translate should be reset if
	 * the root changes in the model. Default is true.
	 */
	protected boolean resetViewOnRootChange = true;

	/**
	 * Specifies if loops (aka self-references) are allowed.
	 * Default is false.
	 */
	protected boolean resetEdgesOnResize = false;

	/**
	 * Specifies if edge control points should be reset after
	 * the move of a connected cell. Default is false.
	 */
	protected boolean resetEdgesOnMove = false;

	/**
	 * Specifies if edge control points should be reset after
	 * the the edge has been reconnected. Default is true.
	 */
	protected boolean resetEdgesOnConnect = true;

	/**
	 * Specifies if loops (aka self-references) are allowed.
	 * Default is false.
	 */
	protected boolean allowLoops = false;

	/**
	 * Specifies the multiplicities to be used for validation of the graph.
	 */
	protected mxMultiplicity[] multiplicities;

	/**
	 * Specifies the default style for loops.
	 */
	protected mxEdgeStyle.mxEdgeStyleFunction defaultLoopStyle = mxEdgeStyle.Loop;

	/**
	 * Specifies if multiple edges in the same direction between
	 * the same pair of vertices are allowed. Default is true.
	 */
	protected boolean multigraph = true;

	/**
	 * Specifies if edges are connectable. Default is false.
	 * This overrides the connectable field in edges.
	 */
	protected boolean connectableEdges = false;

	/**
	 * Specifies if edges with disconnected terminals are
	 * allowed in the graph. Default is false.
	 */
	protected boolean allowDanglingEdges = true;

	/**
	 * Specifies if edges that are cloned should be validated and only inserted
	 * if they are valid. Default is true.
	 */
	protected boolean cloneInvalidEdges = false;

	/**
	 * Specifies if edges should be disconnected from their terminals when they
	 * are moved. Default is true.
	 */
	protected boolean disconnectOnMove = true;

	/**
	 * Specifies if labels should be visible. This is used in
	 * getLabel. Default is true.
	 */
	protected boolean labelsVisible = true;

	/**
	 * Specifies the return value for isHtmlLabel. Default is false.
	 */
	protected boolean htmlLabels = false;

	/**
	 * Specifies if nesting of swimlanes is allowed. Default is true.
	 */
	protected boolean swimlaneNesting = true;

	/**
	 * Specifies the maximum number of changes that should be processed to find
	 * the dirty region. If the number of changes is larger, then the complete
	 * grah is repainted. A value of zero will always compute the dirty region
	 * for any number of changes. Default is 1000.
	 */
	protected int changesRepaintThreshold = 1000;

	/**
	 * Specifies if the origin should be automatically updated. 
	 */
	protected boolean autoOrigin = false;

	/**
	 * Holds the current automatic origin.
	 */
	protected mxPoint origin = new mxPoint();

	/**
	 * Fires repaint events for full repaints.
	 */
	protected mxIEventListener fullRepaintHandler = new mxIEventListener()
	{
		public void invoke(Object sender, mxEventObject evt)
		{
			repaint();
		}
	};

	/**
	 * Fires repaint events for model changes.
	 */
	protected mxIEventListener graphModelChangeHandler = new mxIEventListener()
	{
		@SuppressWarnings("unchecked")
		public void invoke(Object sender, mxEventObject evt)
		{
			mxRectangle dirty = graphModelChanged((mxIGraphModel) sender,
					(List<mxUndoableChange>) evt.getProperty("changes"));
			repaint(dirty);
		}
	};

	/**
	 * Constructs a new graph with an empty
	 * {@link com.mxgraph.model.mxGraphModel}.
	 */
	public mxGraph()
	{
		this(null, null);
	}

	/**
	 * Constructs a new graph for the specified model. If no model is
	 * specified, then a new, empty {@link com.mxgraph.model.mxGraphModel} is
	 * used.
	 * 
	 * @param model Model that contains the graph data
	 */
	public mxGraph(mxIGraphModel model)
	{
		this(model, null);
	}

	/**
	 * Constructs a new graph for the specified model. If no model is
	 * specified, then a new, empty {@link com.mxgraph.model.mxGraphModel} is
	 * used.
	 * 
	 * @param stylesheet The stylesheet to use for the graph.
	 */
	public mxGraph(mxStylesheet stylesheet)
	{
		this(null, stylesheet);
	}

	/**
	 * Constructs a new graph for the specified model. If no model is
	 * specified, then a new, empty {@link com.mxgraph.model.mxGraphModel} is
	 * used.
	 * 
	 * @param model Model that contains the graph data
	 */
	public mxGraph(mxIGraphModel model, mxStylesheet stylesheet)
	{
		selectionModel = createSelectionModel();
		setModel((model != null) ? model : new mxGraphModel());
		setStylesheet((stylesheet != null) ? stylesheet : createStylesheet());
		setView(createGraphView());
	}

	/**
	 * Constructs a new selection model to be used in this graph.
	 */
	protected mxGraphSelectionModel createSelectionModel()
	{
		return new mxGraphSelectionModel(this);
	}

	/**
	 * Constructs a new stylesheet to be used in this graph.
	 */
	protected mxStylesheet createStylesheet()
	{
		return new mxStylesheet();
	}

	/**
	 * Constructs a new view to be used in this graph.
	 */
	protected mxGraphView createGraphView()
	{
		return new mxGraphView(this);
	}

	/**
	 * Returns the graph model that contains the graph data.
	 * 
	 * @return Returns the model that contains the graph data
	 */
	public mxIGraphModel getModel()
	{
		return model;
	}

	/**
	 * Sets the graph model that contains the data, and fires an
	 * mxEvent.CHANGE followed by an mxEvent.REPAINT event.
	 * 
	 * @param value Model that contains the graph data
	 */
	public void setModel(mxIGraphModel value)
	{
		if (model != null)
		{
			model.removeListener(graphModelChangeHandler);
		}

		Object oldModel = model;
		model = value;

		if (view != null)
		{
			view.revalidate();
		}

		model.addListener(mxEvent.CHANGE, graphModelChangeHandler);
		changeSupport.firePropertyChange("model", oldModel, model);
		repaint();
	}

	/**
	 * Returns the view that contains the cell states.
	 * 
	 * @return Returns the view that contains the cell states
	 */
	public mxGraphView getView()
	{
		return view;
	}

	/**
	 * Sets the view that contains the cell states.
	 * 
	 * @param value View that contains the cell states
	 */
	public void setView(mxGraphView value)
	{
		if (view != null)
		{
			view.removeListener(fullRepaintHandler);
		}

		Object oldView = view;
		view = value;

		if (view != null)
		{
			view.revalidate();
		}

		// Listens to changes in the view
		view.addListener(mxEvent.SCALE, fullRepaintHandler);
		view.addListener(mxEvent.TRANSLATE, fullRepaintHandler);
		view.addListener(mxEvent.SCALE_AND_TRANSLATE, fullRepaintHandler);
		view.addListener(mxEvent.UP, fullRepaintHandler);
		view.addListener(mxEvent.DOWN, fullRepaintHandler);

		changeSupport.firePropertyChange("view", oldView, view);
	}

	/**
	 * Returns the stylesheet that provides the style.
	 * 
	 * @return Returns the stylesheet that provides the style.
	 */
	public mxStylesheet getStylesheet()
	{
		return stylesheet;
	}

	/**
	 * Sets the stylesheet that provides the style.
	 * 
	 * @param value Stylesheet that provides the style.
	 */
	public void setStylesheet(mxStylesheet value)
	{
		mxStylesheet oldValue = stylesheet;
		stylesheet = value;

		changeSupport.firePropertyChange("stylesheet", oldValue, stylesheet);
	}

	/**
	 * Returns the cells to be selected for the given list of changes.
	 */
	public Object[] getSelectionCellsForChanges(List<mxUndoableChange> changes)
	{
		List<Object> cells = new ArrayList<Object>();
		Iterator<mxUndoableChange> it = changes.iterator();

		while (it.hasNext())
		{
			Object change = it.next();

			if (change instanceof mxChildChange)
			{
				cells.add(((mxChildChange) change).getChild());
			}
			else if (change instanceof mxTerminalChange)
			{
				cells.add(((mxTerminalChange) change).getCell());
			}
			else if (change instanceof mxValueChange)
			{
				cells.add(((mxValueChange) change).getCell());
			}
			else if (change instanceof mxStyleChange)
			{
				cells.add(((mxStyleChange) change).getCell());
			}
			else if (change instanceof mxGeometryChange)
			{
				cells.add(((mxGeometryChange) change).getCell());
			}
			else if (change instanceof mxCollapseChange)
			{
				cells.add(((mxCollapseChange) change).getCell());
			}
			else if (change instanceof mxVisibleChange)
			{
				mxVisibleChange vc = (mxVisibleChange) change;

				if (vc.isVisible())
				{
					cells.add(((mxVisibleChange) change).getCell());
				}
			}
		}

		return mxGraphModel.getTopmostCells(model, cells.toArray());
	}

	/**
	 * Called when the graph model changes. Invokes processChange on each
	 * item of the given array to update the view accordingly.
	 */
	public mxRectangle graphModelChanged(mxIGraphModel sender,
			List<mxUndoableChange> changes)
	{
		int thresh = getChangesRepaintThreshold();
		boolean ignoreDirty = thresh > 0 && changes.size() > thresh;

		// Ignores dirty rectangle if there was a root change
		if (!ignoreDirty)
		{
			Iterator<mxUndoableChange> it = changes.iterator();

			while (it.hasNext())
			{
				if (it.next() instanceof mxRootChange)
				{
					ignoreDirty = true;
					break;
				}
			}
		}

		mxRectangle dirty = processChanges(changes, true, ignoreDirty);
		view.validate();

		if (isAutoOrigin())
		{
			updateOrigin();
		}

		if (!ignoreDirty)
		{
			mxRectangle tmp = processChanges(changes, false, ignoreDirty);

			if (tmp != null)
			{
				if (dirty == null)
				{
					dirty = tmp;
				}
				else
				{
					dirty.add(tmp);
				}
			}
		}

		removeSelectionCells(getRemovedCellsForChanges(changes));

		return dirty;
	}

	/**
	 * Extends the canvas by doing another validation with a shifted
	 * global translation if the bounds of the graph are below (0,0).
	 * 
	 * The first validation is required to compute the bounds of the graph
	 * while the second validation is required to apply the new translate.
	 */
	protected void updateOrigin()
	{
		mxRectangle bounds = getGraphBounds();

		if (bounds != null)
		{
			double scale = getView().getScale();
			double x = bounds.getX() / scale - getBorder();
			double y = bounds.getY() / scale - getBorder();

			if (bounds != null && (x < 0 || y < 0))
			{
				double x0 = Math.min(0, x);
				double y0 = Math.min(0, y);

				origin.setX(origin.getX() + x0);
				origin.setY(origin.getY() + y0);

				mxPoint t = getView().getTranslate();
				getView().setTranslate(
						new mxPoint(t.getX() - x0, t.getY() - y0));
			}
			else if ((x > 0 || y > 0)
					&& (origin.getX() < 0 || origin.getY() < 0))
			{
				double dx = Math.min(-origin.getX(), x);
				double dy = Math.min(-origin.getY(), y);

				origin.setX(origin.getX() + dx);
				origin.setY(origin.getY() + dy);

				mxPoint t = getView().getTranslate();
				getView().setTranslate(
						new mxPoint(t.getX() - dx, t.getY() - dy));
			}
		}
	}

	/**
	 * Returns the cells that have been removed from the model.
	 */
	public Object[] getRemovedCellsForChanges(List<mxUndoableChange> changes)
	{
		List<Object> result = new ArrayList<Object>();
		Iterator<mxUndoableChange> it = changes.iterator();

		while (it.hasNext())
		{
			Object change = it.next();

			if (change instanceof mxRootChange)
			{
				break;
			}
			else if (change instanceof mxChildChange)
			{
				mxChildChange cc = (mxChildChange) change;

				if (cc.getParent() == null)
				{
					result.addAll(mxGraphModel.getDescendants(model, cc
							.getChild()));
				}
			}
			else if (change instanceof mxVisibleChange)
			{
				Object cell = ((mxVisibleChange) change).getCell();
				result.addAll(mxGraphModel.getDescendants(model, cell));
			}
		}

		return result.toArray();
	}

	/**
	 * Processes the changes and returns the minimal rectangle to be
	 * repainted in the buffer. A return value of null means no repaint
	 * is required.
	 */
	public mxRectangle processChanges(List<mxUndoableChange> changes,
			boolean invalidate, boolean ignoreDirty)
	{
		mxRectangle bounds = null;
		Iterator<mxUndoableChange> it = changes.iterator();

		while (it.hasNext())
		{
			mxRectangle rect = processChange(it.next(), invalidate, ignoreDirty);

			if (bounds == null)
			{
				bounds = rect;
			}
			else
			{
				bounds.add(rect);
			}
		}

		return bounds;
	}

	/**
	 * Processes the given change and invalidates the respective cached data
	 * in <view>. This fires a <root> event if the root has changed in the
	 * model.
	 */
	public mxRectangle processChange(mxUndoableChange change,
			boolean invalidate, boolean ignoreDirty)
	{
		mxRectangle result = null;

		if (change instanceof mxRootChange)
		{
			result = (ignoreDirty) ? null : getGraphBounds();

			if (invalidate)
			{
				clearSelection();
				removeStateForCell(((mxRootChange) change).getPrevious());

				if (isResetViewOnRootChange())
				{
					view.setEventsEnabled(false);

					try
					{
						view.scaleAndTranslate(1, 0, 0);
					}
					finally
					{
						view.setEventsEnabled(true);
					}
				}

			}

			fireEvent(new mxEventObject(mxEvent.ROOT));
		}
		else if (change instanceof mxChildChange)
		{
			mxChildChange cc = (mxChildChange) change;

			// Repaints the parent area if it is a rendered cell (vertex or
			// edge) otherwise only the child area is repainted, same holds
			// if the parent and previous are the same object, in which case
			// only the child area needs to be repainted (change of order)
			if (!ignoreDirty)
			{
				if (cc.getParent() != cc.getPrevious())
				{
					if (model.isVertex(cc.getParent())
							|| model.isEdge(cc.getParent()))
					{
						result = getBoundingBox(cc.getParent(), true, true);
					}

					if (model.isVertex(cc.getPrevious())
							|| model.isEdge(cc.getPrevious()))
					{
						if (result != null)
						{
							result.add(getBoundingBox(cc.getPrevious(), true,
									true));
						}
						else
						{
							result = getBoundingBox(cc.getPrevious(), true,
									true);
						}
					}
				}

				if (result == null)
				{
					result = getBoundingBox(cc.getChild(), true, true);
				}
			}

			if (invalidate)
			{
				if (cc.getParent() != null)
				{
					view.clear(cc.getChild(), false, true);
				}
				else
				{
					removeStateForCell(cc.getChild());
				}
			}
		}
		else if (change instanceof mxTerminalChange)
		{
			Object cell = ((mxTerminalChange) change).getCell();

			if (!ignoreDirty)
			{
				result = getBoundingBox(cell, true);
			}

			if (invalidate)
			{
				view.invalidate(cell);
			}
		}
		else if (change instanceof mxValueChange)
		{
			Object cell = ((mxValueChange) change).getCell();

			if (!ignoreDirty)
			{
				result = getBoundingBox(cell);
			}

			if (invalidate)
			{
				view.clear(cell, false, false);
			}
		}
		else if (change instanceof mxStyleChange)
		{
			Object cell = ((mxStyleChange) change).getCell();

			if (!ignoreDirty)
			{
				result = getBoundingBox(cell, true);
			}

			if (invalidate)
			{
				// TODO: Add includeEdges argument to clear method for
				// not having to call invalidate in this case (where it
				// is possible that the perimeter has changed, which
				// means the connected edges need to be invalidated)
				view.clear(cell, false, false);
				view.invalidate(cell);
			}
		}
		else if (change instanceof mxGeometryChange)
		{
			Object cell = ((mxGeometryChange) change).getCell();

			if (!ignoreDirty)
			{
				result = getBoundingBox(cell, true, true);
			}

			if (invalidate)
			{
				view.invalidate(cell);
			}
		}
		else if (change instanceof mxCollapseChange)
		{
			Object cell = ((mxCollapseChange) change).getCell();

			if (!ignoreDirty)
			{
				result = getBoundingBox(((mxCollapseChange) change).getCell(),
						true, true);
			}

			if (invalidate)
			{
				removeStateForCell(cell);
			}
		}
		else if (change instanceof mxVisibleChange)
		{
			Object cell = ((mxVisibleChange) change).getCell();

			if (!ignoreDirty)
			{
				result = getBoundingBox(((mxVisibleChange) change).getCell(),
						true, true);
			}

			if (invalidate)
			{
				removeStateForCell(cell);
			}
		}

		return result;
	}

	/**
	 * Removes all cached information for the given cell and its descendants.
	 * This is called when a cell was removed from the model.
	 * 
	 * @param cell Cell that was removed from the model.
	 */
	protected void removeStateForCell(Object cell)
	{
		int childCount = model.getChildCount(cell);

		for (int i = 0; i < childCount; i++)
		{
			removeStateForCell(model.getChildAt(cell, i));
		}

		view.removeState(cell);
	}

	//
	// Cell styles
	//

	/**
	 * Returns an array of key, value pairs representing the cell style for the
	 * given cell. If no string is defined in the model that specifies the
	 * style, then the default style for the cell is returned or <EMPTY_ARRAY>,
	 * if not style can be found.
	 * 
	 * @param cell Cell whose style should be returned.
	 * @return Returns the style of the cell.
	 */
	public Map<String, Object> getCellStyle(Object cell)
	{
		Map<String, Object> style = (model.isEdge(cell)) ? stylesheet
				.getDefaultEdgeStyle() : stylesheet.getDefaultVertexStyle();

		String name = model.getStyle(cell);

		if (name != null)
		{
			style = stylesheet.getCellStyle(name, style);
		}

		if (style == null)
		{
			style = mxStylesheet.EMPTY_STYLE;
		}

		return style;
	}

	/**
	 * Sets the style of the selection cells to the given value.
	 * 
	 * @param style String representing the new style of the cells.
	 */
	public Object[] setCellStyle(String style)
	{
		return setCellStyle(style, null);
	}

	/**
	 * Sets the style of the specified cells. If no cells are given, then the
	 * selection cells are changed.
	 * 
	 * @param style String representing the new style of the cells.
	 * @param cells Optional array of <mxCells> to set the style for. Default is the
	 * selection cells.
	 */
	public Object[] setCellStyle(String style, Object[] cells)
	{
		if (cells == null)
		{
			cells = getSelectionCells();
		}

		if (cells != null)
		{
			model.beginUpdate();
			try
			{
				for (int i = 0; i < cells.length; i++)
				{
					model.setStyle(cells[i], style);
				}
			}
			finally
			{
				model.endUpdate();
			}
		}

		return cells;
	}

	/**
	 * Toggles the boolean value for the given key in the style of the
	 * given cell. If no cell is specified then the selection cell is
	 * used.
	 * 
	 * @param key Key for the boolean value to be toggled.
	 * @param defaultValue Default boolean value if no value is defined.
	 * @param cell Cell whose style should be modified.
	 */
	public Object toggleCellStyle(String key, boolean defaultValue, Object cell)
	{
		return toggleCellStyles(key, defaultValue, new Object[] { cell })[0];
	}

	/**
	 * Toggles the boolean value for the given key in the style of the
	 * selection cells.
	 * 
	 * @param key Key for the boolean value to be toggled.
	 * @param defaultValue Default boolean value if no value is defined.
	 */
	public Object[] toggleCellStyles(String key, boolean defaultValue)
	{
		return toggleCellStyles(key, defaultValue, null);
	}

	/**
	 * Toggles the boolean value for the given key in the style of the given
	 * cells. If no cells are specified, then the selection cells are used. For
	 * example, this can be used to toggle mxConstants.STYLE_ROUNDED or any
	 * other style with a boolean value.
	 * 
	 * @param key String representing the key of the boolean style to be toggled.
	 * @param defaultValue Default boolean value if no value is defined.
	 * @param cells Cells whose styles should be modified.
	 */
	public Object[] toggleCellStyles(String key, boolean defaultValue,
			Object[] cells)
	{
		if (cells == null)
		{
			cells = getSelectionCells();
		}

		if (cells != null && cells.length > 0)
		{
			mxCellState state = view.getState(cells[0]);
			Map<String, Object> style = (state != null) ? state.getStyle()
					: getCellStyle(cells[0]);

			if (style != null)
			{
				String value = (mxUtils.isTrue(style, key, defaultValue)) ? "0"
						: "1";
				setCellStyles(key, value, cells);
			}
		}

		return cells;
	}

	/**
	 * Sets the key to value in the styles of the selection cells.
	 *
	 * @param key String representing the key to be assigned.
	 * @param value String representing the new value for the key.
	 */
	public Object[] setCellStyles(String key, String value)
	{
		return setCellStyles(key, value, null);
	}

	/**
	 * Sets the key to value in the styles of the given cells. This will modify
	 * the existing cell styles in-place and override any existing assignment
	 * for the given key. If no cells are specified, then the selection cells
	 * are changed. If no value is specified, then the respective key is
	 * removed from the styles.
	 * 
	 * @param key String representing the key to be assigned.
	 * @param value String representing the new value for the key.
	 * @param cells Array of cells to change the style for.
	 */
	public Object[] setCellStyles(String key, String value, Object[] cells)
	{
		if (cells == null)
		{
			cells = getSelectionCells();
		}

		mxUtils.setCellStyles(model, cells, key, value);

		return cells;
	}

	/**
	 * Toggles the given bit for the given key in the styles of the selection
	 * cells.
	 * 
	 * @param key String representing the key to toggle the flag in.
	 * @param flag Integer that represents the bit to be toggled.
	 */
	public Object[] toggleCellStyleFlags(String key, int flag)
	{
		return toggleCellStyleFlags(key, flag, null);
	}

	/**
	 * Toggles the given bit for the given key in the styles of the specified
	 * cells.
	 * 
	 * @param key String representing the key to toggle the flag in.
	 * @param flag Integer that represents the bit to be toggled.
	 * @param cells Optional array of <mxCells> to change the style for. Default is
	 * the selection cells.
	 */
	public Object[] toggleCellStyleFlags(String key, int flag, Object[] cells)
	{
		return setCellStyleFlags(key, flag, null, cells);
	}

	/**
	 * Sets or toggles the given bit for the given key in the styles of the
	 * selection cells.
	 * 
	 * @param key String representing the key to toggle the flag in.
	 * @param flag Integer that represents the bit to be toggled.
	 * @param value Boolean value to be used or null if the value should be
	 * toggled.
	 */
	public Object[] setCellStyleFlags(String key, int flag, boolean value)
	{
		return setCellStyleFlags(key, flag, value, null);
	}

	/**
	 * Sets or toggles the given bit for the given key in the styles of the
	 * specified cells.
	 * 
	 * @param key String representing the key to toggle the flag in.
	 * @param flag Integer that represents the bit to be toggled.
	 * @param value Boolean value to be used or null if the value should be
	 * toggled.
	 * @param cells Optional array of cells to change the style for. If no
	 * cells are specified then the selection cells are used.
	 */
	public Object[] setCellStyleFlags(String key, int flag, Boolean value,
			Object[] cells)
	{
		if (cells == null)
		{
			cells = getSelectionCells();
		}

		if (cells != null && cells.length > 0)
		{
			if (value == null)
			{
				mxCellState state = view.getState(cells[0]);
				Map<String, Object> style = (state != null) ? state.getStyle()
						: getCellStyle(cells[0]);

				if (style != null)
				{
					int current = mxUtils.getInt(style, key);
					value = !((current & flag) == flag);
				}
			}

			mxUtils.setCellStyleFlags(model, cells, key, flag, value);
		}

		return cells;
	}

	//
	// Cell alignment and orientation
	//

	/**
	 * Aligns the selection cells vertically or horizontally according to the
	 * given alignment.
	 * 
	 * @param align Specifies the alignment. Possible values are all constants
	 * in mxConstants with an ALIGN prefix.
	 */
	public Object[] alignCells(String align)
	{
		return alignCells(align, null);
	}

	/**
	 * Aligns the given cells vertically or horizontally according to the given
	 * alignment.
	 * 
	 * @param align Specifies the alignment. Possible values are all constants
	 * in mxConstants with an ALIGN prefix.
	 * @param cells Array of cells to be aligned.
	 */
	public Object[] alignCells(String align, Object[] cells)
	{
		return alignCells(align, cells, null);
	}

	/**
	 * Aligns the given cells vertically or horizontally according to the given
	 * alignment using the optional parameter as the coordinate.
	 * 
	 * @param align Specifies the alignment. Possible values are all constants
	 * in mxConstants with an ALIGN prefix.
	 * @param cells Array of cells to be aligned.
	 * @param param Optional coordinate for the alignment.
	 */
	public Object[] alignCells(String align, Object[] cells, Object param)
	{
		if (cells == null)
		{
			cells = getSelectionCells();
		}

		if (cells != null && cells.length > 1)
		{
			// Finds the required coordinate for the alignment
			if (param == null)
			{
				for (int i = 0; i < cells.length; i++)
				{
					mxGeometry geo = getCellGeometry(cells[i]);

					if (geo != null && !model.isEdge(cells[i]))
					{
						if (param == null)
						{
							if (align == null
									|| align.equals(mxConstants.ALIGN_LEFT))
							{
								param = geo.getX();
							}
							else if (align.equals(mxConstants.ALIGN_CENTER))
							{
								param = geo.getX() + geo.getWidth() / 2;
								break;
							}
							else if (align.equals(mxConstants.ALIGN_RIGHT))
							{
								param = geo.getX() + geo.getWidth();
							}
							else if (align.equals(mxConstants.ALIGN_TOP))
							{
								param = geo.getY();
							}
							else if (align.equals(mxConstants.ALIGN_MIDDLE))
							{
								param = geo.getY() + geo.getHeight() / 2;
								break;
							}
							else if (align.equals(mxConstants.ALIGN_BOTTOM))
							{
								param = geo.getY() + geo.getHeight();
							}
						}
						else
						{
							double tmp = Double.parseDouble(String
									.valueOf(param));

							if (align == null
									|| align.equals(mxConstants.ALIGN_LEFT))
							{
								param = Math.min(tmp, geo.getX());
							}
							else if (align.equals(mxConstants.ALIGN_RIGHT))
							{
								param = Math.max(tmp, geo.getX()
										+ geo.getWidth());
							}
							else if (align.equals(mxConstants.ALIGN_TOP))
							{
								param = Math.min(tmp, geo.getY());
							}
							else if (align.equals(mxConstants.ALIGN_BOTTOM))
							{
								param = Math.max(tmp, geo.getY()
										+ geo.getHeight());
							}
						}
					}
				}
			}

			// Aligns the cells to the coordinate
			model.beginUpdate();
			try
			{
				double tmp = Double.parseDouble(String.valueOf(param));

				for (int i = 0; i < cells.length; i++)
				{
					mxGeometry geo = getCellGeometry(cells[i]);

					if (geo != null && !model.isEdge(cells[i]))
					{
						geo = (mxGeometry) geo.clone();

						if (align == null
								|| align.equals(mxConstants.ALIGN_LEFT))
						{
							geo.setX(tmp);
						}
						else if (align.equals(mxConstants.ALIGN_CENTER))
						{
							geo.setX(tmp - geo.getWidth() / 2);
						}
						else if (align.equals(mxConstants.ALIGN_RIGHT))
						{
							geo.setX(tmp - geo.getWidth());
						}
						else if (align.equals(mxConstants.ALIGN_TOP))
						{
							geo.setY(tmp);
						}
						else if (align.equals(mxConstants.ALIGN_MIDDLE))
						{
							geo.setY(tmp - geo.getHeight() / 2);
						}
						else if (align.equals(mxConstants.ALIGN_BOTTOM))
						{
							geo.setY(tmp - geo.getHeight());
						}

						model.setGeometry(cells[i], geo);

						if (isResetEdgesOnMove())
						{
							resetEdges(new Object[] { cells[i] });
						}
					}
				}

				fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS, "cells",
						cells, "align", align));
			}
			finally
			{
				model.endUpdate();
			}
		}

		return cells;
	}

	/**
	 * Called when the main control point of the edge is double-clicked. This
	 * implementation switches between null (default) and alternateEdgeStyle
	 * and resets the edges control points. Finally, a flip event is fired
	 * before endUpdate is called on the model.
	 * 
	 * @param edge Cell that represents the edge to be flipped.
	 * @return Returns the edge that has been flipped.
	 */
	public Object flipEdge(Object edge)
	{
		if (edge != null && alternateEdgeStyle != null)
		{
			model.beginUpdate();
			try
			{
				String style = model.getStyle(edge);

				if (style == null || style.length() == 0)
				{
					model.setStyle(edge, alternateEdgeStyle);
				}
				else
				{
					model.setStyle(edge, null);
				}

				// Removes all existing control points
				resetEdge(edge);
				fireEvent(new mxEventObject(mxEvent.FLIP_EDGE, "edge", edge));
			}
			finally
			{
				model.endUpdate();
			}
		}

		return edge;
	}

	//
	// Order
	//

	/**
	 * Moves the selection cells to the front or back. This is a shortcut method.
	 * 
	 * @param back Specifies if the cells should be moved to back.
	 */
	public Object[] orderCells(boolean back)
	{
		return orderCells(back, null);
	}

	/**
	 * Moves the given cells to the front or back. The change is carried out
	 * using cellsOrdered. This method fires mxEvent.ORDER_CELLS while the
	 * transaction is in progress.
	 * 
	 * @param back Specifies if the cells should be moved to back.
	 * @param cells Array of cells whose order should be changed. If null is
	 * specified then the selection cells are used.
	 */
	public Object[] orderCells(boolean back, Object[] cells)
	{
		if (cells == null)
		{
			cells = mxUtils.sortCells(getSelectionCells(), true);
		}

		model.beginUpdate();
		try
		{
			cellsOrdered(cells, back);
			fireEvent(new mxEventObject(mxEvent.ORDER_CELLS, "cells", cells,
					"back", back));
		}
		finally
		{
			model.endUpdate();
		}

		return cells;
	}

	/**
	 * Moves the given cells to the front or back. This method fires
	 * mxEvent.CELLS_ORDERED while the transaction is in progress.
	 * 
	 * @param cells Array of cells whose order should be changed.
	 * @param back Specifies if the cells should be moved to back.
	 */
	public void cellsOrdered(Object[] cells, boolean back)
	{
		if (cells != null)
		{
			model.beginUpdate();
			try
			{
				for (int i = 0; i < cells.length; i++)
				{
					Object parent = model.getParent(cells[i]);

					if (back)
					{
						model.add(parent, cells[i], i);
					}
					else
					{
						model.add(parent, cells[i],
								model.getChildCount(parent) - 1);
					}
				}

				fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED, "cells",
						cells, "back", back));
			}
			finally
			{
				model.endUpdate();
			}
		}
	}

	//
	// Grouping
	//

	/**
	 * Groups the selection cells. This is a shortcut method.
	 * 
	 * @return Returns the new group.
	 */
	public Object groupCells()
	{
		return groupCells(null);
	}

	/**
	 * Groups the selection cells and adds them to the given group. This is a
	 * shortcut method.
	 * 
	 * @return Returns the new group.
	 */
	public Object groupCells(Object group)
	{
		return groupCells(group, 0);
	}

	/**
	 * Groups the selection cells and adds them to the given group. This is a
	 * shortcut method.
	 * 
	 * @return Returns the new group.
	 */
	public Object groupCells(Object group, double border)
	{
		return groupCells(group, border, null);
	}

	/**
	 * Adds the cells into the given group. The change is carried out using
	 * cellsAdded, cellsMoved and cellsResized. This method fires
	 * mxEvent.GROUP_CELLS while the transaction is in progress. Returns the
	 * new group. A group is only created if there is at least one entry in the
	 * given array of cells.
	 * 
	 * @param group Cell that represents the target group. If null is specified
	 * then a new group is created using createGroupCell.
	 * @param border Integer that specifies the border between the child area
	 * and the group bounds.
	 * @param cells Optional array of cells to be grouped. If null is specified
	 * then the selection cells are used.
	 */
	public Object groupCells(Object group, double border, Object[] cells)
	{
		if (cells == null)
		{
			cells = mxUtils.sortCells(getSelectionCells(), true);
		}

		cells = getCellsForGroup(cells);

		if (group == null)
		{
			group = createGroupCell(cells);
		}

		mxRectangle bounds = getBoundsForGroup(group, cells, border);

		if (cells.length > 0 && bounds != null)
		{
			Object parent = model.getParent(cells[0]);

			model.beginUpdate();
			try
			{
				// Checks if the group has a geometry and
				// creates one if one does not exist
				if (getCellGeometry(group) == null)
				{
					model.setGeometry(group, new mxGeometry());
				}

				// Adds the children into the group and moves
				int index = model.getChildCount(group);
				cellsAdded(cells, group, index, null, null, false);
				cellsMoved(cells, -bounds.getX(), -bounds.getY(), false, true);

				// Adds the group into the parent and resizes
				index = model.getChildCount(parent);
				cellsAdded(new Object[] { group }, parent, index, null, null,
						false);
				cellsResized(new Object[] { group },
						new mxRectangle[] { bounds });

				fireEvent(new mxEventObject(mxEvent.GROUP_CELLS, "group",
						group, "cells", cells, "border", border));
			}
			finally
			{
				model.endUpdate();
			}
		}

		return group;
	}

	/**
	 * Returns the cells with the same parent as the first cell
	 * in the given array.
	 */
	public Object[] getCellsForGroup(Object[] cells)
	{
		List<Object> result = new ArrayList<Object>(cells.length);

		if (cells != null && cells.length > 0)
		{
			Object parent = model.getParent(cells[0]);
			result.add(cells[0]);

			// Filters selection cells with the same parent
			for (int i = 1; i < cells.length; i++)
			{
				if (model.getParent(cells[i]) == parent)
				{
					result.add(cells[i]);
				}
			}
		}

		return result.toArray();
	}

	/**
	 * Returns the bounds to be used for the given group and children. This
	 * implementation computes the bounding box of the geometries of all
	 * vertices in the given children array. Edges are ignored. If the group
	 * cell is a swimlane the title region is added to the bounds.
	 */
	public mxRectangle getBoundsForGroup(Object group, Object[] children,
			double border)
	{
		mxRectangle result = getBoundingBoxFromGeometry(children);

		if (result != null)
		{
			if (isSwimlane(group))
			{
				mxRectangle size = getStartSize(group);

				result.setX(result.getX() - size.getWidth());
				result.setY(result.getY() - size.getHeight());
				result.setWidth(result.getWidth() + size.getWidth());
				result.setHeight(result.getHeight() + size.getHeight());
			}

			// Adds the border
			result.setX(result.getX() - border);
			result.setY(result.getY() - border);
			result.setWidth(result.getWidth() + 2 * border);
			result.setHeight(result.getHeight() + 2 * border);
		}

		return result;
	}

	/**
	 * Hook for creating the group cell to hold the given array of <mxCells> if
	 * no group cell was given to the <group> function. The children are just
	 * for informational purpose, they will be added to the returned group
	 * later. Note that the returned group should have a geometry. The
	 * coordinates of which are later overridden.
	 * 
	 * @param cells
	 * @return Returns a new group cell.
	 */
	public Object createGroupCell(Object[] cells)
	{
		mxCell group = new mxCell("", new mxGeometry(), null);
		group.setVertex(true);
		group.setConnectable(false);

		return group;
	}

	/**
	 * Ungroups the selection cells. This is a shortcut method.
	 */
	public Object[] ungroupCells()
	{
		return ungroupCells(null);
	}

	/**
	 * Ungroups the given cells by moving the children the children to their
	 * parents parent and removing the empty groups.
	 * 
	 * @param cells Array of cells to be ungrouped. If null is specified then
	 * the selection cells are used.
	 * @return Returns the children that have been removed from the groups.
	 */
	public Object[] ungroupCells(Object[] cells)
	{
		List<Object> result = new ArrayList<Object>();

		if (cells == null)
		{
			cells = getSelectionCells();

			// Finds the cells with children
			List<Object> tmp = new ArrayList<Object>(cells.length);

			for (int i = 0; i < cells.length; i++)
			{
				if (model.getChildCount(cells[i]) > 0)
				{
					tmp.add(cells[i]);
				}
			}

			cells = tmp.toArray();
		}

		if (cells != null && cells.length > 0)
		{
			model.beginUpdate();
			try
			{
				for (int i = 0; i < cells.length; i++)
				{
					Object[] children = mxGraphModel.getChildren(model,
							cells[i]);

					if (children != null && children.length > 0)
					{
						Object parent = model.getParent(cells[i]);
						int index = model.getChildCount(parent);

						cellsAdded(children, parent, index, null, null, true);
						result.addAll(Arrays.asList(children));
					}
				}

				cellsRemoved(addAllEdges(cells));
				fireEvent(new mxEventObject(mxEvent.UNGROUP_CELLS, "cells",
						cells));
			}
			finally
			{
				model.endUpdate();
			}
		}

		return result.toArray();
	}

	/**
	 * Removes the selection cells from their parents and adds them to the
	 * default parent returned by getDefaultParent.
	 */
	public Object[] removeCellsFromParent()
	{
		return removeCellsFromParent(null);
	}

	/**
	 * Removes the specified cells from their parents and adds them to the
	 * default parent.
	 * 
	 * @param cells Array of cells to be removed from their parents.
	 * @return Returns the cells that were removed from their parents.
	 */
	public Object[] removeCellsFromParent(Object[] cells)
	{
		if (cells == null)
		{
			cells = getSelectionCells();
		}

		model.beginUpdate();
		try
		{
			Object parent = getDefaultParent();
			int index = model.getChildCount(parent);

			cellsAdded(cells, parent, index, null, null, true);
			fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT,
					"cells", cells));
		}
		finally
		{
			model.endUpdate();
		}

		return cells;
	}

	/**
	 * Updates the bounds of the given array of groups so that it includes
	 * all child vertices.
	 */
	public Object[] updateGroupBounds()
	{
		return updateGroupBounds(null);
	}

	/**
	 * Updates the bounds of the given array of groups so that it includes
	 * all child vertices.
	 * 
	 * @param cells The groups whose bounds should be updated.
	 */
	public Object[] updateGroupBounds(Object[] cells)
	{
		return updateGroupBounds(cells, 0);
	}

	/**
	 * Updates the bounds of the given array of groups so that it includes
	 * all child vertices.
	 * 
	 * @param cells The groups whose bounds should be updated.
	 * @param border The border to be added in the group.
	 */
	public Object[] updateGroupBounds(Object[] cells, int border)
	{
		return updateGroupBounds(cells, border, false);
	}

	/**
	 * Updates the bounds of the given array of groups so that it includes
	 * all child vertices.
	 * 
	 * @param cells The groups whose bounds should be updated.
	 * @param border The border to be added in the group.
	 * @param moveParent Specifies if the group should be moved.
	 */
	public Object[] updateGroupBounds(Object[] cells, int border,
			boolean moveParent)
	{
		if (cells == null)
		{
			cells = getSelectionCells();
		}

		model.beginUpdate();
		try
		{
			for (int i = 0; i < cells.length; i++)
			{
				mxGeometry geo = getCellGeometry(cells[i]);

				if (geo != null)
				{
					Object[] children = getChildCells(cells[i]);

					if (children != null && children.length > 0)
					{
						mxRectangle childBounds = getBoundingBoxFromGeometry(children);

						if (childBounds.getWidth() > 0
								&& childBounds.getHeight() > 0)
						{
							mxRectangle size = (isSwimlane(cells[i])) ? getStartSize(cells[i])
									: new mxRectangle();

							geo = (mxGeometry) geo.clone();

							if (moveParent)
							{
								geo.setX(geo.getX() + childBounds.getX()
										- size.getWidth() - border);
								geo.setY(geo.getY() + childBounds.getY()
										- size.getHeight() - border);
							}

							geo.setWidth(childBounds.getWidth()
									+ size.getWidth() + 2 * border);
							geo.setHeight(childBounds.getHeight()
									+ size.getHeight() + 2 * border);

							model.setGeometry(cells[i], geo);
							moveCells(children, -childBounds.getX()
									+ size.getWidth() + border, -childBounds
									.getY()
									+ size.getHeight() + border);
						}
					}
				}
			}
		}
		finally
		{
			model.endUpdate();
		}

		return cells;
	}

	//
	// Cell cloning, insertion and removal
	//

	/**
	 * Clones all cells in the given array. To clone all children in a cell and
	 * add them to another graph:
	 * 
	 * <code>
	 * graph2.addCells(graph.cloneCells(new Object[] { parent }));
	 * </code>
	 */
	public Object[] cloneCells(Object[] cells)
	{
		return cloneCells(cells, true);
	}

	/**
	 * Returns the clones for the given cells. If the terminal of an edge is
	 * not in the given array, then the respective end is assigned a terminal
	 * point and the terminal is removed. If a cloned edge is invalid and
	 * allowInvalidEdges is false, then a null pointer will be at this position
	 * in the returned array. Use getCloneableCells on the input array to only
	 * clone the cells where isCellCloneable returns true.
	 * 
	 * @param cells Array of mxCells to be cloned.
	 * @return Returns the clones of the given cells.
	 */
	public Object[] cloneCells(Object[] cells, boolean allowInvalidEdges)
	{
		Object[] clones = null;

		if (cells != null)
		{
			Collection<Object> tmp = new LinkedHashSet<Object>(cells.length);
			tmp.addAll(Arrays.asList(cells));

			if (!tmp.isEmpty())
			{
				double scale = view.getScale();
				mxPoint trans = view.getTranslate();
				clones = model.cloneCells(cells, true);

				for (int i = 0; i < cells.length; i++)
				{
					if (!allowInvalidEdges
							&& model.isEdge(clones[i])
							&& getEdgeValidationError(clones[i], model
									.getTerminal(clones[i], true), model
									.getTerminal(clones[i], false)) != null)
					{
						clones[i] = null;
					}
					else
					{
						mxGeometry g = model.getGeometry(clones[i]);

						if (g != null)
						{
							mxCellState state = view.getState(cells[i]);
							mxCellState pstate = view.getState(model
									.getParent(cells[i]));

							if (state != null && pstate != null)
							{
								double dx = pstate.getOrigin().getX();
								double dy = pstate.getOrigin().getY();

								if (model.isEdge(clones[i]))
								{
									// Checks if the source is cloned or sets the terminal point
									Object src = model.getTerminal(cells[i],
											true);

									while (src != null && !tmp.contains(src))
									{
										src = model.getParent(src);
									}

									if (src == null)
									{
										mxPoint pt = state.getAbsolutePoint(0);
										g.setTerminalPoint(new mxPoint(pt
												.getX()
												/ scale - trans.getX(), pt
												.getY()
												/ scale - trans.getY()), true);
									}

									// Checks if the target is cloned or sets the terminal point
									Object trg = model.getTerminal(cells[i],
											false);

									while (trg != null && !tmp.contains(trg))
									{
										trg = model.getParent(trg);
									}

									if (trg == null)
									{
										mxPoint pt = state
												.getAbsolutePoint(state
														.getAbsolutePointCount() - 1);
										g.setTerminalPoint(new mxPoint(pt
												.getX()
												/ scale - trans.getX(), pt
												.getY()
												/ scale - trans.getY()), false);
									}

									// Translates the control points
									List<mxPoint> points = g.getPoints();

									if (points != null)
									{
										Iterator<mxPoint> it = points
												.iterator();

										while (it.hasNext())
										{
											mxPoint pt = it.next();
											pt.setX(pt.getX() + dx);
											pt.setY(pt.getY() + dy);
										}
									}
								}
								else
								{
									g.setX(g.getX() + dx);
									g.setY(g.getY() + dy);
								}
							}
						}
					}
				}
			}
			else
			{
				clones = new Object[] {};
			}
		}

		return clones;
	}

	/**
	 * Creates and adds a new vertex with an empty style.
	 */
	public Object insertVertex(Object parent, String id, Object value,
			double x, double y, double width, double height)
	{
		return insertVertex(parent, id, value, x, y, width, height, null);
	}

	/**
	 * Adds a new vertex into the given parent using value as the user object
	 * and the given coordinates as the geometry of the new vertex. The id and
	 * style are used for the respective properties of the new cell, which is
	 * returned.
	 * 
	 * @param parent Cell that specifies the parent of the new vertex.
	 * @param id Optional string that defines the Id of the new vertex.
	 * @param value Object to be used as the user object.
	 * @param x Integer that defines the x coordinate of the vertex.
	 * @param y Integer that defines the y coordinate of the vertex.
	 * @param width Integer that defines the width of the vertex.
	 * @param height Integer that defines the height of the vertex.
	 * @param style Optional string that defines the cell style.
	 * @return Returns the new vertex that has been inserted.
	 */
	public Object insertVertex(Object parent, String id, Object value,
			double x, double y, double width, double height, String style)
	{
		Object vertex = createVertex(parent, id, value, x, y, width, height,
				style);

		return addCell(vertex, parent);
	}

	/**
	 * Hook method that creates the new vertex for insertVertex.
	 * 
	 * @param parent Cell that specifies the parent of the new vertex.
	 * @param id Optional string that defines the Id of the new vertex.
	 * @param value Object to be used as the user object.
	 * @param x Integer that defines the x coordinate of the vertex.
	 * @param y Integer that defines the y coordinate of the vertex.
	 * @param width Integer that defines the width of the vertex.
	 * @param height Integer that defines the height of the vertex.
	 * @param style Optional string that defines the cell style.
	 * @return Returns the new vertex to be inserted.
	 */
	public Object createVertex(Object parent, String id, Object value,
			double x, double y, double width, double height, String style)
	{
		mxGeometry geometry = new mxGeometry(x, y, width, height);
		mxCell vertex = new mxCell(value, geometry, style);

		vertex.setId(id);
		vertex.setVertex(true);
		vertex.setConnectable(true);

		return vertex;
	}

	/**
	 * Creates and adds a new edge with an empty style.
	 */
	public Object insertEdge(Object parent, String id, Object value,
			Object source, Object target)
	{
		return insertEdge(parent, id, value, source, target, null);
	}

	/**
	 * Adds a new edge into the given parent using value as the user object and
	 * the given source and target as the terminals of the new edge. The Id and
	 * style are used for the respective properties of the new cell, which is
	 * returned.
	 * 
	 * @param parent Cell that specifies the parent of the new edge.
	 * @param id Optional string that defines the Id of the new edge.
	 * @param value Object to be used as the user object.
	 * @param source Cell that defines the source of the edge.
	 * @param target Cell that defines the target of the edge.
	 * @param style Optional string that defines the cell style.
	 * @return Returns the new edge that has been inserted.
	 */
	public Object insertEdge(Object parent, String id, Object value,
			Object source, Object target, String style)
	{
		Object edge = createEdge(parent, id, value, source, target, style);

		return addEdge(edge, parent, source, target, null);
	}

	/**
	 * Hook method that creates the new edge for insertEdge. This
	 * implementation does not set the source and target of the edge, these
	 * are set when the edge is added to the model.
	 * 
	 * @param parent Cell that specifies the parent of the new edge.
	 * @param id Optional string that defines the Id of the new edge.
	 * @param value Object to be used as the user object.
	 * @param source Cell that defines the source of the edge.
	 * @param target Cell that defines the target of the edge.
	 * @param style Optional string that defines the cell style.
	 * @return Returns the new edge to be inserted.
	 */
	public Object createEdge(Object parent, String id, Object value,
			Object source, Object target, String style)
	{
		mxCell edge = new mxCell(value, new mxGeometry(), style);

		edge.setId(id);
		edge.setEdge(true);
		edge.getGeometry().setRelative(true);

		return edge;
	}

	/**
	 * Adds the edge to the parent and connects it to the given source and
	 * target terminals. This is a shortcut method.
	 * 
	 * @param edge Edge to be inserted into the given parent.
	 * @param parent Object that represents the new parent. If no parent is
	 * given then the default parent is used.
	 * @param source Optional cell that represents the source terminal.
	 * @param target Optional cell that represents the target terminal.
	 * @param index Optional index to insert the cells at. Default is to append.
	 * @return Returns the edge that was added.
	 */
	public Object addEdge(Object edge, Object parent, Object source,
			Object target, Integer index)
	{
		return addCell(edge, parent, index, source, target);
	}

	/**
	 * Adds the cell to the default parent. This is a shortcut method.
	 * 
	 * @param cell Cell to be inserted.
	 * @return Returns the cell that was added.
	 */
	public Object addCell(Object cell)
	{
		return addCell(cell, null);
	}

	/**
	 * Adds the cell to the parent. This is a shortcut method.
	 * 
	 * @param cell Cell tobe inserted.
	 * @param parent Object that represents the new parent. If no parent is
	 * given then the default parent is used.
	 * @return Returns the cell that was added.
	 */
	public Object addCell(Object cell, Object parent)
	{
		return addCell(cell, parent, null, null, null);
	}

	/**
	 * Adds the cell to the parent and connects it to the given source and
	 * target terminals. This is a shortcut method.
	 * 
	 * @param cell Cell to be inserted into the given parent.
	 * @param parent Object that represents the new parent. If no parent is
	 * given then the default parent is used.
	 * @param index Optional index to insert the cells at. Default is to append.
	 * @param source Optional cell that represents the source terminal.
	 * @param target Optional cell that represents the target terminal.
	 * @return Returns the cell that was added.
	 */
	public Object addCell(Object cell, Object parent, Integer index,
			Object source, Object target)
	{
		return addCells(new Object[] { cell }, parent, index, source, target)[0];
	}

	/**
	 * Adds the cells to the default parent. This is a shortcut method.
	 * 
	 * @param cells Array of cells to be inserted.
	 * @return Returns the cells that were added.
	 */
	public Object[] addCells(Object[] cells)
	{
		return addCells(cells, null);
	}

	/**
	 * Adds the cells to the parent. This is a shortcut method.
	 * 
	 * @param cells Array of cells to be inserted.
	 * @param parent Optional cell that represents the new parent. If no parent
	 * is specified then the default parent is used.
	 * @return Returns the cells that were added.
	 */
	public Object[] addCells(Object[] cells, Object parent)
	{
		return addCells(cells, parent, null);
	}

	/**
	 * Adds the cells to the parent at the given index. This is a shortcut method.
	 * 
	 * @param cells Array of cells to be inserted.
	 * @param parent Optional cell that represents the new parent. If no parent
	 * is specified then the default parent is used.
	 * @param index Optional index to insert the cells at. Default is to append.
	 * @return Returns the cells that were added.
	 */
	public Object[] addCells(Object[] cells, Object parent, Integer index)
	{
		return addCells(cells, parent, index, null, null);
	}

	/**
	 * Adds the cells to the parent at the given index, connecting each cell to
	 * the optional source and target terminal. The change is carried out using
	 * cellsAdded. This method fires mxEvent.ADD_CELLS while the transaction
	 * is in progress.
	 * 
	 * @param cells Array of cells to be added.
	 * @param parent Optional cell that represents the new parent. If no parent
	 * is specified then the default parent is used.
	 * @param index Optional index to insert the cells at. Default is to append.
	 * @param source Optional source terminal for all inserted cells.
	 * @param target Optional target terminal for all inserted cells.
	 * @return Returns the cells that were added.
	 */
	public Object[] addCells(Object[] cells, Object parent, Integer index,
			Object source, Object target)
	{
		if (parent == null)
		{
			parent = getDefaultParent();
		}

		if (index == null)
		{
			index = model.getChildCount(parent);
		}

		model.beginUpdate();
		try
		{
			cellsAdded(cells, parent, index, source, target, false);
			fireEvent(new mxEventObject(mxEvent.ADD_CELLS, "cells", cells,
					"parent", parent, "index", index, "source", source,
					"target", target));
		}
		finally
		{
			model.endUpdate();
		}

		return cells;
	}

	/**
	 * Adds the specified cells to the given parent. This method fires
	 * mxEvent.CELLS_ADDED while the transaction is in progress.
	 */
	public void cellsAdded(Object[] cells, Object parent, Integer index,
			Object source, Object target, boolean absolute)
	{
		if (cells != null && parent != null && index != null)
		{
			model.beginUpdate();
			try
			{
				mxCellState parentState = (absolute) ? view.getState(parent)
						: null;
				mxPoint o1 = (parentState != null) ? parentState.getOrigin()
						: null;
				mxPoint zero = new mxPoint(0, 0);

				for (int i = 0; i < cells.length; i++)
				{
					Object previous = model.getParent(cells[i]);

					// Keeps the cell at its absolute location
					if (o1 != null && cells[i] != parent && parent != previous)
					{
						mxCellState oldState = view.getState(previous);
						mxPoint o2 = (oldState != null) ? oldState.getOrigin()
								: zero;
						mxGeometry geo = model.getGeometry(cells[i]);

						if (geo != null)
						{
							double dx = o2.getX() - o1.getX();
							double dy = o2.getY() - o1.getY();

							geo = (mxGeometry) geo.clone();
							geo.translate(dx, dy);

							if (!geo.isRelative() && model.isVertex(cells[i])
									&& !isAllowNegativeCoordinates())
							{
								geo.setX(Math.max(0, geo.getX()));
								geo.setY(Math.max(0, geo.getY()));
							}

							model.setGeometry(cells[i], geo);
						}
					}

					// Decrements all following indices
					// if cell is already in parent
					if (parent == previous)
					{
						index--;
					}

					model.add(parent, cells[i], index + i);

					// Extends the parent
					if (isExtendParentsOnAdd() && isExtendParent(cells[i]))
					{
						extendParent(cells[i]);
					}

					// Constrains the child
					constrainChild(cells[i]);

					// Sets the source terminal
					if (source != null)
					{
						cellConnected(cells[i], source, true, null);
					}

					// Sets the target terminal
					if (target != null)
					{
						cellConnected(cells[i], target, false, null);
					}
				}

				fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, "cells",
						cells, "parent", parent, "index", index, "source",
						source, "target", target, "absolute", absolute));

			}
			finally
			{
				model.endUpdate();
			}
		}
	}

	/**
	 * Removes the selection cells from the graph.
	 * 
	 * @return Returns the cells that have been removed.
	 */
	public Object[] removeCells()
	{
		return removeCells(null);
	}

	/**
	 * Removes the given cells from the graph.
	 * 
	 * @param cells Array of cells to remove.
	 * @return Returns the cells that have been removed.
	 */
	public Object[] removeCells(Object[] cells)
	{
		return removeCells(cells, true);
	}

	/**
	 * Removes the given cells from the graph including all connected edges if
	 * includeEdges is true. The change is carried out using cellsRemoved. This
	 * method fires mxEvent.REMOVE_CELLS while the transaction is in progress.
	 * 
	 * @param cells Array of cells to remove. If null is specified then the
	 * selection cells which are deletable are used.
	 * @param includeEdges Specifies if all connected edges should be removed as
	 * well.
	 */
	public Object[] removeCells(Object[] cells, boolean includeEdges)
	{
		if (cells == null)
		{
			cells = getDeletableCells(getSelectionCells());
		}

		// Adds all edges to the cells
		if (includeEdges)
		{
			cells = getDeletableCells(addAllEdges(cells));
		}

		model.beginUpdate();
		try
		{
			cellsRemoved(cells);
			fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS, "cells", cells,
					"includeEdges", includeEdges));
		}
		finally
		{
			model.endUpdate();
		}

		return cells;
	}

	/**
	 * Removes the given cells from the model. This method fires
	 * mxEvent.CELLS_REMOVED while the transaction is in progress.
	 * 
	 * @param cells Array of cells to remove.
	 */
	public void cellsRemoved(Object[] cells)
	{
		if (cells != null && cells.length > 0)
		{
			double scale = view.getScale();
			mxPoint tr = view.getTranslate();

			model.beginUpdate();
			try
			{
				for (int i = 0; i < cells.length; i++)
				{
					// Disconnects edges which are not in cells
					Collection<Object> cellSet = new HashSet<Object>();
					cellSet.addAll(Arrays.asList(cells));
					Object[] edges = getConnections(cells[i]);

					for (int j = 0; j < edges.length; j++)
					{
						if (!cellSet.contains(edges[j]))
						{
							mxGeometry geo = model.getGeometry(edges[j]);

							if (geo != null)
							{
								mxCellState state = view.getState(edges[j]);

								if (state != null)
								{
									geo = (mxGeometry) geo.clone();
									boolean source = view.getVisibleTerminal(
											edges[j], true) == cells[i];
									int n = (source) ? 0 : state
											.getAbsolutePointCount() - 1;
									mxPoint pt = state.getAbsolutePoint(n);

									geo.setTerminalPoint(new mxPoint(pt.getX()
											/ scale - tr.getX(), pt.getY()
											/ scale - tr.getY()), source);
									model.setTerminal(edges[j], null, source);
									model.setGeometry(edges[j], geo);
								}
							}
						}
					}

					model.remove(cells[i]);
				}

				fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED, "cells",
						cells));
			}
			finally
			{
				model.endUpdate();
			}
		}
	}

	/**
	 * 
	 */
	public Object splitEdge(Object edge, Object[] cells)
	{
		return splitEdge(edge, cells, null, 0, 0);
	}

	/**
	 * 
	 */
	public Object splitEdge(Object edge, Object[] cells, double dx, double dy)
	{
		return splitEdge(edge, cells, null, dx, dy);
	}

	/**
	 * Splits the given edge by adding a newEdge between the previous source
	 * and the given cell and reconnecting the source of the given edge to the
	 * given cell. Fires mxEvent.SPLIT_EDGE while the transaction is in
	 * progress.
	 * 
	 * @param edge Object that represents the edge to be splitted.
	 * @param cells Array that contains the cells to insert into the edge.
	 * @param newEdge Object that represents the edge to be inserted.
	 * @return Returns the new edge that has been inserted.
	 */
	public Object splitEdge(Object edge, Object[] cells, Object newEdge,
			double dx, double dy)
	{
		if (newEdge == null)
		{
			newEdge = cloneCells(new Object[] { edge })[0];
		}

		Object parent = model.getParent(edge);
		Object source = model.getTerminal(edge, true);

		model.beginUpdate();
		try
		{
			cellsMoved(cells, dx, dy, false, false);
			cellsAdded(cells, parent, model.getChildCount(parent), null, null,
					true);
			cellsAdded(new Object[] { newEdge }, parent, model
					.getChildCount(parent), source, cells[0], false);
			cellConnected(edge, cells[0], true, null);
			fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE, "edge", edge,
					"cells", cells, "newEdge", newEdge, "dx", dx, "dy", dy));
		}
		finally
		{
			model.endUpdate();
		}

		return newEdge;
	}

	//
	// Cell visibility
	//

	/**
	 * Sets the visible state of the selection cells. This is a shortcut
	 * method.
	 * 
	 * @param show Boolean that specifies the visible state to be assigned.
	 * @return Returns the cells whose visible state was changed.
	 */
	public Object[] toggleCells(boolean show)
	{
		return toggleCells(show, null);
	}

	/**
	 * Sets the visible state of the specified cells. This is a shortcut
	 * method.
	 *
	 * @param show Boolean that specifies the visible state to be assigned.
	 * @param cells Array of cells whose visible state should be changed.
	 * @return Returns the cells whose visible state was changed.
	 */
	public Object[] toggleCells(boolean show, Object[] cells)
	{
		return toggleCells(show, cells, true);
	}

	/**
	 * Sets the visible state of the specified cells and all connected edges
	 * if includeEdges is true. The change is carried out using cellsToggled.
	 * This method fires mxEvent.TOGGLE_CELLS while the transaction is in
	 * progress.
	 *
	 * @param show Boolean that specifies the visible state to be assigned.
	 * @param cells Array of cells whose visible state should be changed. If
	 * null is specified then the selection cells are used.
	 * @return Returns the cells whose visible state was changed.
	 */
	public Object[] toggleCells(boolean show, Object[] cells,
			boolean includeEdges)
	{
		if (cells == null)
		{
			cells = getSelectionCells();
		}

		// Adds all connected edges recursively
		if (includeEdges)
		{
			cells = addAllEdges(cells);
		}

		model.beginUpdate();
		try
		{
			cellsToggled(cells, show);
			fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS, "show", show,
					"cells", cells, "includeEdges", includeEdges));
		}
		finally
		{
			model.endUpdate();
		}

		return cells;
	}

	/**
	 * Sets the visible state of the specified cells.
	 * 
	 * @param cells Array of cells whose visible state should be changed.
	 * @param show Boolean that specifies the visible state to be assigned.
	 */
	public void cellsToggled(Object[] cells, boolean show)
	{
		if (cells != null && cells.length > 0)
		{
			model.beginUpdate();
			try
			{
				for (int i = 0; i < cells.length; i++)
				{
					model.setVisible(cells[i], show);
				}
			}
			finally
			{
				model.endUpdate();
			}
		}
	}

	//
	// Folding
	//

	/**
	 * Sets the collapsed state of the selection cells without recursion.
	 * This is a shortcut method.
	 * 
	 * @param collapse Boolean that specifies the collapsed state to be
	 * assigned.
	 * @return Returns the cells whose collapsed state was changed.
	 */
	public Object[] foldCells(boolean collapse)
	{
		return foldCells(collapse, false);
	}

	/**
	 * Sets the collapsed state of the selection cells. This is a shortcut
	 * method.
	 * 
	 * @param collapse Boolean that specifies the collapsed state to be
	 * assigned.
	 * @param recurse Boolean that specifies if the collapsed state should
	 * be assigned to all descendants.
	 * @return Returns the cells whose collapsed state was changed.
	 */
	public Object[] foldCells(boolean collapse, boolean recurse)
	{
		return foldCells(collapse, recurse, null);
	}

	/**
	 * Sets the collapsed state of the specified cells and all descendants
	 * if recurse is true. The change is carried out using cellsFolded.
	 * This method fires mxEvent.FOLD_CELLS while the transaction is in
	 * progress. Returns the cells whose collapsed state was changed.
	 * 
	 * @param collapse Boolean indicating the collapsed state to be assigned.
	 * @param recurse Boolean indicating if the collapsed state of all
	 * descendants should be set.
	 * @param cells Array of cells whose collapsed state should be set. If
	 * null is specified then the foldable selection cells are used.
	 */
	public Object[] foldCells(boolean collapse, boolean recurse, Object[] cells)
	{
		if (cells == null)
		{
			cells = getFoldableCells(getSelectionCells(), collapse);
		}

		model.beginUpdate();
		try
		{
			cellsFolded(cells, collapse, recurse);
			fireEvent(new mxEventObject(mxEvent.FOLD_CELLS, "cells", cells,
					"collapse", collapse, "recurse", recurse));
		}
		finally
		{
			model.endUpdate();
		}

		return cells;
	}

	/**
	 * Sets the collapsed state of the specified cells. This method fires
	 * mxEvent.CELLS_FOLDED while the transaction is in progress. Returns the
	 * cells whose collapsed state was changed.
	 * 
	 * @param cells Array of cells whose collapsed state should be set.
	 * @param collapse Boolean indicating the collapsed state to be assigned.
	 * @param recurse Boolean indicating if the collapsed state of all
	 * descendants should be set.
	 */
	public void cellsFolded(Object[] cells, boolean collapse, boolean recurse)
	{
		if (cells != null && cells.length > 0)
		{
			model.beginUpdate();
			try
			{
				for (int i = 0; i < cells.length; i++)
				{
					if (collapse != isCellCollapsed(cells[i]))
					{
						model.setCollapsed(cells[i], collapse);
						swapBounds(cells[i], collapse);

						if (isExtendParent(cells[i]))
						{
							extendParent(cells[i]);
						}

						if (recurse)
						{
							Object[] children = mxGraphModel.getChildren(model,
									cells[i]);
							cellsFolded(children, collapse, recurse);
						}
					}
				}

				fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED, "cells",
						cells, "collapse", collapse, "recurse", recurse));
			}
			finally
			{
				model.endUpdate();
			}
		}
	}

	/**
	 * Swaps the alternate and the actual bounds in the geometry of the given
	 * cell invoking updateAlternateBounds before carrying out the swap.
	 * 
	 * @param cell Cell for which the bounds should be swapped.
	 * @param willCollapse Boolean indicating if the cell is going to be collapsed.
	 */
	public void swapBounds(Object cell, boolean willCollapse)
	{
		if (cell != null)
		{
			mxGeometry geo = model.getGeometry(cell);

			if (geo != null)
			{
				geo = (mxGeometry) geo.clone();

				updateAlternateBounds(cell, geo, willCollapse);
				geo.swap();

				model.setGeometry(cell, geo);
			}
		}
	}

	/**
	 * Updates or sets the alternate bounds in the given geometry for the given
	 * cell depending on whether the cell is going to be collapsed. If no
	 * alternate bounds are defined in the geometry and
	 * collapseToPreferredSize is true, then the preferred size is used for
	 * the alternate bounds. The top, left corner is always kept at the same
	 * location.
	 * 
	 * @param cell Cell for which the geometry is being udpated.
	 * @param geo Geometry for which the alternate bounds should be updated.
	 * @param willCollapse Boolean indicating if the cell is going to be collapsed.
	 */
	public void updateAlternateBounds(Object cell, mxGeometry geo,
			boolean willCollapse)
	{
		if (cell != null && geo != null)
		{
			if (geo.getAlternateBounds() == null)
			{
				mxRectangle bounds = null;

				if (isCollapseToPreferredSize())
				{
					bounds = getPreferredSizeForCell(cell);

					if (isSwimlane(cell))
					{
						mxRectangle size = getStartSize(cell);

						bounds.setHeight(Math.max(bounds.getHeight(), size
								.getHeight()));
						bounds.setWidth(Math.max(bounds.getWidth(), size
								.getWidth()));
					}
				}

				if (bounds == null)
				{
					bounds = geo;
				}

				geo.setAlternateBounds(new mxRectangle(geo.getX(), geo.getY(),
						bounds.getWidth(), bounds.getHeight()));
			}
			else
			{
				geo.getAlternateBounds().setX(geo.getX());
				geo.getAlternateBounds().setY(geo.getY());
			}
		}
	}

	/**
	 * Returns an array with the given cells and all edges that are connected
	 * to a cell or one of its descendants.
	 */
	public Object[] addAllEdges(Object[] cells)
	{
		List<Object> allCells = new ArrayList<Object>(cells.length);
		allCells.addAll(Arrays.asList(cells));
		allCells.addAll(Arrays.asList(getAllEdges(cells)));

		return allCells.toArray();
	}

	/**
	 * Returns all edges connected to the given cells or their descendants.
	 */
	public Object[] getAllEdges(Object[] cells)
	{
		List<Object> edges = new ArrayList<Object>();

		if (cells != null)
		{
			for (int i = 0; i < cells.length; i++)
			{
				int edgeCount = model.getEdgeCount(cells[i]);

				for (int j = 0; j < edgeCount; j++)
				{
					edges.add(model.getEdgeAt(cells[i], j));
				}

				// Recurses
				Object[] children = mxGraphModel.getChildren(model, cells[i]);
				edges.addAll(Arrays.asList(getAllEdges(children)));
			}
		}

		return edges.toArray();
	}

	//
	// Cell sizing
	//

	/**
	 * Updates the size of the given cell in the model using
	 * getPreferredSizeForCell to get the new size. This function
	 * fires beforeUpdateSize and afterUpdateSize events.
	 * 
	 * @param cell <mxCell> for which the size should be changed.
	 */
	public Object updateCellSize(Object cell)
	{
		return updateCellSize(cell, false);
	}

	/**
	 * Updates the size of the given cell in the model using
	 * getPreferredSizeForCell to get the new size. This function
	 * fires mxEvent.UPDATE_CELL_SIZE.
	 * 
	 * @param cell Cell for which the size should be changed.
	 */
	public Object updateCellSize(Object cell, boolean ignoreChildren)
	{
		model.beginUpdate();
		try
		{
			cellSizeUpdated(cell, ignoreChildren);
			fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE, "cell", cell,
					"ignoreChildren", ignoreChildren));
		}
		finally
		{
			model.endUpdate();
		}

		return cell;
	}

	/**
	 * Updates the size of the given cell in the model using
	 * getPreferredSizeForCell to get the new size.
	 * 
	 * @param cell Cell for which the size should be changed.
	 */
	public void cellSizeUpdated(Object cell, boolean ignoreChildren)
	{
		if (cell != null)
		{
			model.beginUpdate();
			try
			{
				mxRectangle size = getPreferredSizeForCell(cell);
				mxGeometry geo = model.getGeometry(cell);

				if (size != null && geo != null)
				{
					boolean collapsed = isCellCollapsed(cell);
					geo = (mxGeometry) geo.clone();

					if (isSwimlane(cell))
					{
						mxCellState state = view.getState(cell);
						Map<String, Object> style = (state != null) ? state
								.getStyle() : getCellStyle(cell);
						String cellStyle = model.getStyle(cell);

						if (cellStyle == null)
						{
							cellStyle = "";
						}

						if (mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL,
								true))
						{
							cellStyle = mxUtils.setStyle(cellStyle,
									mxConstants.STYLE_STARTSIZE, String
											.valueOf(size.getHeight() + 8));

							if (collapsed)
							{
								geo.setHeight(size.getHeight() + 8);
							}

							geo.setWidth(size.getWidth());
						}
						else
						{
							cellStyle = mxUtils.setStyle(cellStyle,
									mxConstants.STYLE_STARTSIZE, String
											.valueOf(size.getWidth() + 8));

							if (collapsed)
							{
								geo.setWidth(size.getWidth() + 8);
							}

							geo.setHeight(size.getHeight());
						}

						model.setStyle(cell, cellStyle);
					}
					else
					{
						geo.setWidth(size.getWidth());
						geo.setHeight(size.getHeight());
					}

					if (!ignoreChildren && !collapsed)
					{
						mxRectangle bounds = view.getBounds(mxGraphModel
								.getChildren(model, cell));

						if (bounds != null)
						{
							mxPoint tr = view.getTranslate();
							double scale = view.getScale();

							double width = (bounds.getX() + bounds.getWidth())
									/ scale - geo.getX() - tr.getX();
							double height = (bounds.getY() + bounds.getHeight())
									/ scale - geo.getY() - tr.getY();

							geo.setWidth(Math.max(geo.getWidth(), width));
							geo.setHeight(Math.max(geo.getHeight(), height));
						}
					}

					cellsResized(new Object[] { cell },
							new mxRectangle[] { geo });
				}
			}
			finally
			{
				model.endUpdate();
			}
		}
	}

	/**
	 * Returns the preferred width and height of the given <mxCell> as an
	 * <mxRectangle>.
	 * 
	 * @param cell <mxCell> for which the preferred size should be returned.
	 */
	public mxRectangle getPreferredSizeForCell(Object cell)
	{
		mxRectangle result = null;

		if (cell != null)
		{
			mxCellState state = view.getState(cell);
			Map<String, Object> style = (state != null) ? state.style
					: getCellStyle(cell);

			if (style != null && !model.isEdge(cell))
			{
				double dx = 0;
				double dy = 0;

				// Adds dimension of image if shape is a label
				if (getImage(state) != null
						|| mxUtils.getString(style, mxConstants.STYLE_IMAGE) != null)
				{
					if (mxUtils.getString(style, mxConstants.STYLE_SHAPE, "")
							.equals(mxConstants.SHAPE_LABEL))
					{
						if (mxUtils.getString(style,
								mxConstants.STYLE_VERTICAL_ALIGN, "").equals(
								mxConstants.ALIGN_MIDDLE))
						{
							dx += mxUtils.getDouble(style,
									mxConstants.STYLE_IMAGE_WIDTH,
									mxConstants.DEFAULT_IMAGESIZE);
						}

						if (mxUtils.getString(style, mxConstants.STYLE_ALIGN,
								"").equals(mxConstants.ALIGN_CENTER))
						{
							dy += mxUtils.getDouble(style,
									mxConstants.STYLE_IMAGE_HEIGHT,
									mxConstants.DEFAULT_IMAGESIZE);
						}
					}
				}

				// Adds spacings
				double spacing = mxUtils.getDouble(style,
						mxConstants.STYLE_SPACING);
				dx += 2 * spacing;
				dx += mxUtils.getDouble(style, mxConstants.STYLE_SPACING_LEFT);
				dx += mxUtils.getDouble(style, mxConstants.STYLE_SPACING_RIGHT);

				dy += 2 * spacing;
				dy += mxUtils.getDouble(style, mxConstants.STYLE_SPACING_TOP);
				dy += mxUtils
						.getDouble(style, mxConstants.STYLE_SPACING_BOTTOM);

				// LATER: Add space for collapse/expand icon if applicable

				// Adds space for label
				String value = getLabel(cell);

				if (value != null && value.length() > 0)
				{
					// FIXME: Check word-wrap style and balance width/height
					mxRectangle size = mxUtils.getLabelSize(value, style,
							isHtmlLabel(cell), 0);
					double width = size.getWidth() + dx;
					double height = size.getHeight() + dy;

					if (!mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL,
							true))
					{
						double tmp = height;

						height = width;
						width = tmp;
					}

					if (gridEnabled)
					{
						width = snap(width + gridSize / 2);
						height = snap(height + gridSize / 2);
					}

					result = new mxRectangle(0, 0, width, height);
				}
				else
				{
					double gs2 = 4 * gridSize;
					result = new mxRectangle(0, 0, gs2, gs2);
				}
			}
		}

		return result;
	}

	/**
	 * Sets the bounds of the given cell using resizeCells. Returns the
	 * cell which was passed to the function.
	 * 
	 * @param cell <mxCell> whose bounds should be changed.
	 * @param bounds <mxRectangle> that represents the new bounds.
	 */
	public Object resizeCell(Object cell, mxRectangle bounds)
	{
		return resizeCells(new Object[] { cell }, new mxRectangle[] { bounds })[0];
	}

	/**
	 * Sets the bounds of the given cells and fires a mxEvent.RESIZE_CELLS
	 * event. while the transaction is in progress. Returns the cells which
	 * have been passed to the function.
	 * 
	 * @param cells Array of cells whose bounds should be changed.
	 * @param bounds Array of rectangles that represents the new bounds.
	 */
	public Object[] resizeCells(Object[] cells, mxRectangle[] bounds)
	{
		model.beginUpdate();
		try
		{
			cellsResized(cells, bounds);
			fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS, "cells", cells,
					"bounds", bounds));
		}
		finally
		{
			model.endUpdate();
		}

		return cells;
	}

	/**
	 * Sets the bounds of the given cells and fires a <mxEvent.CELLS_RESIZED>
	 * event. If extendParents is true, then the parent is extended if a child
	 * size is changed so that it overlaps with the parent.
	 * 
	 * @param cells Array of <mxCells> whose bounds should be changed.
	 * @param bounds Array of <mxRectangles> that represents the new bounds.
	 */
	public void cellsResized(Object[] cells, mxRectangle[] bounds)
	{
		if (cells != null && bounds != null && cells.length == bounds.length)
		{
			model.beginUpdate();
			try
			{
				for (int i = 0; i < cells.length; i++)
				{
					mxRectangle tmp = bounds[i];
					mxGeometry geo = model.getGeometry(cells[i]);

					if (geo != null
							&& (geo.getX() != tmp.getX()
									|| geo.getY() != tmp.getY()
									|| geo.getWidth() != tmp.getWidth() || geo
									.getHeight() != tmp.getHeight()))
					{
						geo = (mxGeometry) geo.clone();

						if (geo.isRelative())
						{
							mxPoint offset = geo.getOffset();

							if (offset != null)
							{
								offset.setX(offset.getX() + tmp.getX()
										- geo.getX());
								offset.setY(offset.getY() + tmp.getY()
										- geo.getY());
							}
						}
						else
						{
							geo.setX(tmp.getX());
							geo.setY(tmp.getY());
						}

						geo.setWidth(tmp.getWidth());
						geo.setHeight(tmp.getHeight());

						if (!geo.isRelative() && model.isVertex(cells[i])
								&& !isAllowNegativeCoordinates())
						{
							geo.setX(Math.max(0, geo.getX()));
							geo.setY(Math.max(0, geo.getY()));
						}

						model.setGeometry(cells[i], geo);

						if (isExtendParent(cells[i]))
						{
							extendParent(cells[i]);
						}
					}
				}

				if (isResetEdgesOnResize())
				{
					resetEdges(cells);
				}

				// RENAME BOUNDSARRAY TO BOUNDS
				fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED, "cells",
						cells, "bounds", bounds));
			}
			finally
			{
				model.endUpdate();
			}
		}
	}

	/**
	 * Resizes the parents recursively so that they contain the complete area
	 * of the resized child cell.
	 * 
	 * @param cell <mxCell> that has been resized.
	 */
	public void extendParent(Object cell)
	{
		if (cell != null)
		{
			Object parent = model.getParent(cell);
			mxGeometry p = model.getGeometry(parent);

			if (parent != null && p != null && !isCellCollapsed(parent))
			{
				mxGeometry geo = model.getGeometry(cell);

				if (geo != null
						&& (p.getWidth() < geo.getX() + geo.getWidth() || p
								.getHeight() < geo.getY() + geo.getHeight()))
				{
					p = (mxGeometry) p.clone();

					p.setWidth(Math.max(p.getWidth(), geo.getX()
							+ geo.getWidth()));
					p.setHeight(Math.max(p.getHeight(), geo.getY()
							+ geo.getHeight()));

					cellsResized(new Object[] { parent },
							new mxRectangle[] { p });
				}
			}
		}
	}

	//
	// Cell moving
	//

	/**
	 * Moves the cells by the given amount. This is a shortcut method.
	 */
	public Object[] moveCells(Object[] cells, double dx, double dy)
	{
		return moveCells(cells, dx, dy, false);
	}

	/**
	 * Moves or clones the cells and moves the cells or clones by the given
	 * amount. This is a shortcut method.
	 */
	public Object[] moveCells(Object[] cells, double dx, double dy,
			boolean clone)
	{
		return moveCells(cells, dx, dy, clone, null, null);
	}

	/**
	 * Moves or clones the specified cells and moves the cells or clones by the
	 * given amount, adding them to the optional target cell. The location is
	 * the position of the mouse pointer as the mouse was released. The change
	 * is carried out using cellsMoved. This method fires mxEvent.MOVE_CELLS
	 * while the transaction is in progress.
	 * 
	 * @param cells Array of cells to be moved, cloned or added to the target.
	 * @param dx Integer that specifies the x-coordinate of the vector.
	 * @param dy Integer that specifies the y-coordinate of the vector.
	 * @param clone Boolean indicating if the cells should be cloned.
	 * @param target Cell that represents the new parent of the cells.
	 * @param location Location where the mouse was released.
	 * @return Returns the cells that were moved.
	 */
	public Object[] moveCells(Object[] cells, double dx, double dy,
			boolean clone, Object target, Point location)
	{
		if (cells != null && (dx != 0 || dy != 0 || clone || target != null))
		{
			model.beginUpdate();
			try
			{
				if (clone)
				{
					cells = cloneCells(cells, isCloneInvalidEdges());

					if (target == null)
					{
						target = getDefaultParent();
					}
				}

				cellsMoved(cells, dx, dy, !clone && isDisconnectOnMove()
						&& isAllowDanglingEdges(), target == null);

				if (target != null)
				{
					Integer index = model.getChildCount(target);
					cellsAdded(cells, target, index, null, null, true);
				}

				fireEvent(new mxEventObject(mxEvent.MOVE_CELLS, "cells", cells,
						"dx", dx, "dy", dy, "clone", clone, "target", target,
						"location", location));
			}
			finally
			{
				model.endUpdate();
			}
		}

		return cells;
	}

	/**
	 * Moves the specified cells by the given vector, disconnecting the cells
	 * using disconnectGraph if disconnect is true. This method fires
	 * mxEvent.CELLS_MOVED while the transaction is in progress.
	 */
	public void cellsMoved(Object[] cells, double dx, double dy,
			boolean disconnect, boolean constrain)
	{
		if (cells != null && (dx != 0 || dy != 0))
		{
			model.beginUpdate();
			try
			{
				if (disconnect)
				{
					disconnectGraph(cells);
				}

				for (int i = 0; i < cells.length; i++)
				{
					translateCell(cells[i], dx, dy);

					if (constrain)
					{
						constrainChild(cells[i]);
					}
				}

				if (isResetEdgesOnMove())
				{
					resetEdges(cells);
				}

				fireEvent(new mxEventObject(mxEvent.CELLS_MOVED, "cells",
						cells, "dx", dx, "dy", dy, "disconnect", disconnect));
			}
			finally
			{
				model.endUpdate();
			}
		}
	}

	/**
	 * Translates the geometry of the given cell and stores the new,
	 * translated geometry in the model as an atomic change.
	 */
	public void translateCell(Object cell, double dx, double dy)
	{
		mxGeometry geo = model.getGeometry(cell);

		if (geo != null)
		{
			geo = (mxGeometry) geo.clone();
			geo.translate(dx, dy);

			if (!geo.isRelative() && model.isVertex(cell)
					&& !isAllowNegativeCoordinates())
			{
				geo.setX(Math.max(0, geo.getX()));
				geo.setY(Math.max(0, geo.getY()));
			}

			if (geo.isRelative() && !model.isEdge(cell))
			{
				if (geo.getOffset() == null)
				{
					geo.setOffset(new mxPoint(dx, dy));
				}
				else
				{
					mxPoint offset = geo.getOffset();

					offset.setX(offset.getX() + dx);
					offset.setY(offset.getY() + dy);
				}
			}

			model.setGeometry(cell, geo);
		}
	}

	/**
	 * Returns the mxRectangle inside which a cell is to be kept.
	 */
	public mxRectangle getCellContainmentArea(Object cell)
	{
		if (cell != null && !model.isEdge(cell))
		{
			Object parent = model.getParent(cell);

			if (parent == getDefaultParent() || parent == getCurrentRoot())
			{
				return getMaximumGraphBounds();
			}
			else if (parent != null && parent != getDefaultParent())
			{
				mxGeometry g = model.getGeometry(parent);

				if (g != null)
				{
					double x = 0;
					double y = 0;
					double w = g.getWidth();
					double h = g.getHeight();

					if (isSwimlane(parent))
					{
						mxRectangle size = getStartSize(parent);

						x = size.getWidth();
						w -= size.getWidth();
						y = size.getHeight();
						h -= size.getHeight();
					}

					return new mxRectangle(x, y, w, h);
				}
			}
		}

		return null;
	}

	/**
	 * @return the maximumGraphBounds
	 */
	public mxRectangle getMaximumGraphBounds()
	{
		return maximumGraphBounds;
	}

	/**
	 * @param value the maximumGraphBounds to set
	 */
	public void setMaximumGraphBounds(mxRectangle value)
	{
		mxRectangle oldValue = maximumGraphBounds;
		maximumGraphBounds = value;

		changeSupport.firePropertyChange("maximumGraphBounds", oldValue,
				maximumGraphBounds);
	}

	/**
	 * Keeps the given cell inside the bounds returned by
	 * getCellContainmentArea for its parent, according to the rules defined by
	 * getOverlap and isConstrainChild. This modifies the cell's geometry
	 * in-place and does not clone it.
	 * 
	 * @param cell Cell which should be constrained.
	 */
	public void constrainChild(Object cell)
	{
		if (cell != null)
		{
			mxGeometry geo = model.getGeometry(cell);
			mxRectangle area = (isConstrainChild(cell)) ? getCellContainmentArea(cell)
					: getMaximumGraphBounds();

			if (geo != null && area != null)
			{
				// Keeps child within the content area of the parent
				if (!geo.isRelative()
						&& (geo.getX() < area.getX()
								|| geo.getY() < area.getY()
								|| area.getWidth() < geo.getX()
										+ geo.getWidth() || area.getHeight() < geo
								.getY()
								+ geo.getHeight()))
				{
					double overlap = getOverlap(cell);

					if (area.getWidth() > 0)
					{
						geo.setX(Math.min(geo.getX(), area.getX()
								+ area.getWidth() - (1 - overlap)
								* geo.getWidth()));
					}

					if (area.getHeight() > 0)
					{
						geo.setY(Math.min(geo.getY(), area.getY()
								+ area.getHeight() - (1 - overlap)
								* geo.getHeight()));
					}

					geo.setX(Math.max(geo.getX(), area.getX() - geo.getWidth()
							* overlap));
					geo.setY(Math.max(geo.getY(), area.getY() - geo.getHeight()
							* overlap));
				}
			}
		}
	}

	/**
	 * Resets the control points of the edges that are connected to the given
	 * cells if not both ends of the edge are in the given cells array.
	 * 
	 * @param cells Array of mxCells for which the connected edges should be
	 * reset.
	 */
	public void resetEdges(Object[] cells)
	{
		if (cells != null)
		{
			// Prepares a hashtable for faster cell lookups
			HashSet<Object> set = new HashSet<Object>(Arrays.asList(cells));

			model.beginUpdate();
			try
			{
				for (int i = 0; i < cells.length; i++)
				{
					Object[] edges = mxGraphModel.getEdges(model, cells[i]);

					if (edges != null)
					{
						for (int j = 0; j < edges.length; j++)
						{
							Object source = view.getVisibleTerminal(edges[j],
									true);
							Object target = view.getVisibleTerminal(edges[j],
									false);

							// Checks if one of the terminals is not in the given array
							if (!set.contains(source) || !set.contains(target))
							{
								resetEdge(edges[j]);
							}
						}
					}

					resetEdges(mxGraphModel.getChildren(model, cells[i]));
				}
			}
			finally
			{
				model.endUpdate();
			}
		}
	}

	/**
	 * Resets the control points of the given edge.
	 */
	public Object resetEdge(Object edge)
	{
		mxGeometry geo = model.getGeometry(edge);

		if (geo != null)
		{
			// Resets the control points
			List<mxPoint> points = geo.getPoints();

			if (points != null && !points.isEmpty())
			{
				geo = (mxGeometry) geo.clone();
				geo.setPoints(null);
				model.setGeometry(edge, geo);
			}
		}

		return edge;
	}

	//
	// Cell connecting and connection constraints
	//

	/**
	 * Returns an array of all constraints for the given terminal.
	 * 
	 * @param terminal Cell state that represents the terminal.
	 * @param source Specifies if the terminal is the source or target.
	 */
	public mxConnectionConstraint[] getAllConnectionConstraints(
			mxCellState terminal, boolean source)
	{
		return null;
	}

	/**
	 * Returns an connection constraint that describes the given connection
	 * point. This result can then be passed to getConnectionPoint.
	 * 
	 * @param edge Cell state that represents the edge.
	 * @param terminal Cell state that represents the terminal.
	 * @param source Boolean indicating if the terminal is the source or target.
	 */
	public mxConnectionConstraint getConnectionConstraint(mxCellState edge,
			mxCellState terminal, boolean source)
	{
		mxPoint point = null;
		Object x = edge.getStyle()
				.get(
						(source) ? mxConstants.STYLE_EXIT_X
								: mxConstants.STYLE_ENTRY_X);

		if (x != null)
		{
			Object y = edge.getStyle().get(
					(source) ? mxConstants.STYLE_EXIT_Y
							: mxConstants.STYLE_ENTRY_Y);

			if (y != null)
			{
				point = new mxPoint(Double.parseDouble(x.toString()), Double
						.parseDouble(y.toString()));
			}
		}

		boolean perimeter = false;

		if (point != null)
		{
			perimeter = mxUtils.isTrue(edge.style,
					(source) ? mxConstants.STYLE_EXIT_PERIMETER
							: mxConstants.STYLE_ENTRY_PERIMETER, true);
		}

		return new mxConnectionConstraint(point, perimeter);
	}

	/**
	 * Sets the connection constraint that describes the given connection point.
	 * If no constraint is given then nothing is changed. To remove an existing
	 * constraint from the given edge, use an empty constraint instead.
	 * 
	 * @param edge Cell that represents the edge.
	 * @param terminal Cell that represents the terminal.
	 * @param source Boolean indicating if the terminal is the source or target.
	 * @param constraint Optional connection constraint to be used for this connection.
	 */
	public void setConnectionConstraint(Object edge, Object terminal,
			boolean source, mxConnectionConstraint constraint)
	{
		if (constraint != null)
		{
			model.beginUpdate();
			try
			{
				Object[] cells = new Object[] { edge };

				if (constraint == null || constraint.point == null)
				{
					setCellStyles((source) ? mxConstants.STYLE_EXIT_X
							: mxConstants.STYLE_ENTRY_X, null, cells);
					setCellStyles((source) ? mxConstants.STYLE_EXIT_Y
							: mxConstants.STYLE_ENTRY_Y, null, cells);
					setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER
							: mxConstants.STYLE_ENTRY_PERIMETER, null, cells);
				}
				else if (constraint.point != null)
				{
					setCellStyles((source) ? mxConstants.STYLE_EXIT_X
							: mxConstants.STYLE_ENTRY_X, String
							.valueOf(constraint.point.getX()), cells);
					setCellStyles((source) ? mxConstants.STYLE_EXIT_Y
							: mxConstants.STYLE_ENTRY_Y, String
							.valueOf(constraint.point.getY()), cells);

					// Only writes 0 since 1 is default
					if (!constraint.perimeter)
					{
						setCellStyles(
								(source) ? mxConstants.STYLE_EXIT_PERIMETER
										: mxConstants.STYLE_ENTRY_PERIMETER,
								"0", cells);
					}
					else
					{
						setCellStyles(
								(source) ? mxConstants.STYLE_EXIT_PERIMETER
										: mxConstants.STYLE_ENTRY_PERIMETER,
								null, cells);
					}
				}
			}
			finally
			{
				model.endUpdate();
			}
		}
	}

	/**
	 * Sets the connection constraint that describes the given connection point.
	 * If no constraint is given then nothing is changed. To remove an existing
	 * constraint from the given edge, use an empty constraint instead.
	 * 
	 * @param vertex Cell state that represents the vertex.
	 * @param constraint Connection constraint that represents the connection point
	 * constraint as returned by getConnectionConstraint.
	 */
	public mxPoint getConnectionPoint(mxCellState vertex,
			mxConnectionConstraint constraint)
	{
		mxPoint point = null;

		if (vertex != null && constraint.point != null)
		{
			point = new mxPoint(vertex.getX() + constraint.getPoint().getX()
					* vertex.getWidth(), vertex.getY()
					+ constraint.getPoint().getY() * vertex.getHeight());
		}

		if (point != null && constraint.perimeter)
		{
			point = view.getPerimeterPoint(vertex, point, false);
		}

		return point;
	}

	/**
	 * Connects the specified end of the given edge to the given terminal
	 * using cellConnected and fires mxEvent.CONNECT_CELL while the transaction
	 * is in progress.
	 */
	public Object connectCell(Object edge, Object terminal, boolean source)
	{
		return connectCell(edge, terminal, source, null);
	}

	/**
	 * Connects the specified end of the given edge to the given terminal
	 * using cellConnected and fires mxEvent.CONNECT_CELL while the transaction
	 * is in progress.
	 * 
	 * @param edge Edge whose terminal should be updated.
	 * @param terminal New terminal to be used.
	 * @param source Specifies if the new terminal is the source or target.
	 * @param constraint Optional constraint to be used for this connection.
	 * @return Returns the update edge.
	 */
	public Object connectCell(Object edge, Object terminal, boolean source,
			mxConnectionConstraint constraint)
	{
		model.beginUpdate();
		try
		{
			Object previous = model.getTerminal(edge, source);
			cellConnected(edge, terminal, source, constraint);
			fireEvent(new mxEventObject(mxEvent.CONNECT_CELL, "edge", edge,
					"terminal", terminal, "source", source, "previous",
					previous));
		}
		finally
		{
			model.endUpdate();
		}

		return edge;
	}

	/**
	 * Sets the new terminal for the given edge and resets the edge points if
	 * isResetEdgesOnConnect returns true. This method fires
	 * <mxEvent.CELL_CONNECTED> while the transaction is in progress.
	 * 
	 * @param edge Edge whose terminal should be updated.
	 * @param terminal New terminal to be used.
	 * @param source Specifies if the new terminal is the source or target.
	 * @param constraint Constraint to be used for this connection.
	 */
	public void cellConnected(Object edge, Object terminal, boolean source,
			mxConnectionConstraint constraint)
	{
		if (edge != null)
		{
			model.beginUpdate();
			try
			{
				Object previous = model.getTerminal(edge, source);

				// Updates the constraint
				setConnectionConstraint(edge, terminal, source, constraint);

				// Checks if the new terminal is a port
				String id = null;

				if (isPort(terminal) && terminal instanceof mxICell)
				{
					id = ((mxICell) terminal).getId();
					terminal = getTerminalForPort(terminal, source);
				}

				// Sets or resets all previous information for connecting to a child port
				String key = (source) ? mxConstants.STYLE_SOURCE_PORT
						: mxConstants.STYLE_TARGET_PORT;
				setCellStyles(key, id, new Object[] { edge });
				model.setTerminal(edge, terminal, source);

				if (isResetEdgesOnConnect())
				{
					resetEdge(edge);
				}

				fireEvent(new mxEventObject(mxEvent.CELL_CONNECTED, "edge",
						edge, "terminal", terminal, "source", source,
						"previous", previous));
			}
			finally
			{
				model.endUpdate();
			}
		}
	}

	/**
	 * Disconnects the given edges from the terminals which are not in the
	 * given array.
	 * 
	 * @param cells Array of <mxCells> to be disconnected.
	 */
	public void disconnectGraph(Object[] cells)
	{
		if (cells != null)
		{
			model.beginUpdate();
			try
			{
				double scale = view.getScale();
				mxPoint tr = view.getTranslate();

				// Prepares a hashtable for faster cell lookups
				Set<Object> hash = new HashSet<Object>();

				for (int i = 0; i < cells.length; i++)
				{
					hash.add(cells[i]);
				}

				for (int i = 0; i < cells.length; i++)
				{
					if (model.isEdge(cells[i]))
					{
						mxGeometry geo = model.getGeometry(cells[i]);

						if (geo != null)
						{
							mxCellState state = view.getState(cells[i]);
							mxCellState pstate = view.getState(model
									.getParent(cells[i]));

							if (state != null && pstate != null)
							{
								geo = (mxGeometry) geo.clone();

								double dx = -pstate.getOrigin().getX();
								double dy = -pstate.getOrigin().getY();

								Object src = model.getTerminal(cells[i], true);

								if (src != null
										&& isCellDisconnectable(cells[i], src,
												true))
								{
									while (src != null && !hash.contains(src))
									{
										src = model.getParent(src);
									}

									if (src == null)
									{
										mxPoint pt = state.getAbsolutePoint(0);
										geo
												.setTerminalPoint(new mxPoint(
														pt.getX() / scale
																- tr.getX()
																+ dx, pt.getY()
																/ scale
																- tr.getY()
																+ dy), true);
										model.setTerminal(cells[i], null, true);
									}
								}

								Object trg = model.getTerminal(cells[i], false);

								if (trg != null
										&& isCellDisconnectable(cells[i], trg,
												false))
								{
									while (trg != null && !hash.contains(trg))
									{
										trg = model.getParent(trg);
									}

									if (trg == null)
									{
										int n = state.getAbsolutePointCount() - 1;
										mxPoint pt = state.getAbsolutePoint(n);
										geo.setTerminalPoint(new mxPoint(pt
												.getX()
												/ scale - tr.getX() + dx, pt
												.getY()
												/ scale - tr.getY() + dy),
												false);
										model
												.setTerminal(cells[i], null,
														false);
									}
								}
							}

							model.setGeometry(cells[i], geo);
						}
					}
				}
			}
			finally
			{
				model.endUpdate();
			}
		}
	}

	//
	// Drilldown
	//

	/**
	 * Returns the current root of the displayed cell hierarchy. This is a
	 * shortcut to <mxGraphView.currentRoot> in <view>.
	 * 
	 * @return Returns the current root in the view.
	 */
	public Object getCurrentRoot()
	{
		return view.getCurrentRoot();
	}

	/**
	 * Returns the translation to be used if the given cell is the root cell as
	 * an <mxPoint>. This implementation returns null.
	 * 
	 * @param cell Cell that represents the root of the view.
	 * @return Returns the translation of the graph for the given root cell.
	 */
	public mxPoint getTranslateForRoot(Object cell)
	{
		return null;
	}

	/**
	 * Returns true if the given cell is a "port", that is, when connecting to
	 * it, the cell returned by getTerminalForPort should be used as the
	 * terminal and the port should be referenced by the ID in either the
	 * mxConstants.STYLE_SOURCE_PORT or the or the
	 * mxConstants.STYLE_TARGET_PORT. Note that a port should not be movable.
	 * This implementation always returns false.
	 * 
	 * A typical implementation of this method looks as follows:
	 * 
	 * <code>
	 * public boolean isPort(Object cell)
	 * {
	 *   mxGeometry geo = getCellGeometry(cell);
	 *   
	 *   return (geo != null) ? geo.isRelative() : false;
	 * }
	 * </code>
	 * 
	 * @param cell Cell that represents the port.
	 * @return Returns true if the cell is a port.
	 */
	public boolean isPort(Object cell)
	{
		return false;
	}

	/**
	 * Returns the terminal to be used for a given port. This implementation
	 * always returns the parent cell.
	 * 
	 * @param cell Cell that represents the port.
	 * @param source If the cell is the source or target port.
	 * @return Returns the terminal to be used for the given port.
	 */
	public Object getTerminalForPort(Object cell, boolean source)
	{
		return getModel().getParent(cell);
	}

	/**
	 * Returns the offset to be used for the cells inside the given cell. The
	 * root and layer cells may be identified using mxGraphModel.isRoot and
	 * mxGraphModel.isLayer. This implementation returns null.
	 *
	 * @param cell Cell whose offset should be returned.
	 * @return Returns the child offset for the given cell.
	 */
	public mxPoint getChildOffsetForCell(Object cell)
	{
		return null;
	}

	/**
	 * 
	 */
	public void enterGroup()
	{
		enterGroup(null);
	}

	/**
	 * Uses the given cell as the root of the displayed cell hierarchy. If no
	 * cell is specified then the selection cell is used. The cell is only used
	 * if <isValidRoot> returns true.
	 * 
	 * @param cell
	 */
	public void enterGroup(Object cell)
	{
		if (cell == null)
		{
			cell = getSelectionCell();
		}

		if (cell != null && isValidRoot(cell))
		{
			view.setCurrentRoot(cell);
			clearSelection();
		}
	}

	/**
	 * Changes the current root to the next valid root in the displayed cell
	 * hierarchy.
	 */
	public void exitGroup()
	{
		Object root = model.getRoot();
		Object current = getCurrentRoot();

		if (current != null)
		{
			Object next = model.getParent(current);

			// Finds the next valid root in the hierarchy
			while (next != root && !isValidRoot(next)
					&& model.getParent(next) != root)
			{
				next = model.getParent(next);
			}

			// Clears the current root if the new root is
			// the model's root or one of the layers.
			if (next == root || model.getParent(next) == root)
			{
				view.setCurrentRoot(null);
			}
			else
			{
				view.setCurrentRoot(next);
			}

			mxCellState state = view.getState(current);

			// Selects the previous root in the graph
			if (state != null)
			{
				setSelectionCell(current);
			}
		}
	}

	/**
	 * Uses the root of the model as the root of the displayed cell hierarchy
	 * and selects the previous root.
	 */
	public void home()
	{
		Object current = getCurrentRoot();

		if (current != null)
		{
			view.setCurrentRoot(null);
			mxCellState state = view.getState(current);

			if (state != null)
			{
				setSelectionCell(current);
			}
		}
	}

	/**
	 * Returns true if the given cell is a valid root for the cell display
	 * hierarchy. This implementation returns true for all non-null values.
	 * 
	 * @param cell <mxCell> which should be checked as a possible root.
	 * @return Returns true if the given cell is a valid root.
	 */
	public boolean isValidRoot(Object cell)
	{
		return (cell != null);
	}

	//
	// Graph display
	//

	/**
	 * Returns the bounds of the visible graph.
	 */
	public mxRectangle getGraphBounds()
	{
		return view.getGraphBounds();
	}

	/**
	 * Returns the bounds of the given cell.
	 */
	public mxRectangle getCellBounds(Object cell)
	{
		return getCellBounds(cell, false);
	}

	/**
	 * Returns the bounds of the given cell including all connected edges
	 * if includeEdge is true.
	 */
	public mxRectangle getCellBounds(Object cell, boolean includeEdges)
	{
		return getCellBounds(cell, includeEdges, false);
	}

	/**
	 * Returns the bounds of the given cell including all connected edges
	 * if includeEdge is true.
	 */
	public mxRectangle getCellBounds(Object cell, boolean includeEdges,
			boolean includeDescendants)
	{
		return getCellBounds(cell, includeEdges, includeDescendants, false);
	}

	/**
	 * Returns the bounding box for the geometries of the vertices in the
	 * given array of cells.
	 */
	public mxRectangle getBoundingBoxFromGeometry(Object[] cells)
	{
		mxRectangle result = null;

		if (cells != null)
		{
			for (int i = 0; i < cells.length; i++)
			{
				if (getModel().isVertex(cells[i]))
				{
					mxGeometry geo = getCellGeometry(cells[i]);

					if (result == null)
					{
						result = new mxRectangle(geo);
					}
					else
					{
						result.add(geo);
					}
				}
			}
		}

		return result;
	}

	/**
	 * Returns the bounds of the given cell.
	 */
	public mxRectangle getBoundingBox(Object cell)
	{
		return getBoundingBox(cell, false);
	}

	/**
	 * Returns the bounding box of the given cell including all connected edges
	 * if includeEdge is true.
	 */
	public mxRectangle getBoundingBox(Object cell, boolean includeEdges)
	{
		return getBoundingBox(cell, includeEdges, false);
	}

	/**
	 * Returns the bounding box of the given cell including all connected edges
	 * if includeEdge is true.
	 */
	public mxRectangle getBoundingBox(Object cell, boolean includeEdges,
			boolean includeDescendants)
	{
		return getCellBounds(cell, includeEdges, includeDescendants, true);
	}

	/**
	 * Returns the bounding box of the given cells and their descendants.
	 */
	public mxRectangle getPaintBounds(Object[] cells)
	{
		return getBoundsForCells(cells, false, true, true);
	}

	/**
	 * Returns the bounds for the given cells.
	 */
	public mxRectangle getBoundsForCells(Object[] cells, boolean includeEdges,
			boolean includeDescendants, boolean boundingBox)
	{
		mxRectangle result = null;

		if (cells != null && cells.length > 0)
		{
			for (int i = 0; i < cells.length; i++)
			{
				mxRectangle tmp = getCellBounds(cells[i], includeEdges,
						includeDescendants, boundingBox);

				if (tmp != null)
				{
					if (result == null)
					{
						result = new mxRectangle(tmp);
					}
					else
					{
						result.add(tmp);
					}
				}
			}
		}

		return result;
	}

	/**
	 * Returns the bounds of the given cell including all connected edges
	 * if includeEdge is true.
	 */
	public mxRectangle getCellBounds(Object cell, boolean includeEdges,
			boolean includeDescendants, boolean boundingBox)
	{
		mxRectangle result = null;
		Object[] cells;

		// Recursively includes connected edges
		if (includeEdges)
		{
			Set<Object> allCells = new HashSet<Object>();
			allCells.add(cell);

			Set<Object> edges = new HashSet<Object>(Arrays
					.asList(getEdges(cell)));

			while (!edges.isEmpty() && !allCells.containsAll(edges))
			{
				allCells.addAll(edges);

				Set<Object> tmp = new HashSet<Object>();
				Iterator<Object> it = edges.iterator();

				while (it.hasNext())
				{
					Object edge = it.next();
					tmp.addAll(Arrays.asList(getEdges(edge)));
				}

				edges = tmp;
			}

			cells = allCells.toArray();
		}
		else
		{
			cells = new Object[] { cell };
		}

		if (boundingBox)
		{
			result = view.getBoundingBox(cells);
		}
		else
		{
			result = view.getBounds(cells);
		}

		// Recursively includes the bounds of the children
		if (includeDescendants)
		{
			int childCount = model.getChildCount(cell);

			for (int i = 0; i < childCount; i++)
			{
				mxRectangle tmp = getCellBounds(model.getChildAt(cell, i),
						includeEdges, true, boundingBox);

				if (result != null)
				{
					result.add(tmp);
				}
				else
				{
					result = tmp;
				}
			}
		}

		return result;
	}

	/**
	 * Clears all cell states or the states for the hierarchy starting at the
	 * given cell and validates the graph.
	 */
	public void refresh()
	{
		view.reload();
		repaint();
	}

	/**
	 * Fires a repaint event.
	 */
	public void repaint()
	{
		repaint(null);
	}

	/**
	 * Fires a repaint event. The optional region is the rectangle that needs
	 * to be repainted.
	 */
	public void repaint(mxRectangle region)
	{
		fireEvent(new mxEventObject(mxEvent.REPAINT, "region", region));
	}

	/**
	 * Snaps the given numeric value to the grid if <gridEnabled> is true.
	 *
	 * @param value Numeric value to be snapped to the grid.
	 * @return Returns the value aligned to the grid.
	 */
	public double snap(double value)
	{
		if (gridEnabled)
		{
			value = Math.round(value / gridSize) * gridSize;
		}

		return value;
	}

	/**
	 * Returns the geometry for the given cell.
	 * 
	 * @param cell Cell whose geometry should be returned.
	 * @return Returns the geometry of the cell.
	 */
	public mxGeometry getCellGeometry(Object cell)
	{
		return model.getGeometry(cell);
	}

	/**
	 * Returns true if the given cell is visible in this graph. This
	 * implementation uses <mxGraphModel.isVisible>. Subclassers can override
	 * this to implement specific visibility for cells in only one graph, that
	 * is, without affecting the visible state of the cell.
	 * 
	 * When using dynamic filter expressions for cell visibility, then the
	 * graph should be revalidated after the filter expression has changed.
	 * 
	 * @param cell Cell whose visible state should be returned.
	 * @return Returns the visible state of the cell.
	 */
	public boolean isCellVisible(Object cell)
	{
		return model.isVisible(cell);
	}

	/**
	 * Returns true if the given cell is collapsed in this graph. This
	 * implementation uses <mxGraphModel.isCollapsed>. Subclassers can override
	 * this to implement specific collapsed states for cells in only one graph,
	 * that is, without affecting the collapsed state of the cell.
	 * 
	 * When using dynamic filter expressions for the collapsed state, then the
	 * graph should be revalidated after the filter expression has changed.
	 * 
	 * @param cell Cell whose collapsed state should be returned.
	 * @return Returns the collapsed state of the cell.
	 */
	public boolean isCellCollapsed(Object cell)
	{
		return model.isCollapsed(cell);
	}

	/**
	 * Returns true if the given cell is connectable in this graph. This
	 * implementation uses <mxGraphModel.isConnectable>. Subclassers can override
	 * this to implement specific connectable states for cells in only one graph,
	 * that is, without affecting the connectable state of the cell in the model.
	 * 
	 * @param cell Cell whose connectable state should be returned.
	 * @return Returns the connectable state of the cell.
	 */
	public boolean isCellConnectable(Object cell)
	{
		return model.isConnectable(cell);
	}

	/**
	 * Returns true if perimeter points should be computed such that the
	 * resulting edge has only horizontal or vertical segments.
	 * 
	 * @param edge Cell state that represents the edge.
	 */
	public boolean isOrthogonal(mxCellState edge)
	{
		if (edge.getStyle().containsKey(mxConstants.STYLE_ORTHOGONAL))
		{
			return mxUtils
					.isTrue(edge.getStyle(), mxConstants.STYLE_ORTHOGONAL);
		}

		mxEdgeStyle.mxEdgeStyleFunction tmp = view.getEdgeStyle(edge, null,
				null, null);

		return tmp == mxEdgeStyle.ElbowConnector
				|| tmp == mxEdgeStyle.SideToSide
				|| tmp == mxEdgeStyle.TopToBottom
				|| tmp == mxEdgeStyle.EntityRelation;
	}

	/**
	 * Returns true if the given cell state is a loop.
	 * 
	 * @param state <mxCellState> that represents a potential loop.
	 * @return Returns true if the given cell is a loop.
	 */
	public boolean isLoop(mxCellState state)
	{
		Object src = view.getVisibleTerminal(state.getCell(), true);
		Object trg = view.getVisibleTerminal(state.getCell(), false);

		return (src != null && src == trg);
	}

	//
	// Cell validation
	//

	/**
	 * 
	 */
	public void setMultiplicities(mxMultiplicity[] value)
	{
		mxMultiplicity[] oldValue = multiplicities;
		multiplicities = value;

		changeSupport.firePropertyChange("multiplicities", oldValue,
				multiplicities);
	}

	/**
	 * 
	 */
	public mxMultiplicity[] getMultiplicities()
	{
		return multiplicities;
	}

	/**
	 * Checks if the return value of getEdgeValidationError for the given
	 * arguments is null.
	 * 
	 * @param edge Cell that represents the edge to validate.
	 * @param source Cell that represents the source terminal.
	 * @param target Cell that represents the target terminal.
	 */
	public boolean isEdgeValid(Object edge, Object source, Object target)
	{
		return getEdgeValidationError(edge, source, target) == null;
	}

	/**
	 * Returns the validation error message to be displayed when inserting or
	 * changing an edges' connectivity. A return value of null means the edge
	 * is valid, a return value of '' means it's not valid, but do not display
	 * an error message. Any other (non-empty) string returned from this method
	 * is displayed as an error message when trying to connect an edge to a
	 * source and target. This implementation uses the multiplicities, as
	 * well as multigraph and allowDanglingEdges to generate validation
	 * errors.
	 * 
	 * @param edge Cell that represents the edge to validate.
	 * @param source Cell that represents the source terminal.
	 * @param target Cell that represents the target terminal.
	 */
	public String getEdgeValidationError(Object edge, Object source,
			Object target)
	{
		if (edge != null && model.getTerminal(edge, true) == null
				&& model.getTerminal(edge, false) == null)
		{
			return null;
		}

		// Checks if we're dealing with a loop
		if (!isAllowLoops() && source == target && source != null)
		{
			return "";
		}

		// Checks if the connection is generally allowed
		if (!isValidConnection(source, target))
		{
			return "";
		}

		if (source != null && target != null)
		{
			StringBuffer error = new StringBuffer();

			// Checks if the cells are already connected
			// and adds an error message if required			
			if (!multigraph)
			{
				Object[] tmp = mxGraphModel.getEdgesBetween(model, source,
						target, true);

				// Checks if the source and target are not connected by another edge
				if (tmp.length > 1 || (tmp.length == 1 && tmp[0] != edge))
				{
					error.append(mxResources.get("alreadyConnected",
							"Already Connected")
							+ "\n");
				}
			}

			// Gets the number of outgoing edges from the source
			// and the number of incoming edges from the target
			// without counting the edge being currently changed.
			int sourceOut = mxGraphModel.getDirectedEdgeCount(model, source,
					true, edge);
			int targetIn = mxGraphModel.getDirectedEdgeCount(model, target,
					false, edge);

			// Checks the change against each multiplicity rule
			if (multiplicities != null)
			{
				for (int i = 0; i < multiplicities.length; i++)
				{
					String err = multiplicities[i].check(this, edge, source,
							target, sourceOut, targetIn);

					if (err != null)
					{
						error.append(err);
					}
				}
			}

			// Validates the source and target terminals independently
			String err = validateEdge(edge, source, target);

			if (err != null)
			{
				error.append(err);
			}

			return (error.length() > 0) ? error.toString() : null;
		}

		return (allowDanglingEdges) ? null : "";
	}

	/**
	 * Hook method for subclassers to return an error message for the given
	 * edge and terminals. This implementation returns null.
	 * 
	 * @param edge Cell that represents the edge to validate.
	 * @param source Cell that represents the source terminal.
	 * @param target Cell that represents the target terminal.
	 */
	public String validateEdge(Object edge, Object source, Object target)
	{
		return null;
	}

	/**
	 * Checks all multiplicities that cannot be enforced while the graph is
	 * being modified, namely, all multiplicities that require a minimum of
	 * 1 edge.
	 * 
	 * @param cell Cell for which the multiplicities should be checked.
	 */
	public String getCellValidationError(Object cell)
	{
		int outCount = mxGraphModel.getDirectedEdgeCount(model, cell, true);
		int inCount = mxGraphModel.getDirectedEdgeCount(model, cell, false);
		StringBuffer error = new StringBuffer();
		Object value = model.getValue(cell);

		for (int i = 0; i < multiplicities.length; i++)
		{
			mxMultiplicity rule = multiplicities[i];
			int max = rule.getMaxValue();

			if (rule.source
					&& mxUtils.isNode(value, rule.type, rule.attr, rule.value)
					&& ((max == 0 && outCount > 0)
							|| (rule.min == 1 && outCount == 0) || (max == 1 && outCount > 1)))
			{
				error.append(rule.countError + '\n');
			}
			else if (!rule.source
					&& mxUtils.isNode(value, rule.type, rule.attr, rule.value)
					&& ((max == 0 && inCount > 0)
							|| (rule.min == 1 && inCount == 0) || (max == 1 && inCount > 1)))
			{
				error.append(rule.countError + '\n');
			}
		}

		return (error.length() > 0) ? error.toString() : null;
	}

	/**
	 * Hook method for subclassers to return an error message for the given
	 * cell and validation context. This implementation returns null.
	 * 
	 * @param cell Cell that represents the cell to validate.
	 * @param context Hashtable that represents the global validation state.
	 */
	public String validateCell(Object cell, Hashtable<Object, Object> context)
	{
		return null;
	}

	//
	// Graph appearance
	//

	/**
	 * @return the labelsVisible
	 */
	public boolean isLabelsVisible()
	{
		return labelsVisible;
	}

	/**
	 * @param value the labelsVisible to set
	 */
	public void setLabelsVisible(boolean value)
	{
		boolean oldValue = labelsVisible;
		labelsVisible = value;

		changeSupport.firePropertyChange("labelsVisible", oldValue,
				labelsVisible);
	}

	/**
	 * @param value the htmlLabels to set
	 */
	public void setHtmlLabels(boolean value)
	{
		boolean oldValue = htmlLabels;
		htmlLabels = value;

		changeSupport.firePropertyChange("htmlLabels", oldValue, htmlLabels);
	}

	/**
	 * 
	 */
	public boolean isHtmlLabels()
	{
		return htmlLabels;
	}

	/**
	 * Returns the textual representation for the given cell.
	 * 
	 * @param cell Cell to be converted to a string.
	 * @return Returns the textual representation of the cell.
	 */
	public String convertValueToString(Object cell)
	{
		Object result = model.getValue(cell);

		return (result != null) ? result.toString() : "";
	}

	/**
	 * Returns a string or DOM node that represents the label for the given
	 * cell. This implementation uses <convertValueToString> if <labelsVisible>
	 * is true. Otherwise it returns an empty string.
	 * 
	 * @param cell <mxCell> whose label should be returned.
	 * @return Returns the label for the given cell.
	 */
	public String getLabel(Object cell)
	{
		String result = "";

		if (cell != null)
		{
			mxCellState state = view.getState(cell);
			Map<String, Object> style = (state != null) ? state.getStyle()
					: getCellStyle(cell);

			if (labelsVisible
					&& !mxUtils.isTrue(style, mxConstants.STYLE_NOLABEL, false))
			{
				result = convertValueToString(cell);
			}
		}

		return result;
	}

	/**
	 * Sets the new label for a cell. If autoSize is true then
	 * <cellSizeUpdated> will be called.
	 * 
	 * @param cell Cell whose label should be changed.
	 * @param value New label to be assigned.
	 * @param autoSize Specifies if cellSizeUpdated should be called.
	 */
	public void cellLabelChanged(Object cell, Object value, boolean autoSize)
	{
		model.beginUpdate();
		try
		{
			getModel().setValue(cell, value);

			if (autoSize)
			{
				cellSizeUpdated(cell, false);
			}
		}
		finally
		{
			model.endUpdate();
		}
	}

	/**
	 * Returns true if the label must be rendered as HTML markup. The default
	 * implementation returns <htmlLabels>.
	 * 
	 * @param cell <mxCell> whose label should be displayed as HTML markup.
	 * @return Returns true if the given cell label is HTML markup.
	 */
	public boolean isHtmlLabel(Object cell)
	{
		return isHtmlLabels();
	}

	/**
	 * Returns the tooltip to be used for the given cell.
	 */
	public String getToolTipForCell(Object cell)
	{
		return convertValueToString(cell);
	}

	/**
	 * Returns the start size of the given swimlane, that is, the width or
	 * height of the part that contains the title, depending on the
	 * horizontal style. The return value is an <mxRectangle> with either
	 * width or height set as appropriate.
	 * 
	 * @param swimlane <mxCell> whose start size should be returned.
	 * @return Returns the startsize for the given swimlane.
	 */
	public mxRectangle getStartSize(Object swimlane)
	{
		mxRectangle result = new mxRectangle();
		mxCellState state = view.getState(swimlane);
		Map<String, Object> style = (state != null) ? state.getStyle()
				: getCellStyle(swimlane);

		if (style != null)
		{
			double size = mxUtils.getDouble(style, mxConstants.STYLE_STARTSIZE,
					mxConstants.DEFAULT_STARTSIZE);

			if (mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL, true))
			{
				result.setHeight(size);
			}
			else
			{
				result.setWidth(size);
			}
		}

		return result;
	}

	/**
	 * Returns the image URL for the given cell state. This implementation
	 * returns the value stored under <mxConstants.STYLE_IMAGE> in the cell
	 * style.
	 * 
	 * @param state
	 * @return Returns the image associated with the given cell state.
	 */
	public String getImage(mxCellState state)
	{
		return (state != null && state.getStyle() != null) ? mxUtils.getString(
				state.getStyle(), mxConstants.STYLE_IMAGE) : null;
	}

	/**
	 * Returns the value of <border>.
	 * 
	 * @return Returns the border.
	 */
	public int getBorder()
	{
		return border;
	}

	/**
	 * Sets the value of <border>.
	 * 
	 * @param value Positive integer that represents the border to be used.
	 */
	public void setBorder(int value)
	{
		border = value;
	}

	/**
	 * Returns the default edge style used for loops.
	 * 
	 * @return Returns the default loop style.
	 */
	public mxEdgeStyle.mxEdgeStyleFunction getDefaultLoopStyle()
	{
		return defaultLoopStyle;
	}

	/**
	 * Sets the default style used for loops.
	 * 
	 * @param value Default style to be used for loops.
	 */
	public void setDefaultLoopStyle(mxEdgeStyle.mxEdgeStyleFunction value)
	{
		mxEdgeStyle.mxEdgeStyleFunction oldValue = defaultLoopStyle;
		defaultLoopStyle = value;

		changeSupport.firePropertyChange("defaultLoopStyle", oldValue,
				defaultLoopStyle);
	}

	/**
	 * Returns true if the given cell is a swimlane. This implementation always
	 * returns false.
	 * 
	 * @param cell Cell that should be checked. 
	 * @return Returns true if the cell is a swimlane.
	 */
	public boolean isSwimlane(Object cell)
	{
		if (cell != null)
		{
			if (model.getParent(cell) != model.getRoot())
			{
				mxCellState state = view.getState(cell);
				Map<String, Object> style = (state != null) ? state.getStyle()
						: getCellStyle(cell);

				if (style != null && !model.isEdge(cell))
				{
					return mxUtils
							.getString(style, mxConstants.STYLE_SHAPE, "")
							.equals(mxConstants.SHAPE_SWIMLANE);
				}
			}
		}

		return false;
	}

	//
	// Cells and labels control options
	//

	/**
	 * Returns true if the given cell may not be moved, sized, bended,
	 * disconnected, edited or selected. This implementation returns true for
	 * all vertices with a relative geometry if cellsLocked is false.
	 * 
	 * @param cell Cell whose locked state should be returned.
	 * @return Returns true if the given cell is locked.
	 */
	public boolean isCellLocked(Object cell)
	{
		mxGeometry geometry = model.getGeometry(cell);

		return isCellsLocked()
				|| (geometry != null && model.isVertex(cell) && geometry
						.isRelative());
	}

	/**
	 * Returns cellsLocked, the default return value for isCellLocked.
	 */
	public boolean isCellsLocked()
	{
		return cellsLocked;
	}

	/**
	 * Sets cellsLocked, the default return value for isCellLocked and fires a
	 * property change event for cellsLocked.
	 */
	public void setCellsLocked(boolean value)
	{
		boolean oldValue = cellsLocked;
		cellsLocked = value;

		changeSupport.firePropertyChange("cellsLocked", oldValue, cellsLocked);
	}

	/**
	 * Returns true if the given cell is movable. This implementation returns editable.
	 * 
	 * @param cell Cell whose editable state should be returned.
	 * @return Returns true if the cell is editable.
	 */
	public boolean isCellEditable(Object cell)
	{
		return isCellsEditable() && !isCellLocked(cell);
	}

	/**
	 * Returns true if editing is allowed in this graph.
	 * 
	 * @return Returns true if the graph is editable.
	 */
	public boolean isCellsEditable()
	{
		return cellsEditable;
	}

	/**
	 * Sets if the graph is editable.
	 */
	public void setCellsEditable(boolean value)
	{
		boolean oldValue = cellsEditable;
		cellsEditable = value;

		changeSupport.firePropertyChange("cellsEditable", oldValue,
				cellsEditable);
	}

	/**
	 * Returns true if the given cell is resizable. This implementation returns
	 * cellsSizable for all cells.
	 * 
	 * @param cell Cell whose resizable state should be returned.
	 * @return Returns true if the cell is sizable.
	 */
	public boolean isCellResizable(Object cell)
	{
		return isCellsResizable() && !isCellLocked(cell);
	}

	/**
	 * Returns true if the given cell is resizable. This implementation return sizable.
	 */
	public boolean isCellsResizable()
	{
		return cellsResizable;
	}

	/**
	 * Sets if the graph is resizable.
	 */
	public void setCellsResizable(boolean value)
	{
		boolean oldValue = cellsResizable;
		cellsResizable = value;

		changeSupport.firePropertyChange("cellsResizable", oldValue,
				cellsResizable);
	}

	/**
	 * Returns the cells which are movable in the given array of cells.
	 */
	public Object[] getMovableCells(Object[] cells)
	{
		return mxGraphModel.filterCells(cells, new Filter()
		{
			public boolean filter(Object cell)
			{
				return isCellMovable(cell);
			}
		});
	}

	/**
	 * Returns true if the given cell is movable. This implementation
	 * returns movable.
	 * 
	 * @param cell Cell whose movable state should be returned.
	 * @return Returns true if the cell is movable.
	 */
	public boolean isCellMovable(Object cell)
	{
		return isCellsMovable() && !isCellLocked(cell);
	}

	/**
	 * Returns cellsMovable.
	 */
	public boolean isCellsMovable()
	{
		return cellsMovable;
	}

	/**
	 * Sets cellsMovable.
	 */
	public void setCellsMovable(boolean value)
	{
		boolean oldValue = cellsMovable;
		cellsMovable = value;

		changeSupport
				.firePropertyChange("cellsMovable", oldValue, cellsMovable);
	}

	/**
	 * Returns true if the given cell is bendable. This implementation returns
	 * bendable. This is used in mxElbowEdgeHandler to determine if the middle
	 * handle should be shown.
	 * 
	 * @param cell Cell whose bendable state should be returned.
	 * @return Returns true if the cell is bendable.
	 */
	public boolean isCellBendable(Object cell)
	{
		return isCellsBendable() && !isCellLocked(cell);
	}

	/**
	 * Returns cellsBendable.
	 */
	public boolean isCellsBendable()
	{
		return cellsBendable;
	}

	/**
	 * Sets cellsBendable.
	 */
	public void setCellsBendable(boolean value)
	{
		boolean oldValue = cellsBendable;
		cellsBendable = value;

		changeSupport.firePropertyChange("cellsBendable", oldValue,
				cellsBendable);
	}

	/**
	 * Returns true if the given cell is selectable. This implementation returns
	 * <selectable>.
	 * 
	 * @param cell <mxCell> whose selectable state should be returned.
	 * @return Returns true if the given cell is selectable.
	 */
	public boolean isCellSelectable(Object cell)
	{
		return isCellsSelectable();
	}

	/**
	 * Returns cellsSelectable.
	 */
	public boolean isCellsSelectable()
	{
		return cellsSelectable;
	}

	/**
	 * Sets cellsSelectable.
	 */
	public void setCellsSelectable(boolean value)
	{
		boolean oldValue = cellsSelectable;
		cellsSelectable = value;

		changeSupport.firePropertyChange("cellsSelectable", oldValue,
				cellsSelectable);
	}

	/**
	 * Returns the cells which are movable in the given array of cells.
	 */
	public Object[] getDeletableCells(Object[] cells)
	{
		return mxGraphModel.filterCells(cells, new Filter()
		{
			public boolean filter(Object cell)
			{
				return isCellDeletable(cell);
			}
		});
	}

	/**
	 * Returns true if the given cell is movable. This implementation always
	 * returns true.
	 * 
	 * @param cell Cell whose movable state should be returned.
	 * @return Returns true if the cell is movable.
	 */
	public boolean isCellDeletable(Object cell)
	{
		return isCellsDeletable();
	}

	/**
	 * Returns cellsDeletable.
	 */
	public boolean isCellsDeletable()
	{
		return cellsDeletable;
	}

	/**
	 * Sets cellsDeletable.
	 */
	public void setCellsDeletable(boolean value)
	{
		boolean oldValue = cellsDeletable;
		cellsDeletable = value;

		changeSupport.firePropertyChange("cellsDeletable", oldValue,
				cellsDeletable);
	}

	/**
	 * Returns the cells which are movable in the given array of cells.
	 */
	public Object[] getCloneableCells(Object[] cells)
	{
		return mxGraphModel.filterCells(cells, new Filter()
		{
			public boolean filter(Object cell)
			{
				return isCellCloneable(cell);
			}
		});
	}

	/**
	 * Returns the constant true. This does not use the cloneable field to
	 * return a value for a given cell, it is simply a hook for subclassers
	 * to disallow cloning of individual cells.
	 */
	public boolean isCellCloneable(Object cell)
	{
		return isCellsCloneable();
	}

	/**
	 * Returns cellsCloneable.
	 */
	public boolean isCellsCloneable()
	{
		return cellsCloneable;
	}

	/**
	 * Specifies if the graph should allow cloning of cells by holding down the
	 * control key while cells are being moved. This implementation updates
	 * cellsCloneable.
	 *
	 * @param value Boolean indicating if the graph should be cloneable.
	 */
	public void setCellsCloneable(boolean value)
	{
		boolean oldValue = cellsCloneable;
		cellsCloneable = value;

		changeSupport.firePropertyChange("cellsCloneable", oldValue,
				cellsCloneable);
	}

	/**
	 * Returns true if the given cell is disconnectable from the source or
	 * target terminal. This returns <disconnectable> for all given cells if
	 * <isLocked> does not return true for the given cell.
	 * 
	 * @param cell <mxCell> whose disconnectable state should be returned.
	 * @param terminal <mxCell> that represents the source or target terminal.
	 * @param source Boolean indicating if the source or target terminal is to be
	 * disconnected.
	 * @return Returns true if the given edge can be disconnected from the given
	 * terminal.
	 */
	public boolean isCellDisconnectable(Object cell, Object terminal,
			boolean source)
	{
		return isCellsDisconnectable() && !isCellLocked(cell);
	}

	/**
	 * Returns cellsDisconnectable.
	 */
	public boolean isCellsDisconnectable()
	{
		return cellsDisconnectable;
	}

	/**
	 * Sets cellsDisconnectable.
	 * 
	 * @param value Boolean indicating if the graph should allow disconnecting of
	 * edges.
	 */
	public void setCellsDisconnectable(boolean value)
	{
		boolean oldValue = cellsDisconnectable;
		cellsDisconnectable = value;

		changeSupport.firePropertyChange("cellsDisconnectable", oldValue,
				cellsDisconnectable);
	}

	/**
	 * Returns true if the overflow portion of labels should be hidden. If this
	 * returns true then vertex labels will be clipped to the size of the vertices.
	 * This implementation returns true if <mxConstants.STYLE_OVERFLOW> in the
	 * style of the given cell is "hidden".
	 * 
	 * @param cell Cell whose label should be clipped.
	 * @return Returns true if the cell label should be clipped.
	 */
	public boolean isLabelClipped(Object cell)
	{
		if (!isLabelsClipped())
		{
			mxCellState state = view.getState(cell);
			Map<String, Object> style = (state != null) ? state.getStyle()
					: getCellStyle(cell);

			return (style != null) ? mxUtils.getString(style,
					mxConstants.STYLE_OVERFLOW, "").equals("hidden") : false;
		}

		return isLabelsClipped();
	}

	/**
	 * Returns labelsClipped.
	 */
	public boolean isLabelsClipped()
	{
		return labelsClipped;
	}

	/**
	 * Sets labelsClipped.
	 */
	public void setLabelsClipped(boolean value)
	{
		boolean oldValue = labelsClipped;
		labelsClipped = value;

		changeSupport.firePropertyChange("labelsClipped", oldValue,
				labelsClipped);
	}

	/**
	 * Returns true if the given edges's label is moveable. This returns
	 * <movable> for all given cells if <isLocked> does not return true
	 * for the given cell.
	 * 
	 * @param cell <mxCell> whose label should be moved.
	 * @return Returns true if the label of the given cell is movable.
	 */
	public boolean isLabelMovable(Object cell)
	{
		return !isCellLocked(cell)
				&& ((model.isEdge(cell) && isEdgeLabelsMovable()) || (model
						.isVertex(cell) && isVertexLabelsMovable()));
	}

	/**
	 * Returns vertexLabelsMovable.
	 */
	public boolean isVertexLabelsMovable()
	{
		return vertexLabelsMovable;
	}

	/**
	 * Sets vertexLabelsMovable.
	 */
	public void setVertexLabelsMovable(boolean value)
	{
		boolean oldValue = vertexLabelsMovable;
		vertexLabelsMovable = value;

		changeSupport.firePropertyChange("vertexLabelsMovable", oldValue,
				vertexLabelsMovable);
	}

	/**
	 * Returns edgeLabelsMovable.
	 */
	public boolean isEdgeLabelsMovable()
	{
		return edgeLabelsMovable;
	}

	/**
	 * Returns edgeLabelsMovable.
	 */
	public void setEdgeLabelsMovable(boolean value)
	{
		boolean oldValue = edgeLabelsMovable;
		edgeLabelsMovable = value;

		changeSupport.firePropertyChange("edgeLabelsMovable", oldValue,
				edgeLabelsMovable);
	}

	//
	// Graph control options
	//

	/**
	 * Returns true if the graph is <enabled>.
	 * 
	 * @return Returns true if the graph is enabled.
	 */
	public boolean isEnabled()
	{
		return enabled;
	}

	/**
	 * Specifies if the graph should allow any interactions. This
	 * implementation updates <enabled>.
	 * 
	 * @param value Boolean indicating if the graph should be enabled.
	 */
	public void setEnabled(boolean value)
	{
		boolean oldValue = enabled;
		enabled = value;

		changeSupport.firePropertyChange("enabled", oldValue, enabled);
	}

	/**
	 * Returns true if the graph allows drop into other cells.
	 */
	public boolean isDropEnabled()
	{
		return dropEnabled;
	}

	/**
	 * Sets dropEnabled.
	 */
	public void setDropEnabled(boolean value)
	{
		boolean oldValue = dropEnabled;
		dropEnabled = value;

		changeSupport.firePropertyChange("dropEnabled", oldValue, dropEnabled);
	}

	/**
	 * Affects the return values of isValidDropTarget to allow for edges as
	 * drop targets. The splitEdge method is called in mxGraphHandler if
	 * mxGraphComponent.isSplitEvent returns true for a given configuration.
	 */
	public boolean isSplitEnabled()
	{
		return splitEnabled;
	}

	/**
	 * Sets splitEnabled.
	 */
	public void setSplitEnabled(boolean value)
	{
		splitEnabled = value;
	}

	/**
	 * Returns multigraph.
	 */
	public boolean isMultigraph()
	{
		return multigraph;
	}

	/**
	 * Sets multigraph.
	 */
	public void setMultigraph(boolean value)
	{
		boolean oldValue = multigraph;
		multigraph = value;

		changeSupport.firePropertyChange("multigraph", oldValue, multigraph);
	}

	/**
	 * Returns swimlaneNesting.
	 */
	public boolean isSwimlaneNesting()
	{
		return swimlaneNesting;
	}

	/**
	 * Sets swimlaneNesting.
	 */
	public void setSwimlaneNesting(boolean value)
	{
		boolean oldValue = swimlaneNesting;
		swimlaneNesting = value;

		changeSupport.firePropertyChange("swimlaneNesting", oldValue,
				swimlaneNesting);
	}

	/**
	 * Returns allowDanglingEdges
	 */
	public boolean isAllowDanglingEdges()
	{
		return allowDanglingEdges;
	}

	/**
	 * Sets allowDanglingEdges.
	 */
	public void setAllowDanglingEdges(boolean value)
	{
		boolean oldValue = allowDanglingEdges;
		allowDanglingEdges = value;

		changeSupport.firePropertyChange("allowDanglingEdges", oldValue,
				allowDanglingEdges);
	}

	/**
	 * Returns cloneInvalidEdges.
	 */
	public boolean isCloneInvalidEdges()
	{
		return cloneInvalidEdges;
	}

	/**
	 * Sets cloneInvalidEdge.
	 */
	public void setCloneInvalidEdges(boolean value)
	{
		boolean oldValue = cloneInvalidEdges;
		cloneInvalidEdges = value;

		changeSupport.firePropertyChange("cloneInvalidEdges", oldValue,
				cloneInvalidEdges);
	}

	/**
	 * Returns disconnectOnMove
	 */
	public boolean isDisconnectOnMove()
	{
		return disconnectOnMove;
	}

	/**
	 * Sets disconnectOnMove.
	 */
	public void setDisconnectOnMove(boolean value)
	{
		boolean oldValue = disconnectOnMove;
		disconnectOnMove = value;

		changeSupport.firePropertyChange("disconnectOnMove", oldValue,
				disconnectOnMove);

	}

	/**
	 * Returns allowLoops.
	 */
	public boolean isAllowLoops()
	{
		return allowLoops;
	}

	/**
	 * Sets allowLoops.
	 */
	public void setAllowLoops(boolean value)
	{
		boolean oldValue = allowLoops;
		allowLoops = value;

		changeSupport.firePropertyChange("allowLoops", oldValue, allowLoops);
	}

	/**
	 * Returns connectableEdges.
	 */
	public boolean isConnectableEdges()
	{
		return connectableEdges;
	}

	/**
	 * Sets connetableEdges.
	 */
	public void setConnectableEdges(boolean value)
	{
		boolean oldValue = connectableEdges;
		connectableEdges = value;

		changeSupport.firePropertyChange("connectableEdges", oldValue,
				connectableEdges);

	}

	/**
	 * Returns resetEdgesOnMove.
	 */
	public boolean isResetEdgesOnMove()
	{
		return resetEdgesOnMove;
	}

	/**
	 * Sets resetEdgesOnMove.
	 */
	public void setResetEdgesOnMove(boolean value)
	{
		boolean oldValue = resetEdgesOnMove;
		resetEdgesOnMove = value;

		changeSupport.firePropertyChange("resetEdgesOnMove", oldValue,
				resetEdgesOnMove);
	}

	/**
	 * Returns resetViewOnRootChange.
	 */
	public boolean isResetViewOnRootChange()
	{
		return resetViewOnRootChange;
	}

	/**
	 * Sets resetEdgesOnResize.
	 */
	public void setResetViewOnRootChange(boolean value)
	{
		boolean oldValue = resetViewOnRootChange;
		resetViewOnRootChange = value;

		changeSupport.firePropertyChange("resetViewOnRootChange", oldValue,
				resetViewOnRootChange);
	}

	/**
	 * Returns resetEdgesOnResize.
	 */
	public boolean isResetEdgesOnResize()
	{
		return resetEdgesOnResize;
	}

	/**
	 * Sets resetEdgesOnResize.
	 */
	public void setResetEdgesOnResize(boolean value)
	{
		boolean oldValue = resetEdgesOnResize;
		resetEdgesOnResize = value;

		changeSupport.firePropertyChange("resetEdgesOnResize", oldValue,
				resetEdgesOnResize);
	}

	/**
	 * Returns resetEdgesOnConnect.
	 */
	public boolean isResetEdgesOnConnect()
	{
		return resetEdgesOnConnect;
	}

	/**
	 * Sets resetEdgesOnConnect.
	 */
	public void setResetEdgesOnConnect(boolean value)
	{
		boolean oldValue = resetEdgesOnConnect;
		resetEdgesOnConnect = value;

		changeSupport.firePropertyChange("resetEdgesOnConnect", oldValue,
				resetEdgesOnResize);
	}

	/**
	 * Returns true if the size of the given cell should automatically be
	 * updated after a change of the label. This implementation returns
	 * autoSize for all given cells.
	 * 
	 * @param cell Cell that should be resized.
	 * @return Returns true if the size of the given cell should be updated.
	 */
	public boolean isAutoSizeCell(Object cell)
	{
		return isAutoSizeCells();
	}

	/**
	 * Returns true if the size of the given cell should automatically be
	 * updated after a change of the label. This implementation returns
	 * autoSize for all given cells.
	 */
	public boolean isAutoSizeCells()
	{
		return autoSizeCells;
	}

	/**
	 * Specifies if cell sizes should be automatically updated after a label
	 * change. This implementation sets autoSize to the given parameter.
	 * 
	 * @param value Boolean indicating if cells should be resized
	 * automatically.
	 */
	public void setAutoSizeCells(boolean value)
	{
		boolean oldValue = autoSizeCells;
		autoSizeCells = value;

		changeSupport.firePropertyChange("autoSizeCells", oldValue,
				autoSizeCells);
	}

	/**
	 * Returns true if the parent of the given cell should be extended if the
	 * child has been resized so that it overlaps the parent. This
		 * implementation returns ExtendParents if cell is not an edge.
	 * 
	 * @param cell Cell that has been resized.
	 */
	public boolean isExtendParent(Object cell)
	{
		return !getModel().isEdge(cell) && isExtendParents();
	}

	/**
	 * Returns extendParents.
	 */
	public boolean isExtendParents()
	{
		return extendParents;
	}

	/**
	 * Sets extendParents.
	 */
	public void setExtendParents(boolean value)
	{
		boolean oldValue = extendParents;
		extendParents = value;

		changeSupport.firePropertyChange("extendParents", oldValue,
				extendParents);
	}

	/**
	 * Returns extendParentsOnAdd.
	 */
	public boolean isExtendParentsOnAdd()
	{
		return extendParentsOnAdd;
	}

	/**
	 * Sets extendParentsOnAdd.
	 */
	public void setExtendParentsOnAdd(boolean value)
	{
		boolean oldValue = extendParentsOnAdd;
		extendParentsOnAdd = value;

		changeSupport.firePropertyChange("extendParentsOnAdd", oldValue,
				extendParentsOnAdd);
	}

	/**
	 * Returns true if the given cell should be kept inside the bounds of its
	 * parent according to the rules defined by getOverlap and
	 * isAllowOverlapParent. This implementation returns
	 * isConstrainChildren() for all given cells.
	 */
	public boolean isConstrainChild(Object cell)
	{
		return isConstrainChildren();
	}

	/**
	 * Returns constrainChildren.
	 * 
	 * @return the keepInsideParentOnMove
	 */
	public boolean isConstrainChildren()
	{
		return constrainChildren;
	}

	/**
	 * @param value the constrainChildren to set
	 */
	public void setConstrainChildren(boolean value)
	{
		boolean oldValue = constrainChildren;
		constrainChildren = value;

		changeSupport.firePropertyChange("constrainChildren", oldValue,
				constrainChildren);
	}

	/**
	 * Returns autoOrigin.
	 */
	public boolean isAutoOrigin()
	{
		return autoOrigin;
	}

	/**
	 * @param value the autoOrigin to set
	 */
	public void setAutoOrigin(boolean value)
	{
		boolean oldValue = autoOrigin;
		autoOrigin = value;

		changeSupport.firePropertyChange("autoOrigin", oldValue, autoOrigin);
	}

	/**
	 * Returns origin.
	 */
	public mxPoint getOrigin()
	{
		return origin;
	}

	/**
	 * @param value the origin to set
	 */
	public void setOrigin(mxPoint value)
	{
		mxPoint oldValue = origin;
		origin = value;

		changeSupport.firePropertyChange("origin", oldValue, origin);
	}

	/**
	 * @return Returns changesRepaintThreshold.
	 */
	public int getChangesRepaintThreshold()
	{
		return changesRepaintThreshold;
	}

	/**
	 * @param value the changesRepaintThreshold to set
	 */
	public void setChangesRepaintThreshold(int value)
	{
		int oldValue = changesRepaintThreshold;
		changesRepaintThreshold = value;

		changeSupport.firePropertyChange("changesRepaintThreshold", oldValue,
				changesRepaintThreshold);
	}

	/**
	 * Returns isAllowNegativeCoordinates.
	 * 
	 * @return the allowNegativeCoordinates
	 */
	public boolean isAllowNegativeCoordinates()
	{
		return allowNegativeCoordinates;
	}

	/**
	 * @param value the allowNegativeCoordinates to set
	 */
	public void setAllowNegativeCoordinates(boolean value)
	{
		boolean oldValue = allowNegativeCoordinates;
		allowNegativeCoordinates = value;

		changeSupport.firePropertyChange("allowNegativeCoordinates", oldValue,
				allowNegativeCoordinates);
	}

	/**
	 * Returns collapseToPreferredSize.
	 * 
	 * @return the collapseToPreferredSize
	 */
	public boolean isCollapseToPreferredSize()
	{
		return collapseToPreferredSize;
	}

	/**
	 * @param value the collapseToPreferredSize to set
	 */
	public void setCollapseToPreferredSize(boolean value)
	{
		boolean oldValue = collapseToPreferredSize;
		collapseToPreferredSize = value;

		changeSupport.firePropertyChange("collapseToPreferredSize", oldValue,
				collapseToPreferredSize);
	}

	/**
	 * @return Returns true if edges are rendered in the foreground.
	 */
	public boolean isKeepEdgesInForeground()
	{
		return keepEdgesInForeground;
	}

	/**
	 * @param value the keepEdgesInForeground to set
	 */
	public void setKeepEdgesInForeground(boolean value)
	{
		boolean oldValue = keepEdgesInForeground;
		keepEdgesInForeground = value;

		changeSupport.firePropertyChange("keepEdgesInForeground", oldValue,
				keepEdgesInForeground);
	}

	/**
	 * @return Returns true if edges are rendered in the background.
	 */
	public boolean isKeepEdgesInBackground()
	{
		return keepEdgesInBackground;
	}

	/**
	 * @param value the keepEdgesInBackground to set
	 */
	public void setKeepEdgesInBackground(boolean value)
	{
		boolean oldValue = keepEdgesInBackground;
		keepEdgesInBackground = value;

		changeSupport.firePropertyChange("keepEdgesInBackground", oldValue,
				keepEdgesInBackground);
	}

	/**
	 * Returns true if the given cell is a valid source for new connections.
	 * This implementation returns true for all non-null values and is
	 * called by is called by <isValidConnection>.
	 * 
	 * @param cell Object that represents a possible source or null.
	 * @return Returns true if the given cell is a valid source terminal.
	 */
	public boolean isValidSource(Object cell)
	{
		return (cell == null && allowDanglingEdges)
				|| (cell != null
						&& (!model.isEdge(cell) || isConnectableEdges()) && isCellConnectable(cell));
	}

	/**
	 * Returns isValidSource for the given cell. This is called by
	 * isValidConnection.
	 *
	 * @param cell Object that represents a possible target or null.
	 * @return Returns true if the given cell is a valid target.
	 */
	public boolean isValidTarget(Object cell)
	{
		return isValidSource(cell);
	}

	/**
	 * Returns true if the given target cell is a valid target for source.
	 * This is a boolean implementation for not allowing connections between
	 * certain pairs of vertices and is called by <getEdgeValidationError>.
	 * This implementation returns true if <isValidSource> returns true for
	 * the source and <isValidTarget> returns true for the target.
	 * 
	 * @param source Object that represents the source cell.
	 * @param target Object that represents the target cell.
	 * @return Returns true if the the connection between the given terminals
	 * is valid.
	 */
	public boolean isValidConnection(Object source, Object target)
	{
		return isValidSource(source) && isValidTarget(target)
				&& (isAllowLoops() || source != target);
	}

	/**
	 * Returns the minimum size of the diagram.
	 * 
	 * @return Returns the minimum container size.
	 */
	public mxRectangle getMinimumGraphSize()
	{
		return minimumGraphSize;
	}

	/**
	 * @param value the minimumGraphSize to set
	 */
	public void setMinimumGraphSize(mxRectangle value)
	{
		mxRectangle oldValue = minimumGraphSize;
		minimumGraphSize = value;

		changeSupport.firePropertyChange("minimumGraphSize", oldValue, value);
	}

	/**
	 * Returns a decimal number representing the amount of the width and height
	 * of the given cell that is allowed to overlap its parent. A value of 0
	 * means all children must stay inside the parent, 1 means the child is
	 * allowed to be placed outside of the parent such that it touches one of
	 * the parents sides. If <isAllowOverlapParent> returns false for the given
	 * cell, then this method returns 0.
	 * 
	 * @param cell
	 * @return Returns the overlapping value for the given cell inside its
	 * parent.
	 */
	public double getOverlap(Object cell)
	{
		return (isAllowOverlapParent(cell)) ? getDefaultOverlap() : 0;
	}

	/**
	 * Gets defaultOverlap.
	 */
	public double getDefaultOverlap()
	{
		return defaultOverlap;
	}

	/**
	 * Sets defaultOverlap.
	 */
	public void setDefaultOverlap(double value)
	{
		double oldValue = defaultOverlap;
		defaultOverlap = value;

		changeSupport.firePropertyChange("defaultOverlap", oldValue, value);
	}

	/**
	 * Returns true if the given cell is allowed to be placed outside of the
	 * parents area.
	 * 
	 * @param cell
	 * @return Returns true if the given cell may overlap its parent.
	 */
	public boolean isAllowOverlapParent(Object cell)
	{
		return false;
	}

	/**
	 * Returns the cells which are movable in the given array of cells.
	 */
	public Object[] getFoldableCells(Object[] cells, final boolean collapse)
	{
		return mxGraphModel.filterCells(cells, new Filter()
		{
			public boolean filter(Object cell)
			{
				return isCellFoldable(cell, collapse);
			}
		});
	}

	/**
	 * Returns true if the given cell is expandable. This implementation
	 * returns true if the cell has at least one child.
	 *
	 * @param cell <mxCell> whose expandable state should be returned.
	 * @return Returns true if the given cell is expandable.
	 */
	public boolean isCellFoldable(Object cell, boolean collapse)
	{
		return model.getChildCount(cell) > 0;
	}

	/**
	 * Returns true if the grid is enabled.
	 * 
	 * @return Returns the enabled state of the grid.
	 */
	public boolean isGridEnabled()
	{
		return gridEnabled;
	}

	/**
	 * Sets if the grid is enabled.
	 * 
	 * @param value Specifies if the grid should be enabled.
	 */
	public void setGridEnabled(boolean value)
	{
		boolean oldValue = gridEnabled;
		gridEnabled = value;

		changeSupport.firePropertyChange("gridEnabled", oldValue, gridSize);
	}

	/**
	 * Returns the grid size.
	 * 
	 * @return Returns the grid size
	 */
	public int getGridSize()
	{
		return gridSize;
	}

	/**
	 * Sets the grid size and fires a property change event for gridSize.
	 * 
	 * @param value New grid size to be used.
	 */
	public void setGridSize(int value)
	{
		int oldValue = gridSize;
		gridSize = value;

		changeSupport.firePropertyChange("gridSize", oldValue, gridSize);
	}

	/**
	 * Returns alternateEdgeStyle.
	 */
	public String getAlternateEdgeStyle()
	{
		return alternateEdgeStyle;
	}

	/**
	 * Sets alternateEdgeStyle.
	 */
	public void setAlternateEdgeStyle(String value)
	{
		String oldValue = alternateEdgeStyle;
		alternateEdgeStyle = value;

		changeSupport.firePropertyChange("alternateEdgeStyle", oldValue,
				alternateEdgeStyle);
	}

	/**
	 * Returns true if the given cell is a valid drop target for the specified
	 * cells. This returns true if the cell is a swimlane, has children and is
	 * not collapsed, or if splitEnabled is true and isSplitTarget returns
	 * true for the given arguments
	 * 
	 * @param cell Object that represents the possible drop target.
	 * @param cells Objects that are going to be dropped.
	 * @return Returns true if the cell is a valid drop target for the given
	 * cells.
	 */
	public boolean isValidDropTarget(Object cell, Object[] cells)
	{
		return cell != null
				&& ((isSplitEnabled() && isSplitTarget(cell, cells)) || (!model
						.isEdge(cell) && (isSwimlane(cell) || (model
						.getChildCount(cell) > 0 && !isCellCollapsed(cell)))));
	}

	/**
	 * Returns true if split is enabled and the given edge may be splitted into
	 * two edges with the given cell as a new terminal between the two.
	 * 
	 * @param target Object that represents the edge to be splitted.
	 * @param cells Array of cells to add into the given edge.
	 * @return Returns true if the given edge may be splitted by the given
	 * cell.
	 */
	public boolean isSplitTarget(Object target, Object[] cells)
	{
		if (target != null && cells != null && cells.length == 1)
		{
			Object src = model.getTerminal(target, true);
			Object trg = model.getTerminal(target, false);

			return (model.isEdge(target)
					&& isCellConnectable(cells[0])
					&& getEdgeValidationError(target, model.getTerminal(target,
							true), cells[0]) == null
					&& !model.isAncestor(cells[0], src) && !model.isAncestor(
					cells[0], trg));
		}

		return false;
	}

	/**
	 * Returns the given cell if it is a drop target for the given cells or the
	 * nearest ancestor that may be used as a drop target for the given cells.
	 * If the given array contains a swimlane and swimlaneNesting is false
	 * then this always returns null. If no cell is given, then the bottommost
	 * swimlane at the location of the given event is returned.
	 * 
	 * This function should only be used if isDropEnabled returns true.
	 */
	public Object getDropTarget(Object[] cells, Point pt, Object cell)
	{
		if (!isSwimlaneNesting())
		{
			for (int i = 0; i < cells.length; i++)
			{
				if (isSwimlane(cells[i]))
				{
					return null;
				}
			}
		}

		Object swimlane = null; //getSwimlaneAt(pt.x, pt.y);

		if (cell == null)
		{
			cell = swimlane;
		}
		else if (swimlane != null)
		{
			// Checks if the cell is an ancestor of the swimlane
			// under the mouse and uses the swimlane in that case
			Object tmp = model.getParent(swimlane);

			while (tmp != null && isSwimlane(tmp) && tmp != cell)
			{
				tmp = model.getParent(tmp);
			}

			if (tmp == cell)
			{
				cell = swimlane;
			}
		}

		while (cell != null && !isValidDropTarget(cell, cells)
				&& model.getParent(cell) != model.getRoot())
		{
			cell = model.getParent(cell);
		}

		return (model.getParent(cell) != model.getRoot() && !mxUtils.contains(
				cells, cell)) ? cell : null;
	};

	//
	// Cell retrieval
	//

	/**
	 * Returns the first child of the root in the model, that is, the first or
	 * default layer of the diagram. 
	 * 
	 * @return Returns the default parent for new cells.
	 */
	public Object getDefaultParent()
	{
		Object parent = defaultParent;

		if (parent == null)
		{
			parent = view.getCurrentRoot();

			if (parent == null)
			{
				Object root = model.getRoot();
				parent = model.getChildAt(root, 0);
			}
		}

		return parent;
	}

	/**
	 * Sets the default parent to be returned by getDefaultParent.
	 * Set this to null to return the first child of the root in
	 * getDefaultParent.
	 */
	public void setDefaultParent(Object value)
	{
		defaultParent = value;
	}

	/**
	 * Returns the visible child vertices of the given parent.
	 * 
	 * @param parent Cell whose children should be returned.
	 */
	public Object[] getChildVertices(Object parent)
	{
		return getChildCells(parent, true, false);
	}

	/**
	 * Returns the visible child edges of the given parent.
	 * 
	 * @param parent Cell whose children should be returned.
	 */
	public Object[] getChildEdges(Object parent)
	{
		return getChildCells(parent, false, true);
	}

	/**
	 * Returns the visible children of the given parent.
	 * 
	 * @param parent Cell whose children should be returned.
	 */
	public Object[] getChildCells(Object parent)
	{
		return getChildCells(parent, false, false);
	}

	/**
	 * Returns the visible child vertices or edges in the given parent. If
	 * vertices and edges is false, then all children are returned.
	 * 
	 * @param parent Cell whose children should be returned.
	 * @param vertices Specifies if child vertices should be returned.
	 * @param edges Specifies if child edges should be returned.
	 * @return Returns the child vertices and edges.
	 */
	public Object[] getChildCells(Object parent, boolean vertices, boolean edges)
	{
		Object[] cells = mxGraphModel.getChildCells(model, parent, vertices,
				edges);
		List<Object> result = new ArrayList<Object>(cells.length);

		// Filters out the non-visible child cells
		for (int i = 0; i < cells.length; i++)
		{
			if (isCellVisible(cells[i]))
			{
				result.add(cells[i]);
			}
		}

		return result.toArray();
	}

	/**
	 * Returns all visible edges connected to the given cell without loops.
	 * 
	 * @param cell Cell whose connections should be returned.
	 * @return Returns the connected edges for the given cell.
	 */
	public Object[] getConnections(Object cell)
	{
		return getConnections(cell, null);
	}

	/**
	 * Returns all visible edges connected to the given cell without loops.
	 * If the optional parent argument is specified, then only child
	 * edges of the given parent are returned.
	 * 
	 * @param cell Cell whose connections should be returned.
	 * @param parent Optional parent of the opposite end for a connection
	 * to be returned.
	 * @return Returns the connected edges for the given cell.
	 */
	public Object[] getConnections(Object cell, Object parent)
	{
		return getEdges(cell, parent, true, true, false);
	}

	/**
	 * Returns all incoming visible edges connected to the given cell without
	 * loops.
	 * 
	 * @param cell Cell whose incoming edges should be returned.
	 * @return Returns the incoming edges of the given cell.
	 */
	public Object[] getIncomingEdges(Object cell)
	{
		return getIncomingEdges(cell, null);
	}

	/**
	 * Returns the visible incoming edges for the given cell. If the optional
	 * parent argument is specified, then only child edges of the given parent
	 * are returned.
	 * 
	 * @param cell Cell whose incoming edges should be returned.
	 * @param parent Optional parent of the opposite end for an edge
	 * to be returned.
	 * @return Returns the incoming edges of the given cell.
	 */
	public Object[] getIncomingEdges(Object cell, Object parent)
	{
		return getEdges(cell, parent, true, false, false);
	}

	/**
	 * Returns all outgoing visible edges connected to the given cell without
	 * loops.
	 * 
	 * @param cell Cell whose outgoing edges should be returned.
	 * @return Returns the outgoing edges of the given cell.
	 */
	public Object[] getOutgoingEdges(Object cell)
	{
		return getOutgoingEdges(cell, null);
	}

	/**
	 * Returns the visible outgoing edges for the given cell. If the optional
	 * parent argument is specified, then only child edges of the given parent
	 * are returned.
	 * 
	 * @param cell Cell whose outgoing edges should be returned.
	 * @param parent Optional parent of the opposite end for an edge
	 * to be returned.
	 * @return Returns the outgoing edges of the given cell.
	 */
	public Object[] getOutgoingEdges(Object cell, Object parent)
	{
		return getEdges(cell, parent, false, true, false);
	}

	/**
	 * Returns all visible edges connected to the given cell including loops.
	 *
	 * @param cell Cell whose edges should be returned.
	 * @return Returns the edges of the given cell.
	 */
	public Object[] getEdges(Object cell)
	{
		return getEdges(cell, null);
	}

	/**
	 * Returns all visible edges connected to the given cell including loops.
	 * 
	 * @param cell Cell whose edges should be returned.
	 * @param parent Optional parent of the opposite end for an edge
	 * to be returned.
	 * @return Returns the edges of the given cell.
	 */
	public Object[] getEdges(Object cell, Object parent)
	{
		return getEdges(cell, parent, true, true, true);
	}

	/**
	 * Returns the incoming and/or outgoing edges for the given cell.
	 * If the optional parent argument is specified, then only edges are returned
	 * where the opposite is in the given parent cell.
	 * 
	 * @param cell Cell whose edges should be returned.
	 * @param parent Optional parent of the opposite end for an edge to be
	 * returned.
	 * @param incoming Specifies if incoming edges should be included in the
	 * result.
	 * @param outgoing Specifies if outgoing edges should be included in the
	 * result.
	 * @param includeLoops Specifies if loops should be included in the result.
	 * @return Returns the edges connected to the given cell.
	 */
	public Object[] getEdges(Object cell, Object parent, boolean incoming,
			boolean outgoing, boolean includeLoops)
	{
		boolean isCollapsed = isCellCollapsed(cell);
		List<Object> edges = new ArrayList<Object>();
		int childCount = model.getChildCount(cell);

		for (int i = 0; i < childCount; i++)
		{
			Object child = model.getChildAt(cell, i);

			if (isCollapsed || !isCellVisible(child))
			{
				edges.addAll(Arrays.asList(mxGraphModel.getEdges(model, child,
						incoming, outgoing, includeLoops)));
			}
		}

		edges.addAll(Arrays.asList(mxGraphModel.getEdges(model, cell, incoming,
				outgoing, includeLoops)));
		List<Object> result = new ArrayList<Object>(edges.size());
		Iterator<Object> it = edges.iterator();

		while (it.hasNext())
		{
			Object edge = it.next();
			Object source = view.getVisibleTerminal(edge, true);
			Object target = view.getVisibleTerminal(edge, false);

			if (includeLoops
					|| ((source != target) && ((incoming && target == cell && (parent == null || model
							.getParent(source) == parent)) || (outgoing
							&& source == cell && (parent == null || model
							.getParent(target) == parent)))))
			{
				result.add(edge);
			}
		}

		return result.toArray();
	}

	/**
	 * Returns all distinct visible opposite cells of the terminal on the given
	 * edges.
	 * 
	 * @param edges
	 * @param terminal
	 * @return Returns the terminals at the opposite ends of the given edges.
	 */
	public Object[] getOpposites(Object[] edges, Object terminal)
	{
		return getOpposites(edges, terminal, true, true);
	}

	/**
	 * Returns all distincts visible opposite cells for the specified terminal
	 * on the given edges.
	 * 
	 * @param edges Edges whose opposite terminals should be returned.
	 * @param terminal Terminal that specifies the end whose opposite should be
	 * returned.
	 * @param sources Specifies if source terminals should be included in the
	 * result.
	 * @param targets Specifies if targer terminals should be included in the
	 * result.
	 * @return Returns the cells at the oppsite ends of the given edges.
	 */
	public Object[] getOpposites(Object[] edges, Object terminal,
			boolean sources, boolean targets)
	{
		Collection<Object> terminals = new LinkedHashSet<Object>();

		if (edges != null)
		{
			for (int i = 0; i < edges.length; i++)
			{
				Object source = view.getVisibleTerminal(edges[i], true);
				Object target = view.getVisibleTerminal(edges[i], false);

				// Checks if the terminal is the source of
				// the edge and if the target should be
				// stored in the result
				if (targets && source == terminal && target != null
						&& target != terminal)
				{
					terminals.add(target);
				}

				// Checks if the terminal is the taget of
				// the edge and if the source should be
				// stored in the result
				else if (sources && target == terminal && source != null
						&& source != terminal)
				{
					terminals.add(source);
				}
			}
		}

		return terminals.toArray();
	}

	/**
	 * Returns the edges between the given source and target. This takes into
	 * account collapsed and invisible cells and returns the connected edges
	 * as displayed on the screen.
	 * 
	 * @param source
	 * @param target
	 * @return Returns all edges between the given terminals.
	 */
	public Object[] getEdgesBetween(Object source, Object target)
	{
		return getEdgesBetween(source, target, false);
	}

	/**
	 * Returns the edges between the given source and target. This takes into
	 * account collapsed and invisible cells and returns the connected edges
	 * as displayed on the screen.
	 * 
	 * @param source
	 * @param target
	 * @param directed
	 * @return Returns all edges between the given terminals.
	 */
	public Object[] getEdgesBetween(Object source, Object target,
			boolean directed)
	{
		Object[] edges = getEdges(source);
		List<Object> result = new ArrayList<Object>(edges.length);

		// Checks if the edge is connected to the correct
		// cell and adds any match to the result
		for (int i = 0; i < edges.length; i++)
		{
			Object src = view.getVisibleTerminal(edges[i], true);
			Object trg = view.getVisibleTerminal(edges[i], false);

			if ((src == source && trg == target)
					|| (!directed && src == target && trg == source))
			{
				result.add(edges[i]);
			}
		}

		return result.toArray();
	}

	/**
	 * Returns the children of the given parent that are contained in the
	 * halfpane from the given point (x0, y0) rightwards and downwards
	 * depending on rightHalfpane and bottomHalfpane.
	 * 
	 * @param x0 X-coordinate of the origin.
	 * @param y0 Y-coordinate of the origin.
	 * @param parent <mxCell> whose children should be checked.
	 * @param rightHalfpane Boolean indicating if the cells in the right halfpane
	 * from the origin should be returned.
	 * @param bottomHalfpane Boolean indicating if the cells in the bottom halfpane
	 * from the origin should be returned.
	 * @return Returns the cells beyond the given halfpane.
	 */
	public Object[] getCellsBeyond(double x0, double y0, Object parent,
			boolean rightHalfpane, boolean bottomHalfpane)
	{
		if (parent == null)
		{
			parent = getDefaultParent();
		}

		int childCount = model.getChildCount(parent);
		List<Object> result = new ArrayList<Object>(childCount);

		if (rightHalfpane || bottomHalfpane)
		{

			if (parent != null)
			{
				for (int i = 0; i < childCount; i++)
				{
					Object child = model.getChildAt(parent, i);
					mxCellState state = view.getState(child);

					if (isCellVisible(child) && state != null)
					{
						if ((!rightHalfpane || state.getX() >= x0)
								&& (!bottomHalfpane || state.getY() >= y0))
						{
							result.add(child);
						}
					}
				}
			}
		}

		return result.toArray();
	}

	/**
	 * Returns all visible children in the given parent which do not have
	 * incoming edges. If the result is empty then the with the greatest
	 * difference between incoming and outgoing edges is returned. This
	 * takes into account edges that are being promoted to the given
	 * root due to invisible children or collapsed cells.
	 * 
	 * @param parent Cell whose children should be checked.
	 * @return Array of tree roots in parent.
	 */
	public Object[] findTreeRoots(Object parent)
	{
		return findTreeRoots(parent, false);
	}

	/**
	 * Returns all visible children in the given parent which do not have
	 * incoming edges. If the result is empty then the children with the
	 * maximum difference between incoming and outgoing edges are returned.
	 * This takes into account edges that are being promoted to the given
	 * root due to invisible children or collapsed cells.
	 * 
	 * @param parent Cell whose children should be checked.
	 * @param isolate Specifies if edges should be ignored if the opposite
	 * end is not a child of the given parent cell.
	 * @return Array of tree roots in parent.
	 */
	public Object[] findTreeRoots(Object parent, boolean isolate)
	{
		return findTreeRoots(parent, isolate, false);
	}

	/**
	 * Returns all visible children in the given parent which do not have
	 * incoming edges. If the result is empty then the children with the
	 * maximum difference between incoming and outgoing edges are returned.
	 * This takes into account edges that are being promoted to the given
	 * root due to invisible children or collapsed cells.
	 * 
	 * @param parent Cell whose children should be checked.
	 * @param isolate Specifies if edges should be ignored if the opposite
	 * end is not a child of the given parent cell.
	 * @param invert Specifies if outgoing or incoming edges should be counted
	 * for a tree root. If false then outgoing edges will be counted.
	 * @return Array of tree roots in parent.
	 */
	public Object[] findTreeRoots(Object parent, boolean isolate, boolean invert)
	{
		List<Object> roots = new ArrayList<Object>();

		if (parent != null)
		{
			int childCount = model.getChildCount(parent);
			Object best = null;
			int maxDiff = 0;

			for (int i = 0; i < childCount; i++)
			{
				Object cell = model.getChildAt(parent, i);

				if (model.isVertex(cell) && isCellVisible(cell))
				{
					Object[] conns = getConnections(cell, (isolate) ? parent
							: null);
					int fanOut = 0;
					int fanIn = 0;

					for (int j = 0; j < conns.length; j++)
					{
						Object src = view.getVisibleTerminal(conns[j], true);

						if (src == cell)
						{
							fanOut++;
						}
						else
						{
							fanIn++;
						}
					}

					if ((invert && fanOut == 0 && fanIn > 0)
							|| (!invert && fanIn == 0 && fanOut > 0))
					{
						roots.add(cell);
					}

					int diff = (invert) ? fanIn - fanOut : fanOut - fanIn;

					if (diff > maxDiff)
					{
						maxDiff = diff;
						best = cell;
					}
				}
			}

			if (roots.isEmpty() && best != null)
			{
				roots.add(best);
			}
		}

		return roots.toArray();
	}

	/**
	 * Traverses the tree starting at the given vertex. Here is how to use this
	 * method for a given vertex (root) which is typically the root of a tree:
	 * <code>
	 * graph.traverse(root, true, new mxICellVisitor()
	 * {
	 *   public boolean visit(Object vertex, Object edge)
	 *   {
	 *     System.out.println("edge="+graph.convertValueToString(edge)+
	 *       " vertex="+graph.convertValueToString(vertex));
	 *     
	 *     return true;
	 *   }
	 * });
	 * </code>
	 * 
	 * @param vertex
	 * @param directed
	 * @param visitor
	 */
	public void traverse(Object vertex, boolean directed, mxICellVisitor visitor)
	{
		traverse(vertex, directed, visitor, null, null);
	}

	/**
	 * Traverses the (directed) graph invoking the given function for each
	 * visited vertex and edge. The function is invoked with the current vertex
	 * and the incoming edge as a parameter. This implementation makes sure
	 * each vertex is only visited once. The function may return false if the
	 * traversal should stop at the given vertex.
	 * 
	 * @param vertex <mxCell> that represents the vertex where the traversal starts.
	 * @param directed Optional boolean indicating if edges should only be traversed
	 * from source to target. Default is true.
	 * @param visitor Visitor that takes the current vertex and the incoming edge.
	 * The traversal stops if the function returns false.
	 * @param edge Optional <mxCell> that represents the incoming edge. This is
	 * null for the first step of the traversal.
	 * @param visited Optional array of cell paths for the visited cells.
	 */
	public void traverse(Object vertex, boolean directed,
			mxICellVisitor visitor, Object edge, Set<Object> visited)
	{
		if (vertex != null && visitor != null)
		{
			if (visited == null)
			{
				visited = new HashSet<Object>();
			}

			if (!visited.contains(vertex))
			{
				visited.add(vertex);

				if (visitor.visit(vertex, edge))
				{
					int edgeCount = model.getEdgeCount(vertex);

					if (edgeCount > 0)
					{
						for (int i = 0; i < edgeCount; i++)
						{
							Object e = model.getEdgeAt(vertex, i);
							boolean isSource = model.getTerminal(e, true) == vertex;

							if (!directed || isSource)
							{
								Object next = model.getTerminal(e, !isSource);
								traverse(next, directed, visitor, e, visited);
							}
						}
					}
				}
			}
		}
	}

	//
	// Selection
	//

	/**
	 * 
	 */
	public mxGraphSelectionModel getSelectionModel()
	{
		return selectionModel;
	}

	/**
	 * 
	 */
	public int getSelectionCount()
	{
		return selectionModel.size();
	}

	/**
	 * 
	 * @param cell
	 * @return Returns true if the given cell is selected.
	 */
	public boolean isCellSelected(Object cell)
	{
		return selectionModel.isSelected(cell);
	}

	/**
	 * 
	 * @return Returns true if the selection is empty.
	 */
	public boolean isSelectionEmpty()
	{
		return selectionModel.isEmpty();
	}

	/**
	 * 
	 */
	public void clearSelection()
	{
		selectionModel.clear();
	}

	/**
	 * 
	 * @return Returns the selection cell.
	 */
	public Object getSelectionCell()
	{
		return selectionModel.getCell();
	}

	/**
	 * 
	 * @param cell
	 */
	public void setSelectionCell(Object cell)
	{
		selectionModel.setCell(cell);
	}

	/**
	 * 
	 * @return Returns the selection cells.
	 */
	public Object[] getSelectionCells()
	{
		return selectionModel.getCells();
	}

	/**
	 * 
	 */
	public void setSelectionCells(Object[] cells)
	{
		selectionModel.setCells(cells);
	}

	/**
	 * 
	 * @param cells
	 */
	public void setSelectionCells(Collection<Object> cells)
	{
		if (cells != null)
		{
			setSelectionCells(cells.toArray());
		}
	}

	/**
	 * 
	 */
	public void addSelectionCell(Object cell)
	{
		selectionModel.addCell(cell);
	}

	/**
	 * 
	 */
	public void addSelectionCells(Object[] cells)
	{
		selectionModel.addCells(cells);
	}

	/**
	 * 
	 */
	public void removeSelectionCell(Object cell)
	{
		selectionModel.removeCell(cell);
	}

	/**
	 * 
	 */
	public void removeSelectionCells(Object[] cells)
	{
		selectionModel.removeCells(cells);
	}

	/**
	 * Selects the next cell.
	 */
	public void selectNextCell()
	{
		selectCell(true, false, false);
	}

	/**
	 * Selects the previous cell.
	 */
	public void selectPreviousCell()
	{
		selectCell(false, false, false);
	}

	/**
	 * Selects the parent cell.
	 */
	public void selectParentCell()
	{
		selectCell(false, true, false);
	}

	/**
	 * Selects the first child cell.
	 */
	public void selectChildCell()
	{
		selectCell(false, false, true);
	}

	/**
	 * Selects the next, parent, first child or previous cell, if all arguments
	 * are false.
	 * 
	 * @param isNext
	 * @param isParent
	 * @param isChild
	 */
	public void selectCell(boolean isNext, boolean isParent, boolean isChild)
	{
		Object cell = getSelectionCell();

		if (getSelectionCount() > 1)
		{
			clearSelection();
		}

		Object parent = (cell != null) ? model.getParent(cell)
				: getDefaultParent();
		int childCount = model.getChildCount(parent);

		if (cell == null && childCount > 0)
		{
			Object child = model.getChildAt(parent, 0);
			setSelectionCell(child);
		}
		else if ((cell == null || isParent) && view.getState(parent) != null
				&& model.getGeometry(parent) != null)
		{
			if (getCurrentRoot() != parent)
			{
				setSelectionCell(parent);
			}
		}
		else if (cell != null && isChild)
		{
			int tmp = model.getChildCount(cell);

			if (tmp > 0)
			{
				Object child = model.getChildAt(cell, 0);
				setSelectionCell(child);
			}
		}
		else if (childCount > 0)
		{
			int i = ((mxICell) parent).getIndex((mxICell) cell);

			if (isNext)
			{
				i++;
				setSelectionCell(model.getChildAt(parent, i % childCount));
			}
			else
			{
				i--;
				int index = (i < 0) ? childCount - 1 : i;
				setSelectionCell(model.getChildAt(parent, index));
			}
		}
	}

	/**
	 * Selects all vertices inside the default parent.
	 */
	public void selectVertices()
	{
		selectVertices(null);
	}

	/**
	 * Selects all vertices inside the given parent or the default parent
	 * if no parent is given.
	 */
	public void selectVertices(Object parent)
	{
		selectCells(true, false, parent);
	}

	/**
	 * Selects all vertices inside the default parent.
	 */
	public void selectEdges()
	{
		selectEdges(null);
	}

	/**
	 * Selects all vertices inside the given parent or the default parent
	 * if no parent is given.
	 */
	public void selectEdges(Object parent)
	{
		selectCells(false, true, parent);
	}

	/**
	 * Selects all vertices and/or edges depending on the given boolean
	 * arguments recursively, starting at the default parent. Use
	 * <code>selectAll</code> to select all cells.
	 *  
	 * @param vertices Boolean indicating if vertices should be selected.
	 * @param edges Boolean indicating if edges should be selected.
	 */
	public void selectCells(boolean vertices, boolean edges)
	{
		selectCells(vertices, edges, null);
	}

	/**
	 * Selects all vertices and/or edges depending on the given boolean
	 * arguments recursively, starting at the given parent or the default
	 * parent if no parent is specified. Use <code>selectAll</code> to select
	 * all cells.
	 * 
	 * @param vertices Boolean indicating if vertices should be selected.
	 * @param edges Boolean indicating if edges should be selected.
	 * @param parent Optional cell that acts as the root of the recursion.
	 * Default is <code>defaultParent</code>.
	 */
	public void selectCells(final boolean vertices, final boolean edges,
			Object parent)
	{
		if (parent == null)
		{
			parent = getDefaultParent();
		}

		Collection<Object> cells = mxGraphModel.filterDescendants(getModel(),
				new mxGraphModel.Filter()
				{

					/**
					 * 
					 */
					public boolean filter(Object cell)
					{
						return view.getState(cell) != null
								&& model.getChildCount(cell) == 0
								&& ((model.isVertex(cell) && vertices) || (model
										.isEdge(cell) && edges));
					}

				});
		setSelectionCells(cells);
	}

	/**
	 * 
	 */
	public void selectAll()
	{
		selectAll(null);
	}

	/**
	 * Selects all children of the given parent cell or the children of the
	 * default parent if no parent is specified. To select leaf vertices and/or
	 * edges use <selectCells>.
	 * 
	 * @param parent  Optional <mxCell> whose children should be selected.
	 * Default is <defaultParent>.
	 */
	public void selectAll(Object parent)
	{
		if (parent == null)
		{
			parent = getDefaultParent();
		}

		Object[] children = mxGraphModel.getChildren(model, parent);

		if (children != null)
		{
			setSelectionCells(children);
		}
	}

	//
	// Images and drawing
	//

	/**
	 * Draws the graph onto the given canvas.
	 * 
	 * @param canvas Canvas onto which the graph should be drawn.
	 */
	public void drawGraph(mxICanvas canvas)
	{
		drawCell(canvas, getModel().getRoot());
	}

	/**
	 * Draws the given cell and its descendants onto the specified canvas.
	 * 
	 * @param canvas Canvas onto which the cell should be drawn.
	 * @param cell Cell that should be drawn onto the canvas.
	 */
	public void drawCell(mxICanvas canvas, Object cell)
	{
		drawState(canvas, getView().getState(cell), getLabel(cell));

		// Draws the children on top of their parent
		int childCount = model.getChildCount(cell);

		for (int i = 0; i < childCount; i++)
		{
			Object child = model.getChildAt(cell, i);
			drawCell(canvas, child);
		}
	}

	/**
	 * Draws the cell state with the given label onto the canvas. No
	 * children or descendants are painted here. This method invokes
	 * cellDrawn after the cell, but not its descendants have been
	 * painted.
	 * 
	 * @param canvas Canvas onto which the cell should be drawn.
	 * @param state State of the cell to be drawn.
	 * @param label Label of the cell to be dranw.
	 */
	public void drawState(mxICanvas canvas, mxCellState state, String label)
	{
		Object cell = (state != null) ? state.getCell() : null;

		if (cell != null && cell != view.getCurrentRoot()
				&& cell != model.getRoot()
				&& (model.isVertex(cell) || model.isEdge(cell)))
		{
			Object obj = canvas.drawCell(state);
			Object lab = null;

			// Holds the current clipping region in case the label will
			// be clipped
			Shape clip = null;
			Rectangle newClip = state.getRectangle();

			// Indirection for image canvas that contains a graphics canvas
			mxICanvas clippedCanvas = (isLabelClipped(state.getCell())) ? canvas
					: null;

			if (clippedCanvas instanceof mxImageCanvas)
			{
				clippedCanvas = ((mxImageCanvas) clippedCanvas)
						.getGraphicsCanvas();
				// TODO: Shift newClip to match the image offset
				//Point pt = ((mxImageCanvas) canvas).getTranslate();
				//newClip.translate(-pt.x, -pt.y);
			}

			if (clippedCanvas instanceof mxGraphics2DCanvas)
			{
				Graphics g = ((mxGraphics2DCanvas) clippedCanvas).getGraphics();
				clip = g.getClip();
				g.setClip(newClip);
			}

			if (label != null && state.getLabelBounds() != null)
			{
				lab = canvas.drawLabel(label, state, isHtmlLabel(cell));
			}

			// Restores the previous clipping region
			if (clippedCanvas instanceof mxGraphics2DCanvas)
			{
				((mxGraphics2DCanvas) clippedCanvas).getGraphics()
						.setClip(clip);
			}

			// Invokes the cellDrawn callback with the object which was created
			// by the canvas to represent the cell graphically
			if (obj != null)
			{
				cellDrawn(canvas, state, obj, lab);
			}
		}
	}

	/**
	 * Called when a cell has been painted as the specified object, typically a
	 * DOM node that represents the given cell graphically in a document.
	 */
	protected void cellDrawn(mxICanvas canvas, mxCellState state,
			Object element, Object labelElement)
	{
		if (element instanceof Element)
		{
			String link = getLinkForCell(state.getCell());

			if (link != null)
			{
				String title = getToolTipForCell(state.getCell());
				Element elem = (Element) element;

				if (elem.getNodeName().startsWith("v:"))
				{
					elem.setAttribute("href", link.toString());

					if (title != null)
					{
						elem.setAttribute("title", title);
					}
				}
				else if (elem.getOwnerDocument().getElementsByTagName("svg")
						.getLength() > 0)
				{
					Element xlink = elem.getOwnerDocument().createElement("a");
					xlink.setAttribute("xlink:href", link.toString());

					elem.getParentNode().replaceChild(xlink, elem);
					xlink.appendChild(elem);

					if (title != null)
					{
						xlink.setAttribute("xlink:title", title);
					}

					elem = xlink;
				}
				else
				{
					Element a = elem.getOwnerDocument().createElement("a");
					a.setAttribute("href", link.toString());
					a.setAttribute("style", "text-decoration:none;");

					elem.getParentNode().replaceChild(a, elem);
					a.appendChild(elem);

					if (title != null)
					{
						a.setAttribute("title", title);
					}

					elem = a;
				}

				String target = getTargetForCell(state.getCell());

				if (target != null)
				{
					elem.setAttribute("target", target);
				}
			}
		}
	}

	/**
	 * Returns the hyperlink to be used for the given cell.
	 */
	protected String getLinkForCell(Object cell)
	{
		return null;
	}

	/**
	 * Returns the hyperlink to be used for the given cell.
	 */
	protected String getTargetForCell(Object cell)
	{
		return null;
	}

	//
	// Redirected to change support
	//

	/**
	 * @param listener
	 * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener)
	 */
	public void addPropertyChangeListener(PropertyChangeListener listener)
	{
		changeSupport.addPropertyChangeListener(listener);
	}

	/**
	 * @param propertyName
	 * @param listener
	 * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
	 */
	public void addPropertyChangeListener(String propertyName,
			PropertyChangeListener listener)
	{
		changeSupport.addPropertyChangeListener(propertyName, listener);
	}

	/**
	 * @param listener
	 * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
	 */
	public void removePropertyChangeListener(PropertyChangeListener listener)
	{
		changeSupport.removePropertyChangeListener(listener);
	}

	/**
	 * @param propertyName
	 * @param listener
	 * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
	 */
	public void removePropertyChangeListener(String propertyName,
			PropertyChangeListener listener)
	{
		changeSupport.removePropertyChangeListener(propertyName, listener);
	}

	/**
	 * Prints the version number on the console. 
	 */
	public static void main(String[] args)
	{
		System.out.println("mxGraph version \"" + VERSION + "\"");
	}

}
