/*
 * @(#)BasicGraphUI.java	1.0 03-JUL-04
 * 
 * Copyright (c) 2001-2004 Gaudenz Alder
 *  
 */
package org.jgraph.plaf.basic;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.VolatileImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.util.Map;
import java.util.TooManyListenersException;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.CellRendererPane;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.TransferHandler;
import javax.swing.UIManager;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;

import org.jgraph.JGraph;
import org.jgraph.event.GraphLayoutCacheEvent;
import org.jgraph.event.GraphLayoutCacheListener;
import org.jgraph.event.GraphModelEvent;
import org.jgraph.event.GraphModelListener;
import org.jgraph.event.GraphSelectionEvent;
import org.jgraph.event.GraphSelectionListener;
import org.jgraph.graph.AbstractCellView;
import org.jgraph.graph.AttributeMap;
import org.jgraph.graph.BasicMarqueeHandler;
import org.jgraph.graph.CellHandle;
import org.jgraph.graph.CellView;
import org.jgraph.graph.CellViewRenderer;
import org.jgraph.graph.ConnectionSet;
import org.jgraph.graph.DefaultGraphModel;
import org.jgraph.graph.EdgeRenderer;
import org.jgraph.graph.EdgeView;
import org.jgraph.graph.GraphCell;
import org.jgraph.graph.GraphCellEditor;
import org.jgraph.graph.GraphConstants;
import org.jgraph.graph.GraphContext;
import org.jgraph.graph.GraphLayoutCache;
import org.jgraph.graph.GraphModel;
import org.jgraph.graph.GraphSelectionModel;
import org.jgraph.graph.GraphTransferHandler;
import org.jgraph.graph.ParentMap;
import org.jgraph.graph.PortView;
import org.jgraph.plaf.GraphUI;
import org.jgraph.util.RectUtils;

/**
 * The basic L&F for a graph data structure.
 * 
 * @version 1.0 1/1/02
 * @author Gaudenz Alder
 */

public class BasicGraphUI extends GraphUI implements Serializable {

	/**
	 * Controls live-preview in dragEnabled mode. This is used to disable
	 * live-preview in dragEnabled mode on Java 1.4.0 to workaround a bug that
	 * cause the VM to hang during concurrent DnD and repaints. Is this still
	 * required?
	 */
	public static final boolean DNDPREVIEW = System.getProperty("java.version")
			.compareTo("1.4.0") < 0
			|| System.getProperty("java.version").compareTo("1.4.0") > 0;

	/** Border in pixels to scroll if marquee or dragging are active. */
	public static int SCROLLBORDER = 18;

	/** Multiplicator for width and height when autoscrolling (=stepsize). */
	public static float SCROLLSTEP = 0.05f;

	/** The maximum number of cells to paint when dragging. */
	public static int MAXCELLS = 20;

	/** The maximum number of handles to paint individually. */
	public static int MAXHANDLES = 20;

	/** Maximum number of cells to compute clipping bounds for. */
	public static int MAXCLIPCELLS = 20;

	/** Minimum preferred size. */
	protected Dimension preferredMinSize;

	/** Component that we're going to be drawing into. */
	protected JGraph graph;

	/** Reference to the graph's view (geometric pattern). */
	protected GraphLayoutCache graphLayoutCache;

	/** Current editor for the graph. */
	protected GraphCellEditor cellEditor;

	/**
	 * Set to false when editing and shouldSelectCell() returns true meaning the
	 * node should be selected before editing, used in completeEditing.
	 */
	protected boolean stopEditingInCompleteEditing;

	/** Used to paint the CellRenderer. */
	protected CellRendererPane rendererPane;

	/** Size needed to completely display all the cells. */
	protected Dimension preferredSize;

	/** Is the preferredSize valid? */
	protected boolean validCachedPreferredSize;

	/** Used to determine what to display. */
	protected GraphModel graphModel;

	/** Model maintaining the selection. */
	protected GraphSelectionModel graphSelectionModel;

	/** Handle that we are going to use. */
	protected CellHandle handle;

	/** Marquee that we are going to use. */
	protected BasicMarqueeHandler marquee;

	// Following 4 ivars are only valid when editing.
	/**
	 * When editing, this will be the Component that is doing the actual
	 * editing.
	 */
	protected Component editingComponent;

	/** The focused cell under the mousepointer and the last focused cell. */
	protected CellView focus, lastFocus;

	/** Path that is being edited. */
	protected Object editingCell;

	/** Set to true if the editor has a different size than the renderer. */
	protected boolean editorHasDifferentSize;

	/** Needed to exchange information between Transfer- and MouseListener. */
	protected Point insertionLocation;

	/**
	 * Needed to exchange information between DropTargetHandler and
	 * TransferHandler.
	 */
	protected int dropAction = TransferHandler.NONE;

	/**
	 * If ture, a the view under mousepointer will be snapped to the grid lines
	 * during a drag operation. If snap-to-grid mode is disabled, views are
	 * moved by a snap increment.
	 */
	protected boolean snapSelectedView = false;

	// Cached listeners
	/** Listens for JGraph property changes and updates display. */
	protected PropertyChangeListener propertyChangeListener;

	/** Listens for Mouse events. */
	protected MouseListener mouseListener;

	/** Listens for KeyListener events. */
	protected KeyListener keyListener;

	/** Listens for Component events. */
	protected ComponentListener componentListener;

	/** Listens for CellEditor events. */
	protected CellEditorListener cellEditorListener = createCellEditorListener();

	/** Updates the display when the selection changes. */
	protected GraphSelectionListener graphSelectionListener;

	/** Is responsible for updating the view based on model events. */
	protected GraphModelListener graphModelListener;

	/** Updates the display when the view has changed. */
	protected GraphLayoutCacheListener graphLayoutCacheListener;

	/** The default TransferHandler. */
	protected TransferHandler defaultTransferHandler;

	/** The default DropTargetListener. */
	protected GraphDropTargetListener defaultDropTargetListener;

	/** The drop target where the default listener was last installed. */
	protected DropTarget dropTarget = null;

	public static ComponentUI createUI(JComponent x) {
		return new BasicGraphUI();
	}

	public BasicGraphUI() {
		super();
	}

	//
	// Methods for configuring the behavior of the graph. None of them
	// push the value to the JGraph instance. You should really only
	// call these methods on the JGraph instance.
	//

	/**
	 * Sets the GraphModel. This invokes <code>updateSize</code>.
	 */
	protected void setModel(GraphModel model) {
		cancelEditing(graph);
		if (graphModel != null && graphModelListener != null)
			graphModel.removeGraphModelListener(graphModelListener);
		graphModel = model;
		if (graphModel != null && graphModelListener != null)
			graphModel.addGraphModelListener(graphModelListener);
		if (graphModel != null) // jmv : to avoid NullPointerException
			updateSize();
	}

	/**
	 * Sets the GraphLayoutCache (geometric pattern). This invokes
	 * <code>updateSize</code>.
	 */
	protected void setGraphLayoutCache(GraphLayoutCache cache) {
		cancelEditing(graph);
		if (graphLayoutCache != null && graphLayoutCacheListener != null)
			graphLayoutCache
					.removeGraphLayoutCacheListener(graphLayoutCacheListener);
		graphLayoutCache = cache;
		if (graphLayoutCache != null && graphLayoutCacheListener != null)
			graphLayoutCache
					.addGraphLayoutCacheListener(graphLayoutCacheListener);
		updateSize();
	}

	/**
	 * Sets the marquee handler.
	 */
	protected void setMarquee(BasicMarqueeHandler marqueeHandler) {
		marquee = marqueeHandler;
	}

	/**
	 * Resets the selection model. The appropriate listeners are installed on
	 * the model.
	 */
	protected void setSelectionModel(GraphSelectionModel newLSM) {
		cancelEditing(graph);
		if (graphSelectionListener != null && graphSelectionModel != null)
			graphSelectionModel
					.removeGraphSelectionListener(graphSelectionListener);
		graphSelectionModel = newLSM;
		if (graphSelectionModel != null && graphSelectionListener != null)
			graphSelectionModel
					.addGraphSelectionListener(graphSelectionListener);
		if (graph != null)
			graph.repaint();
	}

	//
	// GraphUI methods
	//

	/**
	 * 
	 */

	/**
	 * Returns the handle that is currently active, or null, if no handle is
	 * currently active. Typically, the returned objects are instances of the
	 * RootHandle inner class.
	 */
	public CellHandle getHandle() {
		return handle;
	}

	/**
	 * Returns the current drop action.
	 */
	public int getDropAction() {
		return dropAction;
	}

	/**
	 * Returns the cell that has the focus.
	 */
	protected Object getFocusedCell() {
		if (focus != null)
			return focus.getCell();
		return null;
	}

	/** Get the preferred Size for a cell view. */
	public Dimension2D getPreferredSize(JGraph graph, CellView view) {
		// Either label or icon
		if (view != null) {
			Object cell = view.getCell();
			String valueStr = graph.convertValueToString(cell);
			boolean label = (valueStr != null && valueStr.length() > 0);
			boolean icon = GraphConstants.getIcon(view.getAllAttributes()) != null;
			if (label || icon) {
				boolean focus = (getFocusedCell() == cell) && graph.hasFocus();
				// Only ever removed when UI changes, this is OK!
				Component component = view.getRendererComponent(graph, focus,
						false, false);
				if (component != null) {
					graph.add(component);
					component.validate();
					Dimension d = component.getPreferredSize();
					int inset = 2 * GraphConstants.getInset(view
							.getAllAttributes());
					d.width += inset;
					d.height += inset;
					return d;
				}
			}
			if (view.getBounds() == null) {
				if (graphLayoutCache != null) {
					view.update(null);
				} else if (graph.getGraphLayoutCache() != null) {
					view.update(graph.getGraphLayoutCache());
				} else {
					view.update(null);
				}
			}
			Rectangle2D bounds = view.getBounds();
			return new Dimension((int) bounds.getWidth(), (int) bounds
					.getHeight());
		}
		return null;
	}

	//
	// Insertion Location
	//
	// Used to track the location of the mousepointer during Drag-and-Drop.
	//

	/**
	 * Returns the current location of the Drag-and-Drop activity.
	 */
	public Point getInsertionLocation() {
		return insertionLocation;
	}

	/**
	 * Sets the current location for Drag-and-Drop activity. Should be set to
	 * null after a drop. Used from within DropTargetListener.
	 */
	public void setInsertionLocation(Point p) {
		insertionLocation = p;
	}

	//
	// Selection
	//

	/**
	 * From GraphUI interface.
	 */
	public void selectCellsForEvent(JGraph graph, Object[] cells,
			MouseEvent event) {
		selectCellsForEvent(cells, event);
	}

	/**
	 * Messaged to update the selection based on a MouseEvent for a group of
	 * cells. If the event is a toggle selection event, the cells are either
	 * selected, or deselected. Otherwise the cells are selected.
	 */
	public void selectCellsForEvent(Object[] cells, MouseEvent event) {
		if (cells == null || !graph.isSelectionEnabled())
			return;

		// Toggle selection
		if (isToggleSelectionEvent(event)) {
			for (int i = 0; i < cells.length; i++)
				toggleSelectionCellForEvent(cells[i], event);

			// Select cells
		} else if (isAddToSelectionEvent(event))
			graph.addSelectionCells(cells);
		else
			graph.setSelectionCells(cells);
	}

	/**
	 * Messaged to update the selection based on a MouseEvent over a particular
	 * cell. If the event is a toggle selection event, the cell is either
	 * selected, or deselected. Otherwise the cell is selected.
	 */
	public void selectCellForEvent(Object cell, MouseEvent event) {
		if (graph.isSelectionEnabled()) {
			// Toggle selection
			if (isToggleSelectionEvent(event))
				toggleSelectionCellForEvent(cell, event);

			// Select cell
			else if (isAddToSelectionEvent(event))
				graph.addSelectionCell(cell);
			else
				graph.setSelectionCell(cell);
		}
	}

	/**
	 * Messaged to update the selection based on a toggle selection event, which
	 * means the cell's selection state is inverted.
	 */
	protected void toggleSelectionCellForEvent(Object cell, MouseEvent event) {
		if (graph.isCellSelected(cell))
			graph.removeSelectionCell(cell);
		else
			graph.addSelectionCell(cell);
	}

	/**
	 * Returning true signifies that cells are added to the selection.
	 */
	public boolean isAddToSelectionEvent(MouseEvent e) {
		return e.isShiftDown();
	}

	/**
	 * Returning true signifies a mouse event on the cell should toggle the
	 * selection of only the cell under mouse.
	 */
	public boolean isToggleSelectionEvent(MouseEvent e) {
		switch (Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) {
		case InputEvent.CTRL_MASK:
			return e.isControlDown();
		case InputEvent.ALT_MASK:
			return e.isAltDown();
		case InputEvent.META_MASK:
			return e.isMetaDown();
		default:
			return false;
		}
	}

	/**
	 * Returning true signifies the marquee handler has precedence over other
	 * handlers, and is receiving subsequent mouse events.
	 */
	public boolean isForceMarqueeEvent(MouseEvent event) {
		if (marquee != null)
			return marquee.isForceMarqueeEvent(event);
		return false;
	}

	/**
	 * Returning true signifies a move should only be applied to one direction.
	 */
	public boolean isConstrainedMoveEvent(MouseEvent event) {
		if (event != null)
			return event.isShiftDown();
		return false;
	}

	//
	// Editing
	//

	/**
	 * Returns true if the graph is being edited. The item that is being edited
	 * can be returned by getEditingPath().
	 */
	public boolean isEditing(JGraph graph) {
		return (editingComponent != null);
	}

	/**
	 * Stops the current editing session. This has no effect if the graph isn't
	 * being edited. Returns true if the editor allows the editing session to
	 * stop.
	 */
	public boolean stopEditing(JGraph graph) {
		if (editingComponent != null && cellEditor.stopCellEditing()) {
			completeEditing(false, false, true);
			return true;
		}
		return false;
	}

	/**
	 * Cancels all current editing sessions.
	 */
	public void cancelEditing(JGraph graph) {
		if (editingComponent != null)
			completeEditing(false, true, false);
		// Escape key is handled by the KeyHandler.keyPressed inner class method
	}

	/**
	 * Selects the cell and tries to edit it. Editing will fail if the
	 * CellEditor won't allow it for the selected item.
	 */
	public void startEditingAtCell(JGraph graph, Object cell) {
		graph.scrollCellToVisible(cell);
		if (cell != null)
			startEditing(cell, null);
	}

	/**
	 * Returns the element that is being edited.
	 */
	public Object getEditingCell(JGraph graph) {
		return editingCell;
	}

	//
	// Install methods
	//

	public void installUI(JComponent c) {
		if (c == null)
			throw new NullPointerException(
					"null component passed to BasicGraphUI.installUI()");

		graph = (JGraph) c;
		marquee = graph.getMarqueeHandler();
		prepareForUIInstall();

		// Boilerplate install block
		installDefaults();
		installListeners();
		installKeyboardActions();
		installComponents();

		completeUIInstall();
	}

	/**
	 * Invoked after the <code>graph</code> instance variable has been set,
	 * but before any defaults/listeners have been installed.
	 */
	protected void prepareForUIInstall() {
		// Data member initializations
		stopEditingInCompleteEditing = true;
		preferredSize = new Dimension();
		setGraphLayoutCache(graph.getGraphLayoutCache());
		setModel(graph.getModel());
	}

	/**
	 * Invoked from installUI after all the defaults/listeners have been
	 * installed.
	 */
	protected void completeUIInstall() {
		// Custom install code
		setSelectionModel(graph.getSelectionModel());
		updateSize();
	}

	/**
	 * Invoked as part from the boilerplate install block. This sets the look
	 * and feel specific variables in JGraph.
	 */
	protected void installDefaults() {
		if (graph.getBackground() == null
				|| graph.getBackground() instanceof UIResource) {
			graph.setBackground(UIManager.getColor("Tree.background"));
		}
		if (graph.getFont() == null || graph.getFont() instanceof UIResource) {
			// UIManager.getFont not supported in headless environment
			try {
				graph.setFont(UIManager.getFont("Tree.font"));
			} catch (Error e) {
				// No default font
			}
		}
		// Set JGraph's laf-specific colors
		if (JGraph.IS_MAC) {
			graph.setMarqueeColor(UIManager
					.getColor("MenuItem.selectionBackground"));
		} else {
			graph.setMarqueeColor(UIManager.getColor("Table.gridColor"));
		}
		graph
				.setHandleColor(UIManager
						.getColor("MenuItem.selectionBackground"));
		graph.setLockedHandleColor(UIManager.getColor("MenuItem.background"));
		graph.setGridColor(UIManager.getColor("Tree.selectionBackground"));
		graph.setOpaque(true);
	}

	/**
	 * Invoked as part from the boilerplate install block. This installs the
	 * listeners from BasicGraphUI in the graph.
	 */
	protected void installListeners() {
		// Install Local Handlers
		TransferHandler th = graph.getTransferHandler();
		if (th == null || th instanceof UIResource) {
			defaultTransferHandler = createTransferHandler();
			// Not supported in headless environment
			try {
				graph.setTransferHandler(defaultTransferHandler);
			} catch (Error e) {
				// No default font
			}
		}
		if (graphLayoutCache != null) {
			graphLayoutCacheListener = createGraphLayoutCacheListener();
			graphLayoutCache
					.addGraphLayoutCacheListener(graphLayoutCacheListener);
		}
		dropTarget = graph.getDropTarget();
		try {
			if (dropTarget != null) {
				defaultDropTargetListener = new GraphDropTargetListener();
				dropTarget.addDropTargetListener(defaultDropTargetListener);
			}
		} catch (TooManyListenersException tmle) {
			// should not happen... swing drop target is multicast
		}

		// Install Listeners
		if ((propertyChangeListener = createPropertyChangeListener()) != null)
			graph.addPropertyChangeListener(propertyChangeListener);
		if ((mouseListener = createMouseListener()) != null) {
			graph.addMouseListener(mouseListener);
			if (mouseListener instanceof MouseMotionListener) {
				graph
						.addMouseMotionListener((MouseMotionListener) mouseListener);
			}
		}
		if ((keyListener = createKeyListener()) != null) {
			graph.addKeyListener(keyListener);
		}
		if ((graphModelListener = createGraphModelListener()) != null
				&& graphModel != null)
			graphModel.addGraphModelListener(graphModelListener);
		if ((graphSelectionListener = createGraphSelectionListener()) != null
				&& graphSelectionModel != null)
			graphSelectionModel
					.addGraphSelectionListener(graphSelectionListener);
	}

	/**
	 * Invoked as part from the boilerplate install block.
	 */
	protected void installKeyboardActions() {
		InputMap km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
		SwingUtilities.replaceUIInputMap(graph,
				JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km);
		km = getInputMap(JComponent.WHEN_FOCUSED);
		SwingUtilities.replaceUIInputMap(graph, JComponent.WHEN_FOCUSED, km);
		SwingUtilities.replaceUIActionMap(graph, createActionMap());
	}

	/**
	 * Return JTree's input map.
	 */
	InputMap getInputMap(int condition) {
		if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
			return (InputMap) UIManager.get("Tree.ancestorInputMap");
		else if (condition == JComponent.WHEN_FOCUSED)
			return (InputMap) UIManager.get("Tree.focusInputMap");
		return null;
	}

	/**
	 * Return the mapping between JTree's input map and JGraph's actions.
	 */
	ActionMap createActionMap() {
		// 1: Up, 2: Right, 3: Down, 4: Left
		ActionMap map = new ActionMapUIResource();

		map
				.put("selectPrevious", new GraphIncrementAction(1,
						"selectPrevious"));
		map.put("selectPreviousChangeLead", new GraphIncrementAction(1,
				"selectPreviousLead"));
		map.put("selectPreviousExtendSelection", new GraphIncrementAction(1,
				"selectPreviousExtendSelection"));

		map.put("selectParent", new GraphIncrementAction(4, "selectParent"));
		map.put("selectParentChangeLead", new GraphIncrementAction(4,
				"selectParentChangeLead"));

		map.put("selectNext", new GraphIncrementAction(3, "selectNext"));
		map.put("selectNextChangeLead", new GraphIncrementAction(3,
				"selectNextLead"));
		map.put("selectNextExtendSelection", new GraphIncrementAction(3,
				"selectNextExtendSelection"));

		map.put("selectChild", new GraphIncrementAction(2, "selectChild"));
		map.put("selectChildChangeLead", new GraphIncrementAction(2,
				"selectChildChangeLead"));

		map.put("cancel", new GraphCancelEditingAction("cancel"));
		map.put("startEditing", new GraphEditAction("startEditing"));
		map.put("selectAll", new GraphSelectAllAction("selectAll", true));
		map.put("clearSelection", new GraphSelectAllAction("clearSelection",
				false));
		return map;
	}

	/**
	 * Intalls the subcomponents of the graph, which is the renderer pane.
	 */
	protected void installComponents() {
		if ((rendererPane = createCellRendererPane()) != null)
			graph.add(rendererPane);
	}

	//
	// Create methods.
	//

	/**
	 * Creates an instance of TransferHandler. Used for subclassers to provide
	 * different TransferHandler.
	 */
	protected TransferHandler createTransferHandler() {
		return new GraphTransferHandler();
	}

	/**
	 * Creates a listener that is responsible to update the UI based on how the
	 * graph's bounds properties change.
	 */
	protected PropertyChangeListener createPropertyChangeListener() {
		return new PropertyChangeHandler();
	}

	/**
	 * Creates the listener responsible for calling the correct handlers based
	 * on mouse events, and to select invidual cells.
	 */
	protected MouseListener createMouseListener() {
		return new MouseHandler();
	}

	/**
	 * Creates the listener reponsible for getting key events from the graph.
	 */
	protected KeyListener createKeyListener() {
		return new KeyHandler();
	}

	/**
	 * Creates the listener that updates the display based on selection change
	 * methods.
	 */
	protected GraphSelectionListener createGraphSelectionListener() {
		return new GraphSelectionHandler();
	}

	/**
	 * Creates a listener to handle events from the current editor.
	 */
	protected CellEditorListener createCellEditorListener() {
		return new CellEditorHandler();
	}

	/**
	 * Creates and returns a new ComponentHandler.
	 */
	protected ComponentListener createComponentListener() {
		return new ComponentHandler();
	}

	/**
	 * Returns the renderer pane that renderer components are placed in.
	 */
	protected CellRendererPane createCellRendererPane() {
		return new CellRendererPane();
	}

	/**
	 * Returns a listener that can update the graph when the view changes.
	 */
	protected GraphLayoutCacheListener createGraphLayoutCacheListener() {
		return new GraphLayoutCacheHandler();
	}

	/**
	 * Returns a listener that can update the graph when the model changes.
	 */
	protected GraphModelListener createGraphModelListener() {
		return new GraphModelHandler();
	}

	//
	// Uninstall methods
	//

	public void uninstallUI(JComponent c) {
		cancelEditing(graph);
		uninstallListeners();
		uninstallKeyboardActions();
		uninstallComponents();

		completeUIUninstall();
	}

	protected void completeUIUninstall() {
		graphLayoutCache = null;
		rendererPane = null;
		componentListener = null;
		propertyChangeListener = null;
		keyListener = null;
		setSelectionModel(null);
		graph = null;
		graphModel = null;
		graphSelectionModel = null;
		graphSelectionListener = null;
	}

	protected void uninstallListeners() {
		// Uninstall Handlers
		TransferHandler th = graph.getTransferHandler();
		if (th == defaultTransferHandler)
			graph.setTransferHandler(null);
		if (graphLayoutCacheListener != null)
			graphLayoutCache
					.removeGraphLayoutCacheListener(graphLayoutCacheListener);
		if (dropTarget != null && defaultDropTargetListener != null)
			dropTarget.removeDropTargetListener(defaultDropTargetListener);
		if (componentListener != null)
			graph.removeComponentListener(componentListener);
		if (propertyChangeListener != null)
			graph.removePropertyChangeListener(propertyChangeListener);
		if (mouseListener != null) {
			graph.removeMouseListener(mouseListener);
			if (mouseListener instanceof MouseMotionListener)
				graph
						.removeMouseMotionListener((MouseMotionListener) mouseListener);
		}
		if (keyListener != null)
			graph.removeKeyListener(keyListener);
		if (graphModel != null && graphModelListener != null)
			graphModel.removeGraphModelListener(graphModelListener);
		if (graphSelectionListener != null && graphSelectionModel != null)
			graphSelectionModel
					.removeGraphSelectionListener(graphSelectionListener);
	}

	protected void uninstallKeyboardActions() {
		SwingUtilities.replaceUIActionMap(graph, null);
		SwingUtilities.replaceUIInputMap(graph,
				JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
		SwingUtilities.replaceUIInputMap(graph, JComponent.WHEN_FOCUSED, null);
	}

	/**
	 * Uninstalls the renderer pane.
	 */
	protected void uninstallComponents() {
		if (rendererPane != null)
			graph.remove(rendererPane);
	}

	//
	// Painting routines.
	//

	/**
	 * Main painting routine.
	 */
	public void paint(Graphics g, JComponent c) {
		if (graph != c)
			throw new InternalError("BasicGraphUI cannot paint " + c.toString()
					+ "; " + graph + " was expected.");

		Rectangle2D clipBounds = g.getClipBounds();
		if (clipBounds != null) {
			clipBounds = (Rectangle2D) clipBounds.clone();
		}
		if (graph.isDoubleBuffered()) {
			Graphics offGraphics = graph.getOffgraphics();
			Image offscreen = graph.getOffscreen();
			if (offGraphics == null || offscreen == null) {
				drawGraph(g, clipBounds);
				paintOverlay(g);
				return;
			}
			if (offscreen instanceof VolatileImage) {
				int volatileContentsLostCount = 0;
				do {
					offGraphics = graph.getOffgraphics();
					volatileContentsLostCount++;
					if (volatileContentsLostCount > 10) {
						// Assume a problem with the volatile buffering
						// and move to standard buffered images
						graph.setVolatileOffscreen(false);
					}
				} while (((VolatileImage) offscreen).contentsLost());
			}
			g.drawImage(graph.getOffscreen(), 0, 0, graph);
			
			// Paints the handle and marquee regardless of the double buffering
			// and XOR state so that no artifacts for the marquee appear after
			// scroll
			//if (!graph.isXorEnabled()) {
				// Paint Handle
				if (handle != null) {
					handle.paint(g);
				}

				// Paint Marquee
				if (marquee != null) {
					marquee.paint(graph, g);
				}
			//}
			paintOverlay(g);
			offGraphics.setClip(null);
		} else {
			// Not double buffered
			drawGraph(g, clipBounds);
		}
	}

	/**
	 * Hook method to paints the overlay
	 * 
	 * @param g
	 *            the graphics object to paint the overlay to
	 */
	protected void paintOverlay(Graphics g) {
	}

	/**
	 * Draws the graph to the specified graphics object within the specified
	 * clip bounds, if any
	 * 
	 * @param g
	 *            the graphics object to draw the graph to
	 * @param clipBounds
	 *            the bounds within graph cells must intersect to be redrawn
	 */
	public void drawGraph(Graphics g, Rectangle2D clipBounds) {
//		if (g.getClip() != null && clipBounds != null && !(g.getClip().equals(clipBounds))) {
			g.setClip(clipBounds);
//		}
		Rectangle2D realClipBounds = null;
		if (clipBounds != null) {
			realClipBounds = graph.fromScreen(new Rectangle2D.Double(
					clipBounds.getX(), clipBounds.getY(), clipBounds
							.getWidth(), clipBounds.getHeight()));
		}
		// Paint Background (Typically Grid)
		paintBackground(g);

		Graphics2D g2 = (Graphics2D) g;
		// Remember current affine transform
		AffineTransform at = g2.getTransform();

		// Use anti aliasing
		if (graph.isAntiAliased())
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);

		// Use Swing's scaling
		double scale = graph.getScale();
		g2.scale(scale, scale);

		// Paint cells
		paintCells(g, realClipBounds);

		// Reset affine transform and antialias
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_OFF);

		// Paint Foreground (Typically Ports)
		if (!graph.isPortsScaled())
			g2.setTransform(at);
		paintForeground(g);
		g2.setTransform(at);

		// Paint Handle
		if (handle != null) {
			handle.paint(g);
		}

		// Paint Marquee
		if (marquee != null) {
			marquee.paint(graph, g);
		}

		// Empty out the renderer pane, allowing renderers to be gc'ed.
		if (rendererPane != null) {
			rendererPane.removeAll();
		}
	}

	/**
	 * Hook method to allow subclassers to alter just the cell painting
	 * functionality
	 * @param g the graphics object to paint to
	 * @param realClipBounds the bounds of the region being repainted
	 */
	protected void paintCells(Graphics g, Rectangle2D realClipBounds) {
		CellView[] views = graphLayoutCache.getRoots();
		for (int i = 0; i < views.length; i++) {
			Rectangle2D bounds = views[i].getBounds();
			if (bounds != null) {
				if (realClipBounds == null) {
					paintCell(g, views[i], bounds, false);
				} else if (bounds.intersects(realClipBounds)) {
					paintCell(g, views[i], bounds, false);
				}
			}
		}
	}

	/**
	 * Paints the renderer of <code>view</code> to <code>g</code> at
	 * <code>bounds</code>. Recursive implementation that paints the children
	 * first.
	 * <p>
	 * The reciever should NOT modify <code>clipBounds</code>, or
	 * <code>insets</code>. The <code>preview</code> flag is passed to the
	 * renderer, and is not used here.
	 */
	public void paintCell(Graphics g, CellView view, Rectangle2D bounds,
			boolean preview) {
		// First Paint View
		if (view != null && bounds != null) {
			boolean bfocus = (view == this.focus);
			boolean sel = graph.isCellSelected(view.getCell());
			Component component = view.getRendererComponent(graph, sel, bfocus,
					preview);
			rendererPane.paintComponent(g, component, graph, (int) bounds
					.getX(), (int) bounds.getY(), (int) bounds.getWidth(),
					(int) bounds.getHeight(), true);
		}
		// Then Paint Children
		if (!view.isLeaf()) {
			CellView[] children = view.getChildViews();
			for (int i = 0; i < children.length; i++)
				paintCell(g, children[i], children[i].getBounds(), preview);
		}
	}

	//
	// Background
	//

	/**
	 * Paint the background of this graph. Calls paintGrid.
	 */
	protected void paintBackground(Graphics g) {
		Rectangle clip = g.getClipBounds();
		paintBackgroundImage(g, clip);
		if (graph.isGridVisible()) {
			paintGrid(graph.getGridSize(), g, clip);
		}
	}

	/**
	 * Hook for subclassers to paint the background image.
	 * 
	 * @param g
	 *            The graphics object to paint the image on.
	 * @param clip
	 *            The clipping region to draw into
	 */
	protected void paintBackgroundImage(Graphics g, Rectangle clip) {
		Component component = graph.getBackgroundComponent();
		if (component != null) {
			paintBackgroundComponent(g, component, clip);
		}
		ImageIcon icon = graph.getBackgroundImage();
		if (icon == null) {
			return;
		}
		Image backgroundImage = icon.getImage();
		if (backgroundImage == null) {
			return;
		}
		Graphics2D g2 = (Graphics2D) g;
		AffineTransform transform = null;
		if (graph.isBackgroundScaled()) {
			transform = g2.getTransform();
			g2.scale(graph.getScale(), graph.getScale());
		}
		g2.drawImage(backgroundImage, 0, 0, graph);
		if (transform != null) {
			g2.setTransform(transform);
		}
	}

	/**
	 * Requests that the component responsible for painting the background paint
	 * itself
	 * 
	 * @param g
	 *            The graphics object to paint the image on.
	 * @param component
	 *            the component to be painted onto the background image
	 */
	protected void paintBackgroundComponent(Graphics g, Component component) {
		try {
			g.setPaintMode();
			Dimension dim = component.getPreferredSize();
			rendererPane.paintComponent(g, component, graph, 0, 0, (int) dim
					.getWidth(), (int) dim.getHeight(), true);
		} catch (Exception e) {
		} catch (Error e) {
		}
	}
	
	/**
	 * Requests that the component responsible for painting the background paint
	 * itself
	 * 
	 * @param g
	 *            The graphics object to paint the image on.
	 * @param component
	 *            the component to be painted onto the background image
	 * @param clip
	 *            The clipping region to draw into
	 */
	protected void paintBackgroundComponent(Graphics g, Component component,
			Rectangle clip) {
		try {
			g.setPaintMode();
			Dimension dim = component.getPreferredSize();
			rendererPane.paintComponent(g, component, graph, 0, 0, (int) dim
					.getWidth(), (int) dim.getHeight(), true);
		} catch (Exception e) {
		} catch (Error e) {
		}
	}

	/**
	 * Paint the grid.
	 */
	protected void paintGrid(double gs, Graphics g, Rectangle2D clipBounds) {
		if (clipBounds == null) {
			Rectangle2D graphBounds = graph.getBounds();
			clipBounds = new Rectangle2D.Double(0, 0, graphBounds.getWidth(), graphBounds.getHeight());
		}
		double xl = clipBounds.getX();
		double yt = clipBounds.getY();
		double xr = xl + clipBounds.getWidth();
		double yb = yt + clipBounds.getHeight();
		double sgs = Math.max(2, gs * graph.getScale());

		int xs = (int) (Math.floor(xl / sgs) * sgs);
		int xe = (int) (Math.ceil(xr / sgs) * sgs);
		int ys = (int) (Math.floor(yt / sgs) * sgs);
		int ye = (int) (Math.ceil(yb / sgs) * sgs);

		g.setColor(graph.getGridColor());

		switch (graph.getGridMode()) {

		case JGraph.CROSS_GRID_MODE: {

			int cs = (sgs > 16.0) ? 2 : ((sgs < 8.0) ? 0 : 1);

			for (double x = xs; x <= xe; x += sgs) {
				for (double y = ys; y <= ye; y += sgs) {
					int ix = (int) Math.round(x);
					int iy = (int) Math.round(y);

					g.drawLine(ix - cs, iy, ix + cs, iy);
					g.drawLine(ix, iy - cs, ix, iy + cs);

				}
			}

		}
			break;

		case JGraph.LINE_GRID_MODE: {

			xe += (int) Math.ceil(sgs);
			ye += (int) Math.ceil(sgs);

			for (double x = xs; x <= xe; x += sgs) {

				int ix = (int) Math.round(x);

				g.drawLine(ix, ys, ix, ye);

			}

			for (double y = ys; y <= ye; y += sgs) {

				int iy = (int) Math.round(y);

				g.drawLine(xs, iy, xe, iy);

			}

		}
			break;

		case JGraph.DOT_GRID_MODE:
		default:
			for (double x = xs; x <= xe; x += sgs) {
				for (double y = ys; y <= ye; y += sgs) {

					int ix = (int) Math.round(x);
					int iy = (int) Math.round(y);

					g.drawLine(ix, iy, ix, iy);

				}
			}
			break;

		}

	}

	//
	// Foreground
	//

	/**
	 * Paint the foreground of this graph. Calls paintPorts.
	 */
	protected void paintForeground(Graphics g) {
		if (graph.isPortsVisible() && graph.isPortsOnTop())
			paintPorts(g, graphLayoutCache.getPorts());
	}

	/**
	 * Paint <code>ports</code>.
	 */
	public void paintPorts(Graphics g, CellView[] ports) {
		if (ports != null) {
			Rectangle r = g.getClipBounds();
			for (int i = 0; i < ports.length; i++) {
				if (ports[i] != null) {
					Rectangle2D tmp = ports[i].getBounds();
					Rectangle2D bounds = new Rectangle2D.Double(tmp.getX(), tmp
							.getY(), tmp.getWidth(), tmp.getHeight());
					Point2D center = new Point2D.Double(bounds.getCenterX(),
							bounds.getCenterY());
					if (!graph.isPortsScaled())
						center = graph.toScreen(center);
					bounds.setFrame(center.getX() - bounds.getWidth() / 2,
							center.getY() - bounds.getHeight() / 2, bounds
									.getWidth(), bounds.getHeight());
					if (r == null || bounds.intersects(r))
						paintCell(g, ports[i], bounds, false);
				}
			}
		}
	}

	//
	// Various local methods
	//

	/**
	 * Update the handle using createHandle.
	 */
	public void updateHandle() {
		if (graphLayoutCache != null) {
			Object[] cells = graphLayoutCache.getVisibleCells(graph
					.getSelectionCells());
			if (cells != null && cells.length > 0)
				handle = createHandle(createContext(graph, cells));
			else
				handle = null;
		}
	}

	protected GraphContext createContext(JGraph graph, Object[] cells) {
		return new GraphContext(graph, cells);
	}

	/**
	 * Constructs the "root handle" for <code>context</code>.
	 * 
	 * @param context
	 *            reference to the context of the current selection.
	 */
	public CellHandle createHandle(GraphContext context) {
		if (context != null && !context.isEmpty() && graph.isEnabled()) {
			try {
				return new RootHandle(context);
			} catch (HeadlessException e) {
				// Assume because of running on a server
			} catch (RuntimeException e) {
				throw e;
			}
		}
		return null;
	}

	/**
	 * Messages the Graph with <code>graphDidChange</code>.
	 */
	public void updateSize() {
		validCachedPreferredSize = false;
		graph.graphDidChange();
		updateHandle();
	}

	/**
	 * Updates the <code>preferredSize</code> instance variable, which is
	 * returned from <code>getPreferredSize()</code>.
	 */
	protected void updateCachedPreferredSize() {
		// FIXME: Renderer for the views might have an old state
		Rectangle2D size = AbstractCellView.getBounds(graphLayoutCache
				.getRoots());
		if (size == null)
			size = new Rectangle2D.Double();
		Point2D psize = new Point2D.Double(size.getX() + size.getWidth(), size
				.getY()
				+ size.getHeight());
		Dimension d = graph.getMinimumSize();
		Point2D min = (d != null) ? graph
				.toScreen(new Point(d.width, d.height)) : new Point(0, 0);
		Point2D scaled = graph.toScreen(psize);
		preferredSize = new Dimension(
				(int) Math.max(min.getX(), scaled.getX()), (int) Math.max(min
						.getY(), scaled.getY()));
		// Allow for background image
		ImageIcon image = graph.getBackgroundImage();
		if (image != null) {
			int height = image.getIconHeight();
			int width = image.getIconWidth();
			Point2D imageSize = graph.toScreen(new Point(width, height));
			preferredSize = new Dimension((int) Math.max(preferredSize
					.getWidth(), imageSize.getX()), (int) Math.max(
					preferredSize.getHeight(), imageSize.getY()));
		}
		Insets in = graph.getInsets();
		if (in != null) {
			preferredSize.setSize(
					preferredSize.getWidth() + in.left + in.right,
					preferredSize.getHeight() + in.top + in.bottom);
		}
		validCachedPreferredSize = true;
	}

	/**
	 * Sets the preferred minimum size.
	 */
	public void setPreferredMinSize(Dimension newSize) {
		preferredMinSize = newSize;
	}

	/**
	 * Returns the minimum preferred size.
	 */
	public Dimension getPreferredMinSize() {
		if (preferredMinSize == null)
			return null;
		return new Dimension(preferredMinSize);
	}

	/**
	 * Returns the preferred size to properly display the graph.
	 */
	public Dimension getPreferredSize(JComponent c) {
		Dimension pSize = this.getPreferredMinSize();

		if (!validCachedPreferredSize)
			updateCachedPreferredSize();
		if (graph != null) {
			if (pSize != null)
				return new Dimension(
						Math.max(pSize.width, preferredSize.width), Math.max(
								pSize.height, preferredSize.height));
			return new Dimension(preferredSize.width, preferredSize.height);
		} else if (pSize != null)
			return pSize;
		else
			return new Dimension(0, 0);
	}

	/**
	 * Returns the minimum size for this component. Which will be the min
	 * preferred size or 0, 0.
	 */
	public Dimension getMinimumSize(JComponent c) {
		if (this.getPreferredMinSize() != null)
			return this.getPreferredMinSize();
		return new Dimension(0, 0);
	}

	/**
	 * Returns the maximum size for this component, which will be the preferred
	 * size if the instance is currently in a JGraph, or 0, 0.
	 */
	public Dimension getMaximumSize(JComponent c) {
		if (graph != null)
			return getPreferredSize(graph);
		if (this.getPreferredMinSize() != null)
			return this.getPreferredMinSize();
		return new Dimension(0, 0);
	}

	/**
	 * Messages to stop the editing session. If the UI the receiver is providing
	 * the look and feel for returns true from
	 * <code>getInvokesStopCellEditing</code>, stopCellEditing will invoked
	 * on the current editor. Then completeEditing will be messaged with false,
	 * true, false to cancel any lingering editing.
	 */
	protected void completeEditing() {
		/* If should invoke stopCellEditing, try that */
		if (graph.getInvokesStopCellEditing() && stopEditingInCompleteEditing
				&& editingComponent != null) {
			cellEditor.stopCellEditing();
		}
		/*
		 * Invoke cancelCellEditing, this will do nothing if stopCellEditing was
		 * succesful.
		 */
		completeEditing(false, true, false);
	}

	/**
	 * Stops the editing session. If messageStop is true the editor is messaged
	 * with stopEditing, if messageCancel is true the editor is messaged with
	 * cancelEditing. If messageGraph is true the graphModel is messaged with
	 * valueForCellChanged.
	 */
	protected void completeEditing(boolean messageStop, boolean messageCancel,
			boolean messageGraph) {
		if (stopEditingInCompleteEditing && editingComponent != null) {
			Component oldComponent = editingComponent;
			Object oldCell = editingCell;
			GraphCellEditor oldEditor = cellEditor;
			boolean requestFocus = (graph != null && (graph.hasFocus() || SwingUtilities
					.findFocusOwner(editingComponent) != null));
			editingCell = null;
			editingComponent = null;
			if (messageStop)
				oldEditor.stopCellEditing();
			else if (messageCancel)
				oldEditor.cancelCellEditing();
			graph.remove(oldComponent);
			if (requestFocus)
				graph.requestFocus();
			if (messageGraph) {
				Object newValue = oldEditor.getCellEditorValue();
				graphLayoutCache.valueForCellChanged(oldCell, newValue);
			}
			updateSize();
			// Remove Editor Listener
			if (oldEditor != null && cellEditorListener != null)
				oldEditor.removeCellEditorListener(cellEditorListener);
			cellEditor = null;
		}
	}

	/**
	 * Will start editing for cell if there is a cellEditor and shouldSelectCell
	 * returns true.
	 * <p>
	 * This assumes that cell is valid and visible.
	 */
	protected boolean startEditing(Object cell, MouseEvent event) {
		completeEditing();
		if (graph.isCellEditable(cell)) {
			CellView tmp = graphLayoutCache.getMapping(cell, false);
			cellEditor = tmp.getEditor();
			editingComponent = cellEditor.getGraphCellEditorComponent(graph,
					cell, graph.isCellSelected(cell));
			if (cellEditor.isCellEditable(event)) {
				Rectangle2D cellBounds = graph.getCellBounds(cell);

				editingCell = cell;

				Dimension2D editorSize = editingComponent.getPreferredSize();

				graph.add(editingComponent);
				Point2D p = getEditorLocation(cell, editorSize, graph
						.toScreen(new Point2D.Double(cellBounds.getX(),
								cellBounds.getY())));

				editingComponent.setBounds((int) p.getX(), (int) p.getY(),
						(int) editorSize.getWidth(), (int) editorSize
								.getHeight());
				editingCell = cell;
				editingComponent.validate();

				// Add Editor Listener
				if (cellEditorListener == null)
					cellEditorListener = createCellEditorListener();
				if (cellEditor != null && cellEditorListener != null)
					cellEditor.addCellEditorListener(cellEditorListener);
				Rectangle2D visRect = graph.getVisibleRect();
				graph.paintImmediately((int) p.getX(), (int) p.getY(),
						(int) (visRect.getWidth() + visRect.getX() - cellBounds
								.getX()), (int) editorSize.getHeight());
				if (cellEditor.shouldSelectCell(event)
						&& graph.isSelectionEnabled()) {
					stopEditingInCompleteEditing = false;
					try {
						graph.setSelectionCell(cell);
					} catch (Exception e) {
						System.err.println("Editing exception: " + e);
					}
					stopEditingInCompleteEditing = true;
				}

				if (event instanceof MouseEvent) {
					/*
					 * Find the component that will get forwarded all the mouse
					 * events until mouseReleased.
					 */
					Point componentPoint = SwingUtilities.convertPoint(graph,
							new Point(event.getX(), event.getY()),
							editingComponent);

					/*
					 * Create an instance of BasicTreeMouseListener to handle
					 * passing the mouse/motion events to the necessary
					 * component.
					 */
					// We really want similiar behavior to getMouseEventTarget,
					// but it is package private.
					Component activeComponent = SwingUtilities
							.getDeepestComponentAt(editingComponent,
									componentPoint.x, componentPoint.y);
					if (activeComponent != null) {
						new MouseInputHandler(graph, activeComponent, event);
					}
				}
				return true;
			} else
				editingComponent = null;
		}
		return false;
	}

	/**
	 * Subclassers may override this to provide a better location for the
	 * in-place editing of edges (if you do not inherit from the EdgeRenderer
	 * class).
	 */
	protected Point2D getEditorLocation(Object cell, Dimension2D editorSize,
			Point2D pt) {
		// Edges have different editor position and size
		CellView view = graphLayoutCache.getMapping(cell, false);
		if (view instanceof EdgeView) {
			EdgeView edgeView = (EdgeView) view;
			CellViewRenderer renderer = edgeView.getRenderer();
			if (renderer instanceof EdgeRenderer) {
				Point2D tmp = ((EdgeRenderer) renderer)
						.getLabelPosition(edgeView);
				if (tmp != null)
					pt = tmp;
				else
					pt = AbstractCellView.getCenterPoint(edgeView);
				pt.setLocation(Math.max(0, pt.getX() - editorSize.getWidth()
						/ 2), Math.max(0, pt.getY() - editorSize.getHeight()
						/ 2));
			}
			graph.toScreen(pt);
		}
		return pt;
	}

	/**
	 * Scroll the graph for an event at <code>p</code>.
	 */
	public static void autoscroll(JGraph graph, Point p) {
		Rectangle view = graph.getBounds();
		if (graph.getParent() instanceof JViewport)
			view = ((JViewport) graph.getParent()).getViewRect();
		if (view.contains(p)) {
			Point target = new Point(p);
			int dx = (int) (graph.getWidth() * SCROLLSTEP);
			int dy = (int) (graph.getHeight() * SCROLLSTEP);
			if (target.x - view.x < SCROLLBORDER)
				target.x -= dx;
			if (target.y - view.y < SCROLLBORDER)
				target.y -= dy;
			if (view.x + view.width - target.x < SCROLLBORDER)
				target.x += dx;
			if (view.y + view.height - target.y < SCROLLBORDER)
				target.y += dy;
			graph.scrollPointToVisible(target);
		}
	}

	/**
	 * Updates the preferred size when scrolling (if necessary).
	 */
	public class ComponentHandler extends ComponentAdapter implements
			ActionListener {
		/**
		 * Timer used when inside a scrollpane and the scrollbar is adjusting.
		 */
		protected Timer timer;

		/** ScrollBar that is being adjusted. */
		protected JScrollBar scrollBar;

		public void componentMoved(ComponentEvent e) {
			if (timer == null) {
				JScrollPane scrollPane = getScrollPane();

				if (scrollPane == null)
					updateSize();
				else {
					scrollBar = scrollPane.getVerticalScrollBar();
					if (scrollBar == null || !scrollBar.getValueIsAdjusting()) {
						// Try the horizontal scrollbar.
						if ((scrollBar = scrollPane.getHorizontalScrollBar()) != null
								&& scrollBar.getValueIsAdjusting())
							startTimer();
						else
							updateSize();
					} else
						startTimer();
				}
			}
		}

		/**
		 * Creates, if necessary, and starts a Timer to check if need to resize
		 * the bounds.
		 */
		protected void startTimer() {
			if (timer == null) {
				timer = new Timer(200, this);
				timer.setRepeats(true);
			}
			timer.start();
		}

		/**
		 * Returns the JScrollPane housing the JGraph, or null if one isn't
		 * found.
		 */
		protected JScrollPane getScrollPane() {
			Component c = graph.getParent();

			while (c != null && !(c instanceof JScrollPane))
				c = c.getParent();
			if (c instanceof JScrollPane)
				return (JScrollPane) c;
			return null;
		}

		/**
		 * Public as a result of Timer. If the scrollBar is null, or not
		 * adjusting, this stops the timer and updates the sizing.
		 */
		public void actionPerformed(ActionEvent ae) {
			if (scrollBar == null || !scrollBar.getValueIsAdjusting()) {
				if (timer != null)
					timer.stop();
				updateSize();
				timer = null;
				scrollBar = null;
			}
		}
	} // End of BasicGraphUI.ComponentHandler

	/**
	 * Listens for changes in the graph model and updates the view accordingly.
	 */
	public class GraphModelHandler implements GraphModelListener, Serializable {

		public void graphChanged(GraphModelEvent e) {
			Object[] removed = e.getChange().getRemoved();
			// Remove from selection & focus
			if (removed != null && removed.length > 0) {
				// Update Focus If Necessary
				if (focus != null) {
					Object focusedCell = focus.getCell();
					for (int i = 0; i < removed.length; i++) {
						if (removed[i] == focusedCell) {
							lastFocus = focus;
							focus = null;
							break;
						}
					}
				}
				// Remove from selection
				graph.getSelectionModel().removeSelectionCells(removed);
			}
			Rectangle2D oldDirty = null;
			Rectangle2D dirtyRegion = e.getChange().getDirtyRegion();
			if (dirtyRegion == null) {
				oldDirty = graph.getClipRectangle(e.getChange());
			}
			
			if (graphLayoutCache != null) {
				graphLayoutCache.graphChanged(e.getChange());
			}
			// Get arrays
			Object[] inserted = e.getChange().getInserted();
			Object[] changed = e.getChange().getChanged();
			// Insert
			if (inserted != null && inserted.length > 0) {
				// Update focus to first inserted cell
				for (int i = 0; i < inserted.length; i++)
					graph.updateAutoSize(graphLayoutCache.getMapping(
							inserted[i], false));
			}
			// Change (update size)
			if (changed != null && changed.length > 0) {
				for (int i = 0; i < changed.length; i++)
					graph.updateAutoSize(graphLayoutCache.getMapping(
							changed[i], false));
			}
			if (dirtyRegion == null) {
				Rectangle2D newDirtyRegion = graph.getClipRectangle(e.getChange());
				dirtyRegion = RectUtils.union(oldDirty, newDirtyRegion);
				e.getChange().setDirtyRegion(dirtyRegion);
			}
			
			if (dirtyRegion != null) {
				graph.addOffscreenDirty(dirtyRegion);
			}
			// Select if not partial
			if (!graphLayoutCache.isPartial()
					&& graphLayoutCache.isSelectsAllInsertedCells()
					&& graph.isEnabled()) {
				Object[] roots = DefaultGraphModel.getRoots(graphModel,
						inserted);
				if (roots != null && roots.length > 0) {
					lastFocus = focus;
					focus = graphLayoutCache.getMapping(roots[0], false);
					graph.setSelectionCells(roots);
				}
			}
			updateSize();
		}
		
	} // End of BasicGraphUI.GraphModelHandler

	/**
	 * Listens for changes in the graph view and updates the size accordingly.
	 */
	public class GraphLayoutCacheHandler implements GraphLayoutCacheListener,
			Serializable {

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.jgraph.event.GraphLayoutCacheListener#graphLayoutCacheChanged(org.jgraph.event.GraphLayoutCacheEvent)
		 */
		public void graphLayoutCacheChanged(GraphLayoutCacheEvent e) {
			Object[] changed = e.getChange().getChanged();
			if (changed != null && changed.length > 0) {
				for (int i = 0; i < changed.length; i++) {
					graph.updateAutoSize(graphLayoutCache.getMapping(
							changed[i], false));
				}
			}
			Rectangle2D oldDirtyRegion = e.getChange().getDirtyRegion();
			graph.addOffscreenDirty(oldDirtyRegion);
			Rectangle2D newDirtyRegion = graph.getClipRectangle(e.getChange());
			graph.addOffscreenDirty(newDirtyRegion);
			Object[] inserted = e.getChange().getInserted();
			if (inserted != null
					&& inserted.length > 0
					&& graphLayoutCache.isSelectsLocalInsertedCells()
					&& !(graphLayoutCache.isSelectsAllInsertedCells() && !graphLayoutCache
							.isPartial()) && graph.isEnabled()) {
				Object[] roots = DefaultGraphModel.getRoots(graphModel,
						inserted);
				if (roots != null && roots.length > 0) {
					lastFocus = focus;
					focus = graphLayoutCache.getMapping(roots[0], false);
					graph.setSelectionCells(roots);
				}
			}
			updateSize();
		}

	} // End of BasicGraphUI.GraphLayoutCacheHandler

	/**
	 * Listens for changes in the selection model and updates the display
	 * accordingly.
	 */
	public class GraphSelectionHandler implements GraphSelectionListener,
			Serializable {
		/**
		 * Messaged when the selection changes in the graph we're displaying
		 * for. Stops editing, updates handles and displays the changed cells.
		 */
		public void valueChanged(GraphSelectionEvent event) {
			// cancelEditing(graph);
			updateHandle();
			Object[] cells = event.getCells();
			if (cells != null && cells.length <= MAXCLIPCELLS) {
				Rectangle2D r = graph.toScreen(graph.getCellBounds(cells));

				// Includes dirty region of focused cell
				if (focus != null) {
					if (r != null)
						Rectangle2D.union(r, focus.getBounds(), r);
					else
						r = focus.getBounds();
				}

				// And last focused cell
				if (lastFocus != null) {
					if (r != null)
						Rectangle2D.union(r, lastFocus.getBounds(), r);
					else
						r = lastFocus.getBounds();
				}
				if (r != null) {
					Rectangle2D unscaledDirty = graph.fromScreen((Rectangle2D)r.clone());
					graph.addOffscreenDirty(unscaledDirty);
					int hsize = (int) graph.getHandleSize() + 1;
					updateHandle();
					Rectangle dirtyRegion = new Rectangle((int)(r.getX()
							- hsize), (int)(r.getY() - hsize),
							(int)(r.getWidth() + 2 * hsize), (int)(r.getHeight() + 2 * hsize));
					graph.repaint(dirtyRegion);
				}
			} else {
				Rectangle dirtyRegion = new Rectangle(graph.getSize());
				graph.addOffscreenDirty(dirtyRegion);
				graph.repaint();
			}
		}
	}

	/**
	 * Listener responsible for getting cell editing events and updating the
	 * graph accordingly.
	 */
	public class CellEditorHandler implements CellEditorListener, Serializable {
		/** Messaged when editing has stopped in the graph. */
		public void editingStopped(ChangeEvent e) {
			completeEditing(false, false, true);
		}

		/** Messaged when editing has been canceled in the graph. */
		public void editingCanceled(ChangeEvent e) {
			completeEditing(false, false, false);
		}
	} // BasicGraphUI.CellEditorHandler

	/**
	 * This is used to get mutliple key down events to appropriately generate
	 * events.
	 */
	public class KeyHandler extends KeyAdapter implements Serializable {
		/** Key code that is being generated for. */
		protected Action repeatKeyAction;

		/** Set to true while keyPressed is active. */
		protected boolean isKeyDown;

		public void keyPressed(KeyEvent e) {
			if (graph != null && graph.hasFocus() && graph.isEnabled()) {
				KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyCode(), e
						.getModifiers());

				if (graph.getConditionForKeyStroke(keyStroke) == JComponent.WHEN_FOCUSED) {
					ActionListener listener = graph
							.getActionForKeyStroke(keyStroke);

					if (listener instanceof Action)
						repeatKeyAction = (Action) listener;
					else
						repeatKeyAction = null;
				} else {
					repeatKeyAction = null;
					if (keyStroke.getKeyCode() == KeyEvent.VK_ESCAPE) {
						// System.out.println("Calling release on " + marquee);
						if (marquee != null)
							marquee.mouseReleased(null);
						if (mouseListener != null)
							mouseListener.mouseReleased(null);
						updateHandle();
						graph.repaint();
					}
				}
				if (isKeyDown && repeatKeyAction != null) {
					repeatKeyAction.actionPerformed(new ActionEvent(graph,
							ActionEvent.ACTION_PERFORMED, ""));
					e.consume();
				} else
					isKeyDown = true;
			}
		}

		public void keyReleased(KeyEvent e) {
			isKeyDown = false;
		}

	} // End of BasicGraphUI.KeyHandler

	/**
	 * TreeMouseListener is responsible for updating the selection based on
	 * mouse events.
	 */
	public class MouseHandler extends MouseAdapter implements
			MouseMotionListener, Serializable {

		/* The cell under the mousepointer. */
		protected CellView cell;

		/* The object that handles mouse operations. */
		protected Object handler;

		protected transient Cursor previousCursor = null;

		/**
		 * Invoked when a mouse button has been pressed on a component.
		 */
		public void mousePressed(MouseEvent e) {
			handler = null;
			if (!e.isConsumed() && graph.isEnabled()) {
				graph.requestFocus();
				int s = graph.getTolerance();
				Rectangle2D r = graph.fromScreen(new Rectangle2D.Double(e
						.getX()
						- s, e.getY() - s, 2 * s, 2 * s));
				lastFocus = focus;
				focus = (focus != null && focus.intersects(graph, r)) ? focus
						: null;
				cell = graph.getNextSelectableViewAt(focus, e.getX(), e.getY());
				if (focus == null)
					focus = cell;
				completeEditing();
				boolean isForceMarquee = isForceMarqueeEvent(e);
				boolean isEditable = graph.isGroupsEditable()
						|| (focus != null && focus.isLeaf());
				if (!isForceMarquee) {
					if (e.getClickCount() == graph.getEditClickCount()
							&& focus != null && isEditable
							&& focus.getParentView() == null
							&& graph.isCellEditable(focus.getCell())
							&& handleEditTrigger(cell.getCell(), e)) {
						e.consume();
						cell = null;
					} else if (!isToggleSelectionEvent(e)) {
						if (handle != null) {
							handle.mousePressed(e);
							handler = handle;
						}
						// Immediate Selection
						if (!e.isConsumed() && cell != null
								&& !graph.isCellSelected(cell.getCell())) {
							selectCellForEvent(cell.getCell(), e);
							focus = cell;
							if (handle != null) {
								handle.mousePressed(e);
								handler = handle;
							}
							e.consume();
							cell = null;
						}
					}
				}
				// Marquee Selection
				if (!e.isConsumed()
						&& marquee != null
						&& (!isToggleSelectionEvent(e) || focus == null || isForceMarquee)) {
					marquee.mousePressed(e);
					handler = marquee;
				}
			}
		}

		/**
		 * Handles edit trigger by starting the edit and return true if the
		 * editing has already started.
		 * 
		 * @param cell
		 *            the cell being edited
		 * @param e
		 *            the mouse event triggering the edit
		 * @return <code>true</code> if the editing has already started
		 */
		protected boolean handleEditTrigger(Object cell, MouseEvent e) {
			graph.scrollCellToVisible(cell);
			if (cell != null)
				startEditing(cell, e);
			return graph.isEditing();
		}

		public void mouseDragged(MouseEvent e) {
			autoscroll(graph, e.getPoint());
			if (graph.isEnabled()) {
				if (handler != null && handler == marquee)
					marquee.mouseDragged(e);
				else if (handler == null && !isEditing(graph) && focus != null) {
					if (!graph.isCellSelected(focus.getCell())) {
						selectCellForEvent(focus.getCell(), e);
						cell = null;
					}
					if (handle != null)
						handle.mousePressed(e);
					handler = handle;
				}
				if (handle != null && handler == handle)
					handle.mouseDragged(e);
			}
		}

		/**
		 * Invoked when the mouse pointer has been moved on a component (with no
		 * buttons down).
		 */
		public void mouseMoved(MouseEvent e) {
			if (previousCursor == null) {
				previousCursor = graph.getCursor();
			}
			if (graph != null && graph.isEnabled()) {
				if (marquee != null)
					marquee.mouseMoved(e);
				if (handle != null)
					handle.mouseMoved(e);
				if (!e.isConsumed() && previousCursor != null) {
					Cursor currentCursor = graph.getCursor();
					if (currentCursor != previousCursor) {
						graph.setCursor(previousCursor);
					}
					previousCursor = null;
				}
			}
		}

		// Event may be null when called to cancel the current operation.
		public void mouseReleased(MouseEvent e) {
			try {
				if (e != null && !e.isConsumed() && graph != null
						&& graph.isEnabled()) {
					if (handler == marquee && marquee != null)
						marquee.mouseReleased(e);
					else if (handler == handle && handle != null)
						handle.mouseReleased(e);
					if (isDescendant(cell, focus) && e.getModifiers() != 0) {
						// Do not switch to parent if Special Selection
						cell = focus;
					}
					if (!e.isConsumed() && cell != null) {
						Object tmp = cell.getCell();
						boolean wasSelected = graph.isCellSelected(tmp);
						if (!e.isPopupTrigger() || !wasSelected) {
							selectCellForEvent(tmp, e);
							focus = cell;
							postProcessSelection(e, tmp, wasSelected);
						}
					}
				}
			} finally {
				handler = null;
				cell = null;
			}
		}

		/**
		 * Invoked after a cell has been selected in the mouseReleased method.
		 * This can be used to do something interesting if the cell was already
		 * selected, in which case this implementation selects the parent.
		 * Override if you want different behaviour, such as start editing.
		 */
		protected void postProcessSelection(MouseEvent e, Object cell,
				boolean wasSelected) {
			if (wasSelected && graph.isCellSelected(cell)
					&& e.getModifiers() != 0) {
				Object parent = cell;
				Object nextParent = null;
				while (((nextParent = graphModel.getParent(parent)) != null)
						&& graphLayoutCache.isVisible(nextParent))
					parent = nextParent;
				selectCellForEvent(parent, e);
				lastFocus = focus;
				focus = graphLayoutCache.getMapping(parent, false);
			}
		}

		protected boolean isDescendant(CellView parentView, CellView childView) {
			if (parentView == null || childView == null) {
				return false;
			}

			Object parent = parentView.getCell();
			Object child = childView.getCell();
			Object ancestor = child;

			do {
				if (ancestor == parent)
					return true;
			} while ((ancestor = graphModel.getParent(ancestor)) != null);

			return false;
		}

	} // End of BasicGraphUI.MouseHandler

	public class RootHandle implements CellHandle, Serializable {
		// x and y offset from the mouse press event to the left/top corner of a
		// view that is returned by a findViewForPoint().
		// These are used only when the isSnapSelectedView mode is enabled.
		protected transient double _mouseToViewDelta_x = 0;

		protected transient double _mouseToViewDelta_y = 0;

		protected transient boolean firstDrag = true;

		/* Temporary views for the cells. */
		protected transient CellView[] views;

		protected transient CellView[] contextViews;

		protected transient CellView[] portViews;

		protected transient CellView targetGroup, ignoreTargetGroup;

		/* Bounds of the cells. Non-null if too many cells. */
		protected transient Rectangle2D cachedBounds;

		/* Initial top left corner of the selection */
		protected transient Point2D initialLocation;

		/* Child handles. Null if too many handles. */
		protected transient CellHandle[] handles;

		/* The point where the mouse was pressed. */
		protected transient Point2D start = null, last, snapStart, snapLast;

		/** Reference to graph off screen graphics */
		protected transient Graphics offgraphics;

		/**
		 * Indicates whether this handle is currently moving cells. Start may be
		 * non-null and isMoving false while the minimum movement has not been
		 * reached.
		 */
		protected boolean isMoving = false;

		/**
		 * Indicates whether this handle has started drag and drop. Note:
		 * isDragging => isMoving.
		 */
		protected boolean isDragging = false;

		/** The handle that consumed the last mousePressedEvent. Initially null. */
		protected transient CellHandle activeHandle = null;

		/* The current selection context, responsible for cloning the cells. */
		protected transient GraphContext context;

		/*
		 * True after the graph was repainted to block xor-ed painting of
		 * background.
		 */
		protected boolean isContextVisible = true;

		protected boolean blockPaint = false;

		protected Point2D current;

		/* Defines the Disconnection if DisconnectOnMove is True */
		protected transient ConnectionSet disconnect = null;

		/**
		 * Creates a root handle which contains handles for the given cells. The
		 * root handle and all its childs point to the specified JGraph
		 * instance. The root handle is responsible for dragging the selection.
		 */
		public RootHandle(GraphContext ctx) {
			this.context = ctx;
			if (!ctx.isEmpty()) {
				// Temporary cells
				views = ctx.createTemporaryCellViews();
				Rectangle2D tmpBounds = graph.toScreen(graph.getCellBounds(ctx
						.getCells()));
				if (ctx.getDescendantCount() < MAXCELLS) {
					contextViews = ctx.createTemporaryContextViews();
					initialLocation = graph.toScreen(getInitialLocation(ctx
							.getCells()));
				} else
					cachedBounds = tmpBounds;
				if (initialLocation == null && tmpBounds != null) {
					initialLocation = new Point2D.Double(tmpBounds.getX(),
							tmpBounds.getY());
				}
				// Sub-Handles
				Object[] cells = ctx.getCells();
				if (cells.length < MAXHANDLES) {
					handles = new CellHandle[views.length];
					for (int i = 0; i < views.length; i++)
						handles[i] = views[i].getHandle(ctx);
					// PortView Preview
					portViews = ctx.createTemporaryPortViews();
				}
			}
		}

		/**
		 * Returns the initial location, which is the top left corner of the
		 * selection, ignoring all connected endpoints of edges.
		 */
		protected Point2D getInitialLocation(Object[] cells) {
			if (cells != null && cells.length > 0) {
				Rectangle2D ret = null;
				for (int i = 0; i < cells.length; i++) {
					if (graphModel != null && graphModel.isEdge(cells[i])) {
						CellView cellView = graphLayoutCache.getMapping(
								cells[i], false);
						if (cellView instanceof EdgeView) {
							EdgeView edgeView = (EdgeView) cellView;
							if (edgeView.getSource() == null) {
								Point2D pt = edgeView.getPoint(0);
								if (pt != null) {
									if (ret == null)
										ret = new Rectangle2D.Double(pt.getX(),
												pt.getY(), 0, 0);
									else
										Rectangle2D.union(ret,
												new Rectangle2D.Double(pt
														.getX(), pt.getY(), 0,
														0), ret);
								}
							}
							if (edgeView.getTarget() == null) {
								Point2D pt = edgeView.getPoint(edgeView
										.getPointCount() - 1);
								if (pt != null) {
									if (ret == null)
										ret = new Rectangle2D.Double(pt.getX(),
												pt.getY(), 0, 0);
									else
										Rectangle2D.union(ret,
												new Rectangle2D.Double(pt
														.getX(), pt.getY(), 0,
														0), ret);
								}
							}
						}
					} else {
						Rectangle2D r = graph.getCellBounds(cells[i]);
						if (r != null) {
							if (ret == null)
								ret = (Rectangle2D) r.clone();
							Rectangle2D.union(ret, r, ret);
						}
					}
				}
				if (ret != null)
					return new Point2D.Double(ret.getX(), ret.getY());
			}
			return null;
		}

		/* Returns the context of this root handle. */
		public GraphContext getContext() {
			return context;
		}

		/* Paint the handles. Use overlay to paint the current state. */
		public void paint(Graphics g) {
			if (handles != null && handles.length < MAXHANDLES)
				for (int i = 0; i < handles.length; i++)
					if (handles[i] != null)
						handles[i].paint(g);
			blockPaint = true;
			if (!graph.isXorEnabled() && current != null) {
				double dx = current.getX() - start.getX();
				double dy = current.getY() - start.getY();
				if (dx != 0 || dy != 0) {
					overlay(g);
				}
			} else {
				blockPaint = true;
			}
		}

		public void overlay(Graphics g) {
			if (isDragging && !DNDPREVIEW) // BUG IN 1.4.0 (FREEZE)
				return;
			if (cachedBounds != null) { // Paint Cached Bounds
				g.setColor(Color.black);
				g.drawRect((int) cachedBounds.getX(),
						(int) cachedBounds.getY(), (int) cachedBounds
								.getWidth() - 2,
						(int) cachedBounds.getHeight() - 2);

			} else {
				Graphics2D g2 = (Graphics2D) g;
				AffineTransform oldTransform = g2.getTransform();
				g2.scale(graph.getScale(), graph.getScale());
				if (views != null) { // Paint Temporary Views
					for (int i = 0; i < views.length; i++)
						paintCell(g, views[i], views[i].getBounds(), true);
				}
				// Paint temporary context
				if (contextViews != null && isContextVisible) {
					for (int i = 0; i < contextViews.length; i++) {
						paintCell(g, contextViews[i], contextViews[i]
								.getBounds(), true);
					}
				}
				if (!graph.isPortsScaled())
					g2.setTransform(oldTransform);
				if (portViews != null && graph.isPortsVisible())
					paintPorts(g, portViews);
				g2.setTransform(oldTransform);
			}

			// Paints the target group to move into
			if (targetGroup != null) {
				Rectangle2D b = graph.toScreen((Rectangle2D) targetGroup
						.getBounds().clone());
				g.setColor(graph.getHandleColor());
				g.fillRect((int) b.getX() - 1, (int) b.getY() - 1, (int) b
						.getWidth() + 2, (int) b.getHeight() + 2);
				g.setColor(graph.getMarqueeColor());
				g.draw3DRect((int) b.getX() - 2, (int) b.getY() - 2, (int) b
						.getWidth() + 3, (int) b.getHeight() + 3, true);
			}
		}

		/**
		 * Invoked when the mouse pointer has been moved on a component (with no
		 * buttons down).
		 */
		public void mouseMoved(MouseEvent event) {
			if (!event.isConsumed() && handles != null) {
				for (int i = handles.length - 1; i >= 0 && !event.isConsumed(); i--)
					if (handles[i] != null)
						handles[i].mouseMoved(event);
			}
		}

		public void mousePressed(MouseEvent event) {
			if (!event.isConsumed() && graph.isMoveable()) {
				if (handles != null) { // Find Handle
					for (int i = handles.length - 1; i >= 0; i--) {
						if (handles[i] != null) {
							handles[i].mousePressed(event);
							if (event.isConsumed()) {
								activeHandle = handles[i];
								return;
							}
						}
					}
				}
				if (views != null) { // Start Move if over cell
					Point2D screenPoint = event.getPoint();
					Point2D pt = graph
							.fromScreen((Point2D) screenPoint.clone());
					CellView view = findViewForPoint(pt);
					if (view != null) {
						if (snapSelectedView) {
							Rectangle2D bounds = view.getBounds();
							start = graph.toScreen(new Point2D.Double(bounds
									.getX(), bounds.getY()));
							snapStart = graph.snap((Point2D) start.clone());
							_mouseToViewDelta_x = screenPoint.getX()
									- start.getX();
							_mouseToViewDelta_y = screenPoint.getY()
									- start.getY();
						} else { // this is the original RootHandle's mode.
							snapStart = graph.snap((Point2D) screenPoint
									.clone());
							_mouseToViewDelta_x = snapStart.getX()
									- screenPoint.getX();
							_mouseToViewDelta_y = snapStart.getY()
									- screenPoint.getY();
							start = (Point2D) snapStart.clone();
						}
						last = (Point2D) start.clone();
						snapLast = (Point2D) snapStart.clone();
						isContextVisible = contextViews != null
								&& contextViews.length < MAXCELLS
								&& (!event.isControlDown() || !graph
										.isCloneable());
						event.consume();
					}
				}
				// Analyze for common parent
				if (graph.isMoveIntoGroups() || graph.isMoveOutOfGroups()) {
					Object[] cells = context.getCells();
					Object ignoreGroup = graph.getModel().getParent(cells[0]);
					for (int i = 1; i < cells.length; i++) {
						Object tmp = graph.getModel().getParent(cells[i]);
						if (ignoreGroup != tmp) {
							ignoreGroup = null;
							break;
						}
					}
					if (ignoreGroup != null)
						ignoreTargetGroup = graph.getGraphLayoutCache()
								.getMapping(ignoreGroup, false);
				}
			}
		}

		/**
		 * Hook for subclassers to return a different view for a mouse click at
		 * <code>pt</code>. For example, this can be used to return a leaf
		 * cell instead of a group.
		 */
		protected CellView findViewForPoint(Point2D pt) {
			double snap = graph.getTolerance();
			Rectangle2D r = new Rectangle2D.Double(pt.getX() - snap, pt.getY()
					- snap, 2 * snap, 2 * snap);
			for (int i = 0; i < views.length; i++)
				if (views[i].intersects(graph, r))
					return views[i];
			return null;
		}

		/**
		 * Used for move into group to find the target group.
		 */
		protected CellView findUnselectedInnermostGroup(double x, double y) {
			Object[] cells = graph.getDescendants(graph.getRoots());
			for (int i = cells.length - 1; i >= 0; i--) {
				CellView view = graph.getGraphLayoutCache().getMapping(
						cells[i], false);
				if (view != null && !view.isLeaf()
						&& !context.contains(view.getCell())
						&& view.getBounds().contains(x, y))
					return view;
			}
			return null;
		}

		protected void startDragging(MouseEvent event) {
			isDragging = true;
			if (graph.isDragEnabled()) {
				int action = (event.isControlDown() && graph.isCloneable()) ? TransferHandler.COPY
						: TransferHandler.MOVE;
				TransferHandler th = graph.getTransferHandler();
				setInsertionLocation(event.getPoint());
				try {
					th.exportAsDrag(graph, event, action);
				} catch (Exception ex) {
					// Ignore
				}
			}
		}

		/**
		 * @return Returns the parent graph scrollpane for the specified graph.
		 */
		public Component getFirstOpaqueParent(Component component) {
			if (component != null) {
				Component parent = component;
				while (parent != null) {
					if (parent.isOpaque() && !(parent instanceof JViewport))
						return parent;
					parent = parent.getParent();
				}
			}
			return component;
		}

		protected void initOffscreen() {
			if (!graph.isXorEnabled()) {
				return;
			}
			try {
				offgraphics = graph.getOffgraphics();
			} catch (Exception e) {
				offgraphics = null;
			} catch (Error e) {
				offgraphics = null;
			}
		}

		/** Process mouse dragged event. */
		public void mouseDragged(MouseEvent event) {
			boolean constrained = isConstrainedMoveEvent(event);
			boolean spread = false;
			Rectangle2D dirty = null;
			if (firstDrag && graph.isDoubleBuffered() && cachedBounds == null) {
				initOffscreen();
				firstDrag = false;
			}
			if (event != null && !event.isConsumed()) {
				if (activeHandle != null) // Paint Active Handle
					activeHandle.mouseDragged(event);
				// Invoke Mouse Dragged
				else if (start != null) { // Move Cells
					Graphics g = (offgraphics != null) ? offgraphics : graph
							.getGraphics();
					Point ep = event.getPoint();
					Point2D point = new Point2D.Double(ep.getX()
							- _mouseToViewDelta_x, ep.getY()
							- _mouseToViewDelta_y);
					Point2D snapCurrent = graph.snap(point);
					current = snapCurrent;
					int thresh = graph.getMinimumMove();
					double dx = current.getX() - start.getX();
					double dy = current.getY() - start.getY();
					if (isMoving || Math.abs(dx) > thresh
							|| Math.abs(dy) > thresh) {
						boolean overlayed = false;
						isMoving = true;
						if (disconnect == null && graph.isDisconnectOnMove())
							disconnect = context.disconnect(graphLayoutCache
									.getAllDescendants(views));
						// Constrained movement
						double totDx = current.getX() - start.getX();
						double totDy = current.getY() - start.getY();
						dx = current.getX() - last.getX();
						dy = current.getY() - last.getY();
						Point2D constrainedPosition = constrainDrag(event,
								totDx, totDy, dx, dy);
						if (constrainedPosition != null) {
							dx = constrainedPosition.getX();
							dy = constrainedPosition.getY();
						}
						double scale = graph.getScale();
						dx = dx / scale;
						dy = dy / scale;
						// Start Drag and Drop
						if (graph.isDragEnabled() && !isDragging)
							startDragging(event);
						if (dx != 0 || dy != 0) {
							if (offgraphics != null || !graph.isXorEnabled()) {
								dirty = graph.toScreen(AbstractCellView
										.getBounds(views));
								Rectangle2D t = graph.toScreen(AbstractCellView
										.getBounds(contextViews));
								if (t != null)
									dirty.add(t);
							}
							if (graph.isXorEnabled()) {
								g.setColor(graph.getForeground());

								// use 'darker' to force XOR to distinguish
								// between
								// existing background elements during drag
								// http://sourceforge.net/tracker/index.php?func=detail&aid=677743&group_id=43118&atid=435210
								g.setXORMode(graph.getBackground().darker());
							}
							if (!snapLast.equals(snapStart)
									&& (offgraphics != null || !blockPaint)) {
								if (graph.isXorEnabled()) {
									overlay(g);
								}
								overlayed = true;
							}
							isContextVisible = (!event.isControlDown() || !graph
									.isCloneable())
									&& contextViews != null
									&& (contextViews.length < MAXCELLS);
							blockPaint = false;
							if (constrained && cachedBounds == null) {
								// Reset Initial Positions
								CellView[] all = graphLayoutCache
										.getAllDescendants(views);
								for (int i = 0; i < all.length; i++) {
									CellView orig = graphLayoutCache
											.getMapping(all[i].getCell(), false);
									AttributeMap attr = orig.getAllAttributes();
									all[i].changeAttributes(graph
											.getGraphLayoutCache(),
											(AttributeMap) attr.clone());
									all[i].refresh(graph.getGraphLayoutCache(),
											context, false);
								}
							}
							if (cachedBounds != null) {
								if (dirty != null) {
									dirty.add(cachedBounds);
								}
								cachedBounds.setFrame(cachedBounds.getX() + dx
										* scale, cachedBounds.getY() + dy
										* scale, cachedBounds.getWidth(),
										cachedBounds.getHeight());
								if (dirty != null) {
									dirty.add(cachedBounds);
								}
							} else {
								// Translate
								GraphLayoutCache.translateViews(views, dx, dy);
								if (views != null)
									graphLayoutCache.update(views);
								if (contextViews != null)
									graphLayoutCache.update(contextViews);
							}
							// Change preferred size of graph
							if (graph.isAutoResizeGraph()
									&& (event.getX() > graph.getWidth()
											- SCROLLBORDER || event.getY() > graph
											.getHeight()
											- SCROLLBORDER)) {

								int SPREADSTEP = 25;
								Rectangle view = null;
								if (graph.getParent() instanceof JViewport)
									view = ((JViewport) graph.getParent())
											.getViewRect();
								if (view != null) {
									if (view.contains(event.getPoint())) {
										if (view.x + view.width
												- event.getPoint().x < SCROLLBORDER) {
											preferredSize.width = Math.max(
													preferredSize.width,
													(int) view.getWidth())
													+ SPREADSTEP;
											spread = true;
										}
										if (view.y + view.height
												- event.getPoint().y < SCROLLBORDER) {
											preferredSize.height = Math.max(
													preferredSize.height,
													(int) view.getHeight())
													+ SPREADSTEP;
											spread = true;
										}
										if (spread) {
											graph.revalidate();
											autoscroll(graph, event.getPoint());
											if (graph.isDoubleBuffered())
												initOffscreen();
										}
									}
								}
							}

							// Move into groups
							Rectangle2D ignoredRegion = (ignoreTargetGroup != null) ? (Rectangle2D) ignoreTargetGroup
									.getBounds().clone()
									: null;
							if (targetGroup != null) {
								Rectangle2D tmp = graph
										.toScreen((Rectangle2D) targetGroup
												.getBounds().clone());
								if (dirty != null)
									dirty.add(tmp);
								else
									dirty = tmp;
							}
							targetGroup = null;
							if (graph.isMoveIntoGroups()
									&& (ignoredRegion == null || !ignoredRegion
											.intersects(AbstractCellView
													.getBounds(views)))) {
								targetGroup = (event.isControlDown()) ? null
										: findUnselectedInnermostGroup(
												snapCurrent.getX() / scale,
												snapCurrent.getY() / scale);
								if (targetGroup == ignoreTargetGroup)
									targetGroup = null;
							}
							if (!snapCurrent.equals(snapStart)
									&& (offgraphics != null || !blockPaint)
									&& !spread) {
								if (graph.isXorEnabled()) {
									overlay(g);
								}
								overlayed = true;
							}
							if (constrained)
								last = (Point2D) start.clone();
							last.setLocation(last.getX() + dx * scale, last
									.getY()
									+ dy * scale);
							// It is better to translate <code>last<code> by a
							// scaled dx/dy
							// instead of making it to be the
							// <code>current<code> (as in prev version),
							// so that the view would be catching up with a
							// mouse pointer
							snapLast = snapCurrent;
							if (overlayed
									&& (offgraphics != null || !graph
											.isXorEnabled())) {
								if (dirty == null) {
									dirty = new Rectangle2D.Double();
								}
								dirty.add(graph.toScreen(AbstractCellView
										.getBounds(views)));
								Rectangle2D t = graph.toScreen(AbstractCellView
										.getBounds(contextViews));
								if (t != null)
									dirty.add(t);
								// TODO: Should use real ports if portsVisible
								// and check if ports are scaled
								int border = PortView.SIZE + 4;
								if (graph.isPortsScaled())
									border = (int) (graph.getScale() * border);
								int border2 = border / 2;
								dirty.setFrame(dirty.getX() - border2, dirty
										.getY()
										- border2, dirty.getWidth() + border,
										dirty.getHeight() + border);
								double sx1 = Math.max(0, dirty.getX());
								double sy1 = Math.max(0, dirty.getY());
								double sx2 = sx1 + dirty.getWidth();
								double sy2 = sy1 + dirty.getHeight();
								if (isDragging && !DNDPREVIEW) // BUG IN 1.4.0
									// (FREEZE)
									return;
								if (offgraphics != null) {
									graph.drawImage((int) sx1, (int) sy1,
											(int) sx2, (int) sy2, (int) sx1,
											(int) sy1, (int) sx2, (int) sy2);
								} else {
									graph.repaint((int) dirty.getX(),
											(int) dirty.getY(), (int) dirty
													.getWidth() + 1,
											(int) dirty.getHeight() + 1);
								}
							}
						}
					} // end if (isMoving or ...)
				} // end if (start != null)
			} else if (event == null)
				graph.repaint();
		}

		/**
		 * Hook method to constrain a mouse drag
		 * 
		 * @param event
		 * @param totDx
		 * @param totDy
		 * @param dx
		 * @param dy
		 * @return a point describing any position constraining applied
		 */
		protected Point2D constrainDrag(MouseEvent event, double totDx,
				double totDy, double dx, double dy) {
			boolean constrained = isConstrainedMoveEvent(event);

			if (constrained && cachedBounds == null) {
				if (Math.abs(totDx) < Math.abs(totDy)) {
					dx = 0;
					dy = totDy;
				} else {
					dx = totDx;
					dy = 0;
				}
			} else {
				if (!graph.isMoveBelowZero() && last != null
						&& initialLocation != null && start != null) {
					if (initialLocation.getX() + totDx < 0) {
						// TODO isn't dx always just 0?
						dx = start.getX() - last.getX()
								- initialLocation.getX();
					}
					if (initialLocation.getY() + totDy < 0) {
						// TODO isn't dy always just 0?
						dy = start.getY() - last.getY()
								- initialLocation.getY();
					}
				}
				if (!graph.isMoveBeyondGraphBounds() && last != null
						&& initialLocation != null && start != null) {
					Rectangle2D graphBounds = graph.getBounds();
					Rectangle2D viewBounds = AbstractCellView.getBounds(views);
					if (initialLocation.getX() + totDx + viewBounds.getWidth() > graphBounds
							.getWidth())
						dx = 0;
					if (initialLocation.getY() + totDy + viewBounds.getHeight() > graphBounds
							.getHeight())
						dy = 0;
				}
			}

			return new Point2D.Double(dx, dy);
		}

		public void mouseReleased(MouseEvent event) {
			try {
				if (event != null && !event.isConsumed()) {
					if (activeHandle != null) {
						activeHandle.mouseReleased(event);
						activeHandle = null;
					} else if (isMoving && !event.getPoint().equals(start)) {
						if (cachedBounds != null) {
							
							Point ep = event.getPoint();
							Point2D point = new Point2D.Double(ep.getX()
									- _mouseToViewDelta_x, ep.getY()
									- _mouseToViewDelta_y);
							Point2D snapCurrent = graph.snap(point);
							
							double dx = snapCurrent.getX() - start.getX();
							double dy = snapCurrent.getY() - start.getY();
							
							if (!graph.isMoveBelowZero() && initialLocation.getX() + dx < 0)
							    dx = -1 * initialLocation.getX();
							if (!graph.isMoveBelowZero() && initialLocation.getY() + dy < 0)
							    dy = -1 * initialLocation.getY();
							
							Point2D tmp = graph.fromScreen(new Point2D.Double(
									dx, dy));
							GraphLayoutCache.translateViews(views, tmp.getX(),
									tmp.getY());
						}
						CellView[] all = graphLayoutCache
								.getAllDescendants(views);
						Map attributes = GraphConstants.createAttributes(all,
								null);
						if (event.isControlDown() && graph.isCloneable()) { // Clone
							// Cells
							Object[] cells = graph.getDescendants(graph
									.order(context.getCells()));
							// Include properties from hidden cells
							Map hiddenMapping = graphLayoutCache
									.getHiddenMapping();
							for (int i = 0; i < cells.length; i++) {
								Object witness = attributes.get(cells[i]);
								if (witness == null) {
									CellView view = (CellView) hiddenMapping
											.get(cells[i]);
									if (view != null
											&& !graphModel.isPort(view
													.getCell())) {
										// TODO: Clone required? Same in
										// GraphConstants.
										AttributeMap attrs = (AttributeMap) view
												.getAllAttributes().clone();
										// Maybe translate?
										// attrs.translate(dx, dy);
										attributes.put(cells[i], attrs.clone());
									}
								}
							}
							ConnectionSet cs = ConnectionSet.create(graphModel,
									cells, false);
							ParentMap pm = ParentMap.create(graphModel, cells,
									false, true);
							cells = graphLayoutCache.insertClones(cells, graph
									.cloneCells(cells), attributes, cs, pm, 0,
									0);
						} else if (graph.isMoveable()) { // Move Cells
							ParentMap pm = null;

							// Moves into group
							if (targetGroup != null) {
								pm = new ParentMap(context.getCells(),
										targetGroup.getCell());
							} else if (graph.isMoveOutOfGroups()
									&& (ignoreTargetGroup != null && !ignoreTargetGroup
											.getBounds().intersects(
													AbstractCellView
															.getBounds(views)))) {
								pm = new ParentMap(context.getCells(), null);
							}
							graph.getGraphLayoutCache().edit(attributes,
									disconnect, pm, null);
						}
						event.consume();
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				ignoreTargetGroup = null;
				targetGroup = null;
				isDragging = false;
				disconnect = null;
				firstDrag = true;
				current = null;
				start = null;
			}
		}

	}

	/**
	 * PropertyChangeListener for the graph. Updates the appropriate variable
	 * and takes the appropriate actions, based on what changes.
	 */
	public class PropertyChangeHandler implements PropertyChangeListener,
			Serializable {
		public void propertyChange(PropertyChangeEvent event) {
			if (event.getSource() == graph) {
				String changeName = event.getPropertyName();
				if (changeName.equals("minimumSize"))
					updateCachedPreferredSize();
				else if (changeName.equals(JGraph.GRAPH_MODEL_PROPERTY))
					setModel((GraphModel) event.getNewValue());
				else if (changeName.equals(JGraph.GRAPH_LAYOUT_CACHE_PROPERTY)) {
					setGraphLayoutCache((GraphLayoutCache) event.getNewValue());
					graph.repaint();
				} else if (changeName.equals(JGraph.MARQUEE_HANDLER_PROPERTY))
					setMarquee((BasicMarqueeHandler) event.getNewValue());
				else if (changeName.equals("transferHandler")) {
					if (dropTarget != null)
						dropTarget
								.removeDropTargetListener(defaultDropTargetListener);
					dropTarget = graph.getDropTarget();
					try {
						if (dropTarget != null)
							dropTarget
									.addDropTargetListener(defaultDropTargetListener);
					} catch (TooManyListenersException tmle) {
						// should not happen... swing drop target is multicast
					}
				} else if (changeName.equals(JGraph.EDITABLE_PROPERTY)) {
					boolean editable = ((Boolean) event.getNewValue())
							.booleanValue();
					if (!editable && isEditing(graph))
						cancelEditing(graph);
				} else if (changeName.equals(JGraph.SELECTION_MODEL_PROPERTY))
					setSelectionModel(graph.getSelectionModel());
				else if (changeName.equals(JGraph.GRID_VISIBLE_PROPERTY)
						|| changeName.equals(JGraph.GRID_SIZE_PROPERTY)
						|| changeName.equals(JGraph.GRID_COLOR_PROPERTY)
						|| changeName.equals(JGraph.HANDLE_COLOR_PROPERTY)
						|| changeName
								.equals(JGraph.LOCKED_HANDLE_COLOR_PROPERTY)
						|| changeName.equals(JGraph.HANDLE_SIZE_PROPERTY)
						|| changeName.equals(JGraph.PORTS_VISIBLE_PROPERTY)
						|| changeName.equals(JGraph.ANTIALIASED_PROPERTY))
					graph.repaint();
				else if (changeName.equals(JGraph.SCALE_PROPERTY)) {
					updateSize();
				} else if (changeName.equals(JGraph.PROPERTY_BACKGROUNDIMAGE)) {
					updateSize();
				}
				else if (changeName.equals("font")) {
					completeEditing();
					updateSize();
				} else if (changeName.equals("componentOrientation")) {
					if (graph != null)
						graph.graphDidChange();
				}
			}
		}
	} // End of BasicGraphUI.PropertyChangeHandler

	/**
	 * GraphIncrementAction is used to handle up/down actions.
	 */
	public class GraphIncrementAction extends AbstractAction {
		/** Specifies the direction to adjust the selection by. */
		protected int direction;

		private GraphIncrementAction(int direction, String name) {
			this.direction = direction;
		}

		public void actionPerformed(ActionEvent e) {
			if (graph != null) {
				int step = 70;
				Rectangle rect = graph.getVisibleRect();
				if (direction == 1)
					rect.translate(0, -step); // up
				else if (direction == 2)
					rect.translate(step, 0); // right
				else if (direction == 3)
					rect.translate(0, step); // down
				else if (direction == 4)
					rect.translate(-step, 0); // left
				graph.scrollRectToVisible(rect);
			}
		}

		public boolean isEnabled() {
			return (graph != null && graph.isEnabled());
		}

	} // End of class BasicGraphUI.GraphIncrementAction

	/**
	 * ActionListener that invokes cancelEditing when action performed.
	 */
	private class GraphCancelEditingAction extends AbstractAction {
		public GraphCancelEditingAction(String name) {
		}

		public void actionPerformed(ActionEvent e) {
			if (graph != null)
				cancelEditing(graph);
		}

		public boolean isEnabled() {
			return (graph != null && graph.isEnabled() && graph.isEditing());
		}
	} // End of class BasicGraphUI.GraphCancelEditingAction

	/**
	 * ActionListener invoked to start editing on the focused cell.
	 */
	private class GraphEditAction extends AbstractAction {
		public GraphEditAction(String name) {
		}

		public void actionPerformed(ActionEvent ae) {
			if (isEnabled()) {
				if (getFocusedCell() instanceof GraphCell) {
					graph.startEditingAtCell(getFocusedCell());
				}
			}
		}

		public boolean isEnabled() {
			return (graph != null && graph.isEnabled());
		}
	} // End of BasicGraphUI.GraphEditAction

	/**
	 * Action to select everything in the graph.
	 */
	private class GraphSelectAllAction extends AbstractAction {
		private boolean selectAll;

		public GraphSelectAllAction(String name, boolean selectAll) {
			this.selectAll = selectAll;
		}

		public void actionPerformed(ActionEvent ae) {
			if (graph != null) {
				if (selectAll) {
					graph.setSelectionCells(graph.getGraphLayoutCache()
							.getVisibleCells(graph.getRoots()));
				} else
					graph.clearSelection();
			}
		}

		public boolean isEnabled() {
			return (graph != null && graph.isEnabled());
		}
	} // End of BasicGraphUI.GraphSelectAllAction

	/**
	 * MouseInputHandler handles passing all mouse events, including mouse
	 * motion events, until the mouse is released to the destination it is
	 * constructed with. It is assumed all the events are currently target at
	 * source.
	 */
	public class MouseInputHandler extends Object implements MouseInputListener {
		/** Source that events are coming from. */
		protected Component source;

		/** Destination that receives all events. */
		protected Component destination;

		public MouseInputHandler(Component source, Component destination,
				MouseEvent event) {
			this.source = source;
			this.destination = destination;
			this.source.addMouseListener(this);
			this.source.addMouseMotionListener(this);
			/* Dispatch the editing event */
			destination.dispatchEvent(SwingUtilities.convertMouseEvent(source,
					event, destination));
		}

		public void mouseClicked(MouseEvent e) {
			if (destination != null)
				destination.dispatchEvent(SwingUtilities.convertMouseEvent(
						source, e, destination));
		}

		public void mousePressed(MouseEvent e) {
		}

		public void mouseReleased(MouseEvent e) {
			if (destination != null)
				destination.dispatchEvent(SwingUtilities.convertMouseEvent(
						source, e, destination));
			removeFromSource();
		}

		public void mouseEntered(MouseEvent e) {
			if (!SwingUtilities.isLeftMouseButton(e)) {
				removeFromSource();
			}
		}

		public void mouseExited(MouseEvent e) {
			if (!SwingUtilities.isLeftMouseButton(e)) {
				removeFromSource();
			}
			// insertionLocation = null;
		}

		public void mouseDragged(MouseEvent e) {
			if (destination != null)
				destination.dispatchEvent(SwingUtilities.convertMouseEvent(
						source, e, destination));
		}

		public void mouseMoved(MouseEvent e) {
			removeFromSource();
		}

		protected void removeFromSource() {
			if (source != null) {
				source.removeMouseListener(this);
				source.removeMouseMotionListener(this);
			}
			source = destination = null;
		}

	} // End of class BasicGraphUI.MouseInputHandler

	/**
	 * Graph Drop Target Listener
	 */
	public class GraphDropTargetListener extends BasicGraphDropTargetListener
			implements Serializable {

		/**
		 * called to save the state of a component in case it needs to be
		 * restored because a drop is not performed.
		 */
		protected void saveComponentState(JComponent comp) {
		}

		/**
		 * called to restore the state of a component because a drop was not
		 * performed.
		 */
		protected void restoreComponentState(JComponent comp) {
			if (handle != null)
				handle.mouseDragged(null);
		}

		/**
		 * called to set the insertion location to match the current mouse
		 * pointer coordinates.
		 */
		protected void updateInsertionLocation(JComponent comp, Point p) {
			setInsertionLocation(p);
			if (handle != null) {
				// How to fetch the shift state?
				int mod = (dropAction == TransferHandler.COPY) ? InputEvent.CTRL_MASK
						: 0;
				handle.mouseDragged(new MouseEvent(comp, 0, 0, mod, p.x, p.y,
						1, false));
			}
		}

		public void dragEnter(DropTargetDragEvent e) {
			dropAction = e.getDropAction();
			super.dragEnter(e);
		}

		public void dropActionChanged(DropTargetDragEvent e) {
			dropAction = e.getDropAction();
			super.dropActionChanged(e);
		}

	} // End of BasicGraphUI.GraphDropTargetListener

	/**
	 * @return true if snapSelectedView mode is enabled during the drag
	 *         operation. If it is enabled, the view, that is returned by the
	 *         findViewForPoint(Point pt), will be snapped to the grid lines.
	 *         <br>
	 *         By default, findViewForPoint() returns the first view from the
	 *         GraphContext whose bounds intersect with snap proximity of a
	 *         mouse pointer. If snap-to-grid mode is disabled, views are moved
	 *         by a snap increment.
	 */
	public boolean isSnapSelectedView() {
		return snapSelectedView;
	}

	/**
	 * Sets the mode of the snapSelectedView drag operation.
	 * 
	 * @param snapSelectedView
	 *            specifies if the snap-to-grid mode should be applied during a
	 *            drag operation. If it is enabled, the view, that is returned
	 *            by the findViewForPoint(Point pt), will be snapped to the grid
	 *            lines. <br>
	 *            By default, findViewForPoint() returns the first view from the
	 *            GraphContext whose bounds intersect with snap proximity of a
	 *            mouse pointer. If snap-to-grid mode is disabled, views are
	 *            moved by a snap increment.
	 */
	public void setSnapSelectedView(boolean snapSelectedView) {
		this.snapSelectedView = snapSelectedView;
	}

} // End of class BasicGraphUI
