BasicGraphUI.java :  » Graphic-Library » jgraph » org » jgraph » plaf » basic » Java Open Source

Java Open Source » Graphic Library » jgraph 
jgraph » org » jgraph » plaf » basic » BasicGraphUI.java
/*
 * @(#)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
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.