Package com.mxgraph.swing

Source Code of com.mxgraph.swing.mxGraphComponent$mxMouseRedirector

/**
* Copyright (c) 2009-2010, Gaudenz Alder, David Benson
*/
package com.mxgraph.swing;

import java.awt.BasicStroke;
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.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EventObject;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.swing.BorderFactory;
import javax.swing.BoundedRangeModel;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.TransferHandler;

import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.canvas.mxICanvas;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.model.mxGraphModel.Filter;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.swing.handler.mxCellHandler;
import com.mxgraph.swing.handler.mxConnectionHandler;
import com.mxgraph.swing.handler.mxEdgeHandler;
import com.mxgraph.swing.handler.mxElbowEdgeHandler;
import com.mxgraph.swing.handler.mxGraphHandler;
import com.mxgraph.swing.handler.mxGraphTransferHandler;
import com.mxgraph.swing.handler.mxPanningHandler;
import com.mxgraph.swing.handler.mxSelectionCellsHandler;
import com.mxgraph.swing.handler.mxVertexHandler;
import com.mxgraph.swing.util.mxCellOverlay;
import com.mxgraph.swing.util.mxICellOverlay;
import com.mxgraph.swing.view.mxCellEditor;
import com.mxgraph.swing.view.mxICellEditor;
import com.mxgraph.swing.view.mxInteractiveCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxResources;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxEdgeStyle;
import com.mxgraph.view.mxEdgeStyle.mxEdgeStyleFunction;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphView;
import com.mxgraph.view.mxTemporaryCellStates;

/**
* For setting the preferred size of the viewport for scrolling, use
* mxGraph.setMinimumGraphSize. This component is a combined scrollpane with an
* inner mxGraphControl. The control contains the actual graph display.
*
* To set the background color of the graph, use the following code:
*
* <pre>
* graphComponent.getViewport().setOpaque(true);
* graphComponent.getViewport().setBackground(newColor);
* </pre>
*
* This class fires the following events:
*
* mxEvent.START_EDITING fires before starting the in-place editor for an
* existing cell in startEditingAtCell. The <code>cell</code> property contains
* the cell that is being edit and the <code>event</code> property contains
* optional EventObject which was passed to startEditingAtCell.
*
* mxEvent.LABEL_CHANGED fires between begin- and endUpdate after the call to
* mxGraph.cellLabelChanged in labelChanged. The <code>cell</code> property
* contains the cell, the <code>value</code> property contains the new value for
* the cell and the optional <code>event</code> property contains the
* EventObject that started the edit.
*
* mxEvent.ADD_OVERLAY and mxEvent.REMOVE_OVERLAY fire afer an overlay was added
* or removed using add-/removeOverlay. The <code>cell</code> property contains
* the cell for which the overlay was added or removed and the
* <code>overlay</code> property contain the mxOverlay.
*
* mxEvent.BEFORE_PAINT and mxEvent.AFTER_PAINT fire before and after the paint
* method is called on the component. The <code>g</code> property contains the
* graphics context which is used for painting.
*/
public class mxGraphComponent extends JScrollPane implements Printable
{

  /**
   *
   */
  private static final long serialVersionUID = -30203858391633447L;

  /**
   *
   */
  public static final int GRID_STYLE_DOT = 0;

  /**
   *
   */
  public static final int GRID_STYLE_CROSS = 1;

  /**
   *
   */
  public static final int GRID_STYLE_LINE = 2;

  /**
   *
   */
  public static final int GRID_STYLE_DASHED = 3;

  /**
   *
   */
  public static final int ZOOM_POLICY_NONE = 0;

  /**
   *
   */
  public static final int ZOOM_POLICY_PAGE = 1;

  /**
   *
   */
  public static final int ZOOM_POLICY_WIDTH = 2;

  /**
   *
   */
  public static ImageIcon DEFAULT_EXPANDED_ICON = null;

  /**
   *
   */
  public static ImageIcon DEFAULT_COLLAPSED_ICON = null;

  /**
   *
   */
  public static ImageIcon DEFAULT_WARNING_ICON = null;

  /**
   * Specifies the default page scale. Default is 1.4
   */
  public static final double DEFAULT_PAGESCALE = 1.4;

  /**
   * Loads the collapse and expand icons.
   */
  static
  {
    DEFAULT_EXPANDED_ICON = new ImageIcon(
        mxGraphComponent.class
            .getResource("/com/mxgraph/swing/images/expanded.gif"));
    DEFAULT_COLLAPSED_ICON = new ImageIcon(
        mxGraphComponent.class
            .getResource("/com/mxgraph/swing/images/collapsed.gif"));
    DEFAULT_WARNING_ICON = new ImageIcon(
        mxGraphComponent.class
            .getResource("/com/mxgraph/swing/images/warning.gif"));
  }

  /**
   *
   */
  protected mxGraph graph;

  /**
   *
   */
  protected mxGraphControl graphControl;

  /**
   *
   */
  protected mxEventSource eventSource = new mxEventSource(this);

  /**
   *
   */
  protected mxICellEditor cellEditor;

  /**
   *
   */
  protected mxConnectionHandler connectionHandler;

  /**
   *
   */
  protected mxPanningHandler panningHandler;

  /**
   *
   */
  protected mxSelectionCellsHandler selectionCellsHandler;

  /**
   *
   */
  protected mxGraphHandler graphHandler;

  /**
   * The transparency of previewed cells from 0.0. to 0.1. 0.0 indicates
   * transparent, 1.0 indicates opaque. Default is 1.
   */
  protected float previewAlpha = 0.5f;

  /**
   * Specifies the <mxImage> to be returned by <getBackgroundImage>. Default
   * is null.
   */
  protected ImageIcon backgroundImage;

  /**
   * Background page format.
   */
  protected PageFormat pageFormat = new PageFormat();

  /**
   *
   */
  protected mxInteractiveCanvas canvas;

  /**
   *
   */
  protected BufferedImage tripleBuffer;

  /**
   *
   */
  protected Graphics2D tripleBufferGraphics;

  /**
   * Defines the scaling for the background page metrics. Default is
   * {@link #DEFAULT_PAGESCALE}.
   */
  protected double pageScale = DEFAULT_PAGESCALE;

  /**
   * Specifies if the background page should be visible. Default is false.
   */
  protected boolean pageVisible = false;

  /**
   * If the pageFormat should be used to determine the minimal graph bounds
   * even if the page is not visible (see pageVisible). Default is false.
   */
  protected boolean preferPageSize = false;

  /**
   * Specifies if a dashed line should be drawn between multiple pages.
   */
  protected boolean pageBreaksVisible = true;

  /**
   * Specifies the color of page breaks
   */
  protected Color pageBreakColor = Color.darkGray;

  /**
   * Specifies the number of pages in the horizontal direction.
   */
  protected int horizontalPageCount = 1;

  /**
   * Specifies the number of pages in the vertical direction.
   */
  protected int verticalPageCount = 1;

  /**
   * Specifies if the background page should be centered by automatically
   * setting the translate in the view. Default is true. This does only apply
   * if pageVisible is true.
   */
  protected boolean centerPage = true;

  /**
   * Color of the background area if layout view.
   */
  protected Color pageBackgroundColor = new Color(144, 153, 174);

  /**
   *
   */
  protected Color pageShadowColor = new Color(110, 120, 140);

  /**
   *
   */
  protected Color pageBorderColor = Color.black;

  /**
   * Specifies if the grid is visible. Default is false.
   */
  protected boolean gridVisible = false;

  /**
   *
   */
  protected Color gridColor = new Color(192, 192, 192);

  /**
   * Whether or not to scroll the scrollable container the graph exists in if
   * a suitable handler is active and the graph bounds already exist extended
   * in the direction of mouse travel.
   */
  protected boolean autoScroll = true;

  /**
   * Whether to extend the graph bounds and scroll towards the limit of those
   * new bounds in the direction of mouse travel if a handler is active while
   * the mouse leaves the container that the graph exists in.
   */
  protected boolean autoExtend = true;

  /**
   *
   */
  protected boolean dragEnabled = true;

  /**
   *
   */
  protected boolean importEnabled = true;

  /**
   *
   */
  protected boolean exportEnabled = true;

  /**
   * Specifies if folding (collapse and expand via an image icon in the graph
   * should be enabled). Default is true.
   */
  protected boolean foldingEnabled = true;

  /**
   * Specifies the tolerance for mouse clicks. Default is 4.
   */
  protected int tolerance = 4;

  /**
   * Specifies if swimlanes are selected when the mouse is released over the
   * swimlanes content area. Default is true.
   */
  protected boolean swimlaneSelectionEnabled = true;

  /**
   * Specifies if the content area should be transparent to events. Default is
   * true.
   */
  protected boolean transparentSwimlaneContent = true;

  /**
   *
   */
  protected int gridStyle = GRID_STYLE_DOT;

  /**
   *
   */
  protected ImageIcon expandedIcon = DEFAULT_EXPANDED_ICON;

  /**
   *
   */
  protected ImageIcon collapsedIcon = DEFAULT_COLLAPSED_ICON;

  /**
   *
   */
  protected ImageIcon warningIcon = DEFAULT_WARNING_ICON;

  /**
   *
   */
  protected boolean antiAlias = true;

  /**
   *
   */
  protected boolean textAntiAlias = true;

  /**
   * Specifies <escape> should be invoked when the escape key is pressed.
   * Default is true.
   */
  protected boolean escapeEnabled = true;

  /**
   * If true, when editing is to be stopped by way of selection changing, data
   * in diagram changing or other means stopCellEditing is invoked, and
   * changes are saved. This is implemented in a mouse listener in this class.
   * Default is true.
   */
  protected boolean invokesStopCellEditing = true;

  /**
   * If true, pressing the enter key without pressing control will stop
   * editing and accept the new value. This is used in <mxKeyHandler> to stop
   * cell editing. Default is false.
   */
  protected boolean enterStopsCellEditing = false;

  /**
   * Specifies the zoom policy. Default is ZOOM_POLICY_PAGE. The zoom policy
   * does only apply if pageVisible is true.
   */
  protected int zoomPolicy = ZOOM_POLICY_PAGE;

  /**
   * Internal flag to not reset zoomPolicy when zoom was set automatically.
   */
  private transient boolean zooming = false;

  /**
   * Specifies the factor used for zoomIn and zoomOut. Default is 1.2 (120%).
   */
  protected double zoomFactor = 1.2;

  /**
   * Specifies if the viewport should automatically contain the selection
   * cells after a zoom operation. Default is false.
   */
  protected boolean keepSelectionVisibleOnZoom = false;

  /**
   * Specifies if the zoom operations should go into the center of the actual
   * diagram rather than going from top, left. Default is true.
   */
  protected boolean centerZoom = true;

  /**
   * Specifies if an image buffer should be used for painting the component.
   * Default is false.
   */
  protected boolean tripleBuffered = false;

  /**
   * Used for debugging the dirty region.
   */
  public boolean showDirtyRectangle = false;

  /**
   * Maps from cells to lists of heavyweights.
   */
  protected Hashtable<Object, Component[]> components = new Hashtable<Object, Component[]>();

  /**
   * Maps from cells to lists of overlays.
   */
  protected Hashtable<Object, mxICellOverlay[]> overlays = new Hashtable<Object, mxICellOverlay[]>();

  /**
   * Boolean flag to disable centering after the first time.
   */
  private transient boolean centerOnResize = true;

  /**
   * Updates the heavyweight component structure after any changes.
   */
  protected mxIEventListener updateHandler = new mxIEventListener()
  {
    public void invoke(Object sender, mxEventObject evt)
    {
      updateComponents();
      graphControl.updatePreferredSize();
    }
  };

  /**
   *
   */
  protected mxIEventListener repaintHandler = new mxIEventListener()
  {
    public void invoke(Object source, mxEventObject evt)
    {
      mxRectangle dirty = (mxRectangle) evt.getProperty("region");
      Rectangle rect = (dirty != null) ? dirty.getRectangle() : null;

      if (rect != null)
      {
        rect.grow(1, 1);
      }

      // Updates the triple buffer
      repaintTripleBuffer(rect);

      // Repaints the control using the optional triple buffer
      graphControl.repaint((rect != null) ? rect : getViewport()
          .getViewRect());

      // ----------------------------------------------------------
      // Shows the dirty region as a red rectangle (for debugging)
      JPanel panel = (JPanel) getClientProperty("dirty");

      if (showDirtyRectangle)
      {
        if (panel == null)
        {
          panel = new JPanel();
          panel.setOpaque(false);
          panel.setBorder(BorderFactory.createLineBorder(Color.RED));

          putClientProperty("dirty", panel);
          graphControl.add(panel);
        }

        if (dirty != null)
        {
          panel.setBounds(dirty.getRectangle());
        }

        panel.setVisible(dirty != null);
      }
      else if (panel != null && panel.getParent() != null)
      {
        panel.getParent().remove(panel);
        putClientProperty("dirty", null);
        repaint();
      }
      // ----------------------------------------------------------
    }
  };

  /**
   *
   */
  protected PropertyChangeListener viewChangeHandler = new PropertyChangeListener()
  {
    /**
     *
     */
    public void propertyChange(PropertyChangeEvent evt)
    {
      if (evt.getPropertyName().equals("view"))
      {
        mxGraphView oldView = (mxGraphView) evt.getOldValue();
        mxGraphView newView = (mxGraphView) evt.getNewValue();

        if (oldView != null)
        {
          oldView.removeListener(updateHandler);
        }

        if (newView != null)
        {
          newView.addListener(mxEvent.SCALE, updateHandler);
          newView.addListener(mxEvent.TRANSLATE, updateHandler);
          newView.addListener(mxEvent.SCALE_AND_TRANSLATE,
              updateHandler);
          newView.addListener(mxEvent.UP, updateHandler);
          newView.addListener(mxEvent.DOWN, updateHandler);
        }
      }
      else if (evt.getPropertyName().equals("model"))
      {
        mxGraphModel oldModel = (mxGraphModel) evt.getOldValue();
        mxGraphModel newModel = (mxGraphModel) evt.getNewValue();

        if (oldModel != null)
        {
          oldModel.removeListener(updateHandler);
        }

        if (newModel != null)
        {
          newModel.addListener(mxEvent.CHANGE, updateHandler);
        }
      }
    }

  };

  /**
   * Resets the zoom policy if the scale is changed manually.
   */
  protected mxIEventListener scaleHandler = new mxIEventListener()
  {
    /**
     *
     */
    public void invoke(Object sender, mxEventObject evt)
    {
      if (!zooming)
      {
        zoomPolicy = ZOOM_POLICY_NONE;
      }
    }
  };

  /**
   *
   * @param graph
   */
  public mxGraphComponent(mxGraph graph)
  {
    setCellEditor(createCellEditor());
    canvas = createCanvas();

    // Initializes the buffered view and
    graphControl = createGraphControl();
    installFocusHandler();
    installKeyHandler();
    installResizeHandler();
    setGraph(graph);

    // Adds the viewport view and initializes handlers
    setViewportView(graphControl);
    createHandlers();
    installDoubleClickHandler();
  }

  /**
   * installs a handler to set the focus to the container.
   */
  protected void installFocusHandler()
  {
    graphControl.addMouseListener(new MouseAdapter()
    {
      public void mousePressed(MouseEvent e)
      {
        if (!hasFocus())
        {
          requestFocus();
        }
      }
    });
  }

  /**
   * Handles escape keystrokes.
   */
  protected void installKeyHandler()
  {
    addKeyListener(new KeyAdapter()
    {
      public void keyPressed(KeyEvent e)
      {
        if (e.getKeyCode() == KeyEvent.VK_ESCAPE && isEscapeEnabled())
        {
          escape(e);
        }
      }
    });
  }

  /**
   * Applies the zoom policy if the size of the component changes.
   */
  protected void installResizeHandler()
  {
    addComponentListener(new ComponentAdapter()
    {
      public void componentResized(ComponentEvent e)
      {
        zoomAndCenter();
      }
    });
  }

  /**
   * Adds handling of edit and stop-edit events after all other handlers have
   * been installed.
   */
  protected void installDoubleClickHandler()
  {
    graphControl.addMouseListener(new MouseAdapter()
    {
      public void mouseReleased(MouseEvent e)
      {
        if (isEnabled())
        {
          if (!e.isConsumed() && isEditEvent(e))
          {
            Object cell = getCellAt(e.getX(), e.getY(), false);

            if (cell != null && getGraph().isCellEditable(cell))
            {
              startEditingAtCell(cell, e);
            }
          }
          else
          {
            // Other languages use focus traversal here, in Java
            // we explicitely stop editing after a click elsewhere
            stopEditing(!invokesStopCellEditing);
          }
        }
      }

    });
  }

  /**
   *
   */
  protected mxICellEditor createCellEditor()
  {
    return new mxCellEditor(this);
  }

  /**
   *
   */
  public void setGraph(mxGraph value)
  {
    mxGraph oldValue = graph;

    // Uninstalls listeners for existing graph
    if (graph != null)
    {
      graph.removeListener(repaintHandler);
      graph.getModel().removeListener(updateHandler);
      graph.getView().removeListener(updateHandler);
      graph.removePropertyChangeListener(viewChangeHandler);
      graph.getView().removeListener(scaleHandler);
    }

    graph = value;

    // Updates the buffer if the model changes
    graph.addListener(mxEvent.REPAINT, repaintHandler);

    // Installs the update handler to sync the overlays and controls
    graph.getModel().addListener(mxEvent.CHANGE, updateHandler);

    // Repaint after the following events is handled via
    // mxGraph.repaint-events
    // The respective handlers are installed in mxGraph.setView
    mxGraphView view = graph.getView();

    view.addListener(mxEvent.SCALE, updateHandler);
    view.addListener(mxEvent.TRANSLATE, updateHandler);
    view.addListener(mxEvent.SCALE_AND_TRANSLATE, updateHandler);
    view.addListener(mxEvent.UP, updateHandler);
    view.addListener(mxEvent.DOWN, updateHandler);

    graph.addPropertyChangeListener(viewChangeHandler);

    // Resets the zoom policy if the scale changes
    graph.getView().addListener(mxEvent.SCALE, scaleHandler);
    graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, scaleHandler);

    // Invoke the update handler once for initial state
    updateHandler.invoke(graph.getView(), null);

    firePropertyChange("graph", oldValue, graph);
  }

  /**
   *
   * @return Returns the object that contains the graph.
   */
  public mxGraph getGraph()
  {
    return graph;
  }

  /**
   * Creates the inner control that handles tooltips, preferred size and can
   * draw cells onto a canvas.
   */
  protected mxGraphControl createGraphControl()
  {
    return new mxGraphControl();
  }

  /**
   *
   * @return Returns the control that renders the graph.
   */
  public mxGraphControl getGraphControl()
  {
    return graphControl;
  }

  /**
   * Creates the connection-, panning and graphhandler (in this order).
   */
  protected void createHandlers()
  {
    setTransferHandler(createTransferHandler());
    panningHandler = createPanningHandler();
    selectionCellsHandler = createSelectionCellsHandler();
    connectionHandler = createConnectionHandler();
    graphHandler = createGraphHandler();
  }

  /**
   *
   */
  protected TransferHandler createTransferHandler()
  {
    return new mxGraphTransferHandler();
  }

  /**
   *
   */
  protected mxSelectionCellsHandler createSelectionCellsHandler()
  {
    return new mxSelectionCellsHandler(this);
  }

  /**
   *
   */
  protected mxGraphHandler createGraphHandler()
  {
    return new mxGraphHandler(this);
  }

  /**
   *
   */
  public mxSelectionCellsHandler getSelectionCellsHandler()
  {
    return selectionCellsHandler;
  }

  /**
   *
   */
  public mxGraphHandler getGraphHandler()
  {
    return graphHandler;
  }

  /**
   *
   */
  protected mxConnectionHandler createConnectionHandler()
  {
    return new mxConnectionHandler(this);
  }

  /**
   *
   */
  public mxConnectionHandler getConnectionHandler()
  {
    return connectionHandler;
  }

  /**
   *
   */
  protected mxPanningHandler createPanningHandler()
  {
    return new mxPanningHandler(this);
  }

  /**
   *
   */
  public mxPanningHandler getPanningHandler()
  {
    return panningHandler;
  }

  /**
   *
   */
  public boolean isEditing()
  {
    return getCellEditor().getEditingCell() != null;
  }

  /**
   *
   */
  public mxICellEditor getCellEditor()
  {
    return cellEditor;
  }

  /**
   *
   */
  public void setCellEditor(mxICellEditor value)
  {
    mxICellEditor oldValue = cellEditor;
    cellEditor = value;

    firePropertyChange("cellEditor", oldValue, cellEditor);
  }

  /**
   * @return the tolerance
   */
  public int getTolerance()
  {
    return tolerance;
  }

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

    firePropertyChange("tolerance", oldValue, tolerance);
  }

  /**
   *
   */
  public PageFormat getPageFormat()
  {
    return pageFormat;
  }

  /**
   *
   */
  public void setPageFormat(PageFormat value)
  {
    PageFormat oldValue = pageFormat;
    pageFormat = value;

    firePropertyChange("pageFormat", oldValue, pageFormat);
  }

  /**
   *
   */
  public double getPageScale()
  {
    return pageScale;
  }

  /**
   *
   */
  public void setPageScale(double value)
  {
    double oldValue = pageScale;
    pageScale = value;

    firePropertyChange("pageScale", oldValue, pageScale);
  }

  /**
   * Returns the size of the area that layouts can operate in.
   */
  public mxRectangle getLayoutAreaSize()
  {
    if (pageVisible)
    {
      Dimension d = getPreferredSizeForPage();

      return new mxRectangle(new Rectangle(d));
    }
    else
    {
      return new mxRectangle(new Rectangle(graphControl.getSize()));
    }
  }

  /**
   *
   */
  public ImageIcon getBackgroundImage()
  {
    return backgroundImage;
  }

  /**
   *
   */
  public void setBackgroundImage(ImageIcon value)
  {
    ImageIcon oldValue = backgroundImage;
    backgroundImage = value;

    firePropertyChange("backgroundImage", oldValue, backgroundImage);
  }

  /**
   * @return the pageVisible
   */
  public boolean isPageVisible()
  {
    return pageVisible;
  }

  /**
   * Fires a property change event for <code>pageVisible</code>. zoomAndCenter
   * should be called if this is set to true.
   *
   * @param value
   *            the pageVisible to set
   */
  public void setPageVisible(boolean value)
  {
    boolean oldValue = pageVisible;
    pageVisible = value;

    firePropertyChange("pageVisible", oldValue, pageVisible);
  }

  /**
   * @return the preferPageSize
   */
  public boolean isPreferPageSize()
  {
    return preferPageSize;
  }

  /**
   * Fires a property change event for <code>preferPageSize</code>.
   *
   * @param value
   *            the preferPageSize to set
   */
  public void setPreferPageSize(boolean value)
  {
    boolean oldValue = preferPageSize;
    preferPageSize = value;

    firePropertyChange("preferPageSize", oldValue, preferPageSize);
  }

  /**
   * @return the pageBreaksVisible
   */
  public boolean isPageBreaksVisible()
  {
    return pageBreaksVisible;
  }

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

    firePropertyChange("pageBreaksVisible", oldValue, pageBreaksVisible);
  }

  /**
   * @return the pageBreakColor
   */
  public Color getPageBreakColor()
  {
    return pageBreakColor;
  }

  /**
   * @param pageBreakColor the pageBreakColor to set
   */
  public void setPageBreakColor(Color pageBreakColor)
  {
    this.pageBreakColor = pageBreakColor;
  }

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

    firePropertyChange("horizontalPageCount", oldValue, horizontalPageCount);
  }

  /**
   *
   */
  public int getHorizontalPageCount()
  {
    return horizontalPageCount;
  }

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

    firePropertyChange("verticalPageCount", oldValue, verticalPageCount);
  }

  /**
   *
   */
  public int getVerticalPageCount()
  {
    return verticalPageCount;
  }

  /**
   * @return the centerPage
   */
  public boolean isCenterPage()
  {
    return centerPage;
  }

  /**
   * zoomAndCenter should be called if this is set to true.
   *
   * @param value
   *            the centerPage to set
   */
  public void setCenterPage(boolean value)
  {
    boolean oldValue = centerPage;
    centerPage = value;

    firePropertyChange("centerPage", oldValue, centerPage);
  }

  /**
   * @return the pageBackgroundColor
   */
  public Color getPageBackgroundColor()
  {
    return pageBackgroundColor;
  }

  /**
   * Sets the color that appears behind the page.
   *
   * @param value
   *            the pageBackgroundColor to set
   */
  public void setPageBackgroundColor(Color value)
  {
    Color oldValue = pageBackgroundColor;
    pageBackgroundColor = value;

    firePropertyChange("pageBackgroundColor", oldValue, pageBackgroundColor);
  }

  /**
   * @return the pageShadowColor
   */
  public Color getPageShadowColor()
  {
    return pageShadowColor;
  }

  /**
   * @param value
   *            the pageShadowColor to set
   */
  public void setPageShadowColor(Color value)
  {
    Color oldValue = pageShadowColor;
    pageShadowColor = value;

    firePropertyChange("pageShadowColor", oldValue, pageShadowColor);
  }

  /**
   * @return the pageShadowColor
   */
  public Color getPageBorderColor()
  {
    return pageBorderColor;
  }

  /**
   * @param value
   *            the pageBorderColor to set
   */
  public void setPageBorderColor(Color value)
  {
    Color oldValue = pageBorderColor;
    pageBorderColor = value;

    firePropertyChange("pageBorderColor", oldValue, pageBorderColor);
  }

  /**
   * @return the keepSelectionVisibleOnZoom
   */
  public boolean isKeepSelectionVisibleOnZoom()
  {
    return keepSelectionVisibleOnZoom;
  }

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

    firePropertyChange("keepSelectionVisibleOnZoom", oldValue,
        keepSelectionVisibleOnZoom);
  }

  /**
   * @return the zoomFactor
   */
  public double getZoomFactor()
  {
    return zoomFactor;
  }

  /**
   * @param value
   *            the zoomFactor to set
   */
  public void setZoomFactor(double value)
  {
    double oldValue = zoomFactor;
    zoomFactor = value;

    firePropertyChange("zoomFactor", oldValue, zoomFactor);
  }

  /**
   * @return the centerZoom
   */
  public boolean isCenterZoom()
  {
    return centerZoom;
  }

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

    firePropertyChange("centerZoom", oldValue, centerZoom);
  }

  /**
   *
   */
  public void setZoomPolicy(int value)
  {
    int oldValue = zoomPolicy;
    zoomPolicy = value;

    if (zoomPolicy != ZOOM_POLICY_NONE)
    {
      zoom(zoomPolicy == ZOOM_POLICY_PAGE, true);
    }

    firePropertyChange("zoomPolicy", oldValue, zoomPolicy);
  }

  /**
   *
   */
  public int getZoomPolicy()
  {
    return zoomPolicy;
  }

  /**
   * Callback to process an escape keystroke.
   *
   * @param e
   */
  public void escape(KeyEvent e)
  {
    if (selectionCellsHandler != null)
    {
      selectionCellsHandler.reset();
    }

    if (connectionHandler != null)
    {
      connectionHandler.reset();
    }

    if (graphHandler != null)
    {
      graphHandler.reset();
    }

    if (cellEditor != null)
    {
      cellEditor.stopEditing(true);
    }
  }

  /**
   * Clones and inserts the given cells into the graph using the move method
   * and returns the inserted cells. This shortcut is used if cells are
   * inserted via datatransfer.
   */
  public Object[] importCells(Object[] cells, double dx, double dy,
      Object target, Point location)
  {
    return graph.moveCells(cells, dx, dy, true, target, location);
  }

  /**
   * Refreshes the display and handles.
   */
  public void refresh()
  {
    graph.refresh();
    selectionCellsHandler.refresh();
  }

  /**
   * Returns an mxPoint representing the given event in the unscaled,
   * non-translated coordinate space and applies the grid.
   */
  public mxPoint getPointForEvent(MouseEvent e)
  {
    return getPointForEvent(e, true);
  }

  /**
   * Returns an mxPoint representing the given event in the unscaled,
   * non-translated coordinate space and applies the grid.
   */
  public mxPoint getPointForEvent(MouseEvent e, boolean addOffset)
  {
    double s = graph.getView().getScale();
    mxPoint tr = graph.getView().getTranslate();

    double off = (addOffset) ? graph.getGridSize() / 2 : 0;
    double x = graph.snap(e.getX() / s - tr.getX() - off);
    double y = graph.snap(e.getY() / s - tr.getY() - off);

    return new mxPoint(x, y);
  }

  /**
   *
   */
  public void startEditing()
  {
    startEditingAtCell(null);
  }

  /**
   *
   */
  public void startEditingAtCell(Object cell)
  {
    startEditingAtCell(cell, null);
  }

  /**
   *
   */
  public void startEditingAtCell(Object cell, EventObject evt)
  {
    if (cell == null)
    {
      cell = graph.getSelectionCell();

      if (cell != null && !graph.isCellEditable(cell))
      {
        cell = null;
      }
    }

    if (cell != null)
    {
      eventSource.fireEvent(new mxEventObject(mxEvent.START_EDITING,
          "cell", cell, "event", evt));
      cellEditor.startEditing(cell, evt);
    }
  }

  /**
   *
   */
  public String getEditingValue(Object cell, EventObject trigger)
  {
    return graph.convertValueToString(cell);
  }

  /**
   *
   */
  public void stopEditing(boolean cancel)
  {
    cellEditor.stopEditing(cancel);
  }

  /**
   * Sets the label of the specified cell to the given value using
   * mxGraph.cellLabelChanged and fires mxEvent.LABEL_CHANGED while the
   * transaction is in progress. Returns the cell whose label was changed.
   *
   * @param cell
   *            Cell whose label should be changed.
   * @param value
   *            New value of the label.
   * @param evt
   *            Optional event that triggered the change.
   */
  public Object labelChanged(Object cell, Object value, EventObject evt)
  {
    mxIGraphModel model = graph.getModel();

    model.beginUpdate();
    try
    {
      graph.cellLabelChanged(cell, value, graph.isAutoSizeCell(cell));
      eventSource.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED,
          "cell", cell, "value", value, "event", evt));
    }
    finally
    {
      model.endUpdate();
    }

    return cell;
  }

  /**
   * Returns the (unscaled) preferred size for the current page format (scaled
   * by pageScale).
   */
  protected Dimension getPreferredSizeForPage()
  {
    return new Dimension((int) Math.round(pageFormat.getWidth() * pageScale
        * horizontalPageCount), (int) Math.round(pageFormat.getHeight()
        * pageScale * verticalPageCount));
  }

  /**
   * Returns the vertical border between the page and the control.
   */
  public int getVerticalPageBorder()
  {
    return (int) Math.round(pageFormat.getWidth() * pageScale);
  }

  /**
   * Returns the horizontal border between the page and the control.
   */
  public int getHorizontalPageBorder()
  {
    return (int) Math.round(0.5 * pageFormat.getHeight() * pageScale);
  }

  /**
   * Returns the scaled preferred size for the current graph.
   */
  protected Dimension getScaledPreferredSizeForGraph()
  {
    mxRectangle bounds = graph.getGraphBounds();
    int border = graph.getBorder();

    return new Dimension(
        (int) Math.round(bounds.getX() + bounds.getWidth()) + border
            + 1, (int) Math.round(bounds.getY()
            + bounds.getHeight())
            + border + 1);
  }

  /**
   * Should be called by a hook inside mxGraphView/mxGraph
   */
  protected mxPoint getPageTranslate(double scale)
  {
    Dimension d = getPreferredSizeForPage();
    Dimension bd = new Dimension(d);

    if (!preferPageSize)
    {
      bd.width += 2 * getHorizontalPageBorder();
      bd.height += 2 * getVerticalPageBorder();
    }

    double width = Math.max(bd.width, (getViewport().getWidth() - 8)
        / scale);
    double height = Math.max(bd.height, (getViewport().getHeight() - 8)
        / scale);

    double dx = Math.max(0, (width - d.width) / 2);
    double dy = Math.max(0, (height - d.height) / 2);

    return new mxPoint(dx, dy);
  }

  /**
   * Invoked after the component was resized to update the zoom if the zoom
   * policy is not none and/or update the translation of the diagram if
   * pageVisible and centerPage are true.
   */
  public void zoomAndCenter()
  {
    if (zoomPolicy != ZOOM_POLICY_NONE)
    {
      // Centers only on the initial zoom call
      zoom(zoomPolicy == ZOOM_POLICY_PAGE, centerOnResize
          || zoomPolicy == ZOOM_POLICY_PAGE);
      centerOnResize = false;
    }
    else if (pageVisible && centerPage)
    {
      mxPoint translate = getPageTranslate(graph.getView().getScale());
      graph.getView().setTranslate(translate);
    }
    else
    {
      getGraphControl().updatePreferredSize();
    }
  }

  /**
   * Zooms into the graph by zoomFactor.
   */
  public void zoomIn()
  {
    zoom(zoomFactor);
  }

  /**
   * Function: zoomOut
   *
   * Zooms out of the graph by <zoomFactor>.
   */
  public void zoomOut()
  {
    zoom(1 / zoomFactor);
  }

  /**
   *
   */
  public void zoom(double factor)
  {
    mxGraphView view = graph.getView();
    double newScale = (double) ((int) (view.getScale() * 100 * factor)) / 100;

    if (newScale != view.getScale() && newScale > 0.04)
    {
      mxPoint translate = (pageVisible && centerPage) ? getPageTranslate(newScale)
          : new mxPoint();
      graph.getView().scaleAndTranslate(newScale, translate.getX(),
          translate.getY());

      if (keepSelectionVisibleOnZoom && !graph.isSelectionEmpty())
      {
        getGraphControl().scrollRectToVisible(
            view.getBoundingBox(graph.getSelectionCells())
                .getRectangle());
      }
      else
      {
        maintainScrollBar(true, factor, centerZoom);
        maintainScrollBar(false, factor, centerZoom);
      }
    }
  }

  /**
   *
   */
  public void zoomTo(final double newScale, final boolean center)
  {
    mxGraphView view = graph.getView();
    final double scale = view.getScale();

    mxPoint translate = (pageVisible && centerPage) ? getPageTranslate(newScale)
        : new mxPoint();
    graph.getView().scaleAndTranslate(newScale, translate.getX(),
        translate.getY());

    // Causes two repaints on the scrollpane, namely one for the scale
    // change with the new preferred size and one for the change of
    // the scrollbar position. The latter cannot be done immediately
    // because the scrollbar keeps the value <= max - extent, and if
    // max is changed the value change will trigger a syncScrollPane
    // WithViewport in BasicScrollPaneUI, which will update the value
    // for the previous maximum (ie. it must be invoked later).
    SwingUtilities.invokeLater(new Runnable()
    {
      public void run()
      {
        maintainScrollBar(true, newScale / scale, center);
        maintainScrollBar(false, newScale / scale, center);
      }
    });
  }

  /**
   * Function: zoomActual
   *
   * Resets the zoom and panning in the view.
   */
  public void zoomActual()
  {
    mxPoint translate = (pageVisible && centerPage) ? getPageTranslate(1)
        : new mxPoint();
    graph.getView()
        .scaleAndTranslate(1, translate.getX(), translate.getY());

    if (isPageVisible())
    {
      // Causes two repaints, see zoomTo for more details
      SwingUtilities.invokeLater(new Runnable()
      {
        public void run()
        {
          Dimension pageSize = getPreferredSizeForPage();

          if (getViewport().getWidth() > pageSize.getWidth())
          {
            scrollToCenter(true);
          }
          else
          {
            JScrollBar scrollBar = getHorizontalScrollBar();

            if (scrollBar != null)
            {
              scrollBar.setValue((scrollBar.getMaximum() / 3) - 4);
            }
          }

          if (getViewport().getHeight() > pageSize.getHeight())
          {
            scrollToCenter(false);
          }
          else
          {
            JScrollBar scrollBar = getVerticalScrollBar();

            if (scrollBar != null)
            {
              scrollBar.setValue((scrollBar.getMaximum() / 4) - 4);
            }
          }
        }
      });
    }
  }

  /**
   *
   */
  public void zoom(final boolean page, final boolean center)
  {
    if (pageVisible && !zooming)
    {
      zooming = true;

      try
      {
        int off = (getPageShadowColor() != null) ? 8 : 0;
       
        // Adds some extra space for the shadow and border
        double width = getViewport().getWidth() - off;
        double height = getViewport().getHeight() - off;

        Dimension d = getPreferredSizeForPage();
        double pageWidth = d.width;
        double pageHeight = d.height;

        double scaleX = width / pageWidth;
        double scaleY = (page) ? height / pageHeight : scaleX;

        // Rounds the new scale to 5% steps
        final double newScale = (double) ((int) (Math.min(scaleX,
            scaleY) * 20)) / 20;

        if (newScale > 0)
        {
          mxGraphView graphView = graph.getView();
          final double scale = graphView.getScale();
          mxPoint translate = (centerPage) ? getPageTranslate(newScale)
              : new mxPoint();
          graphView.scaleAndTranslate(newScale, translate.getX(),
              translate.getY());

          // Causes two repaints, see zoomTo for more details
          final double factor = newScale / scale;

          SwingUtilities.invokeLater(new Runnable()
          {
            public void run()
            {
              if (center)
              {
                if (page)
                {
                  scrollToCenter(true);
                  scrollToCenter(false);
                }
                else
                {
                  scrollToCenter(true);
                  maintainScrollBar(false, factor, false);
                }
              }
              else if (factor != 1)
              {
                maintainScrollBar(true, factor, false);
                maintainScrollBar(false, factor, false);
              }
            }
          });
        }
      }
      finally
      {
        zooming = false;
      }
    }
  }

  /**
   *
   */
  protected void maintainScrollBar(boolean horizontal, double factor,
      boolean center)
  {
    JScrollBar scrollBar = (horizontal) ? getHorizontalScrollBar()
        : getVerticalScrollBar();

    if (scrollBar != null)
    {
      BoundedRangeModel model = scrollBar.getModel();
      int newValue = (int) Math.round(model.getValue() * factor)
          + (int) Math.round((center) ? (model.getExtent()
              * (factor - 1) / 2) : 0);
      model.setValue(newValue);
    }
  }

  /**
   *
   */
  public void scrollToCenter(boolean horizontal)
  {
    JScrollBar scrollBar = (horizontal) ? getHorizontalScrollBar()
        : getVerticalScrollBar();

    if (scrollBar != null)
    {
      final BoundedRangeModel model = scrollBar.getModel();
      final int newValue = ((model.getMaximum()) / 2) - model.getExtent()
          / 2;
      model.setValue(newValue);
    }
  }

  /**
   * Scrolls the graph so that it shows the given cell.
   *
   * @param cell
   */
  public void scrollCellToVisible(Object cell)
  {
    scrollCellToVisible(cell, false);
  }

  /**
   * Scrolls the graph so that it shows the given cell.
   *
   * @param cell
   */
  public void scrollCellToVisible(Object cell, boolean center)
  {
    mxCellState state = graph.getView().getState(cell);

    if (state != null)
    {
      mxRectangle bounds = state;

      if (center)
      {
        bounds = (mxRectangle) bounds.clone();

        bounds.setX(bounds.getCenterX() - getWidth() / 2);
        bounds.setWidth(getWidth());
        bounds.setY(bounds.getCenterY() - getHeight() / 2);
        bounds.setHeight(getHeight());
      }

      getGraphControl().scrollRectToVisible(bounds.getRectangle());
    }
  }

  /**
   *
   * @param x
   * @param y
   * @return Returns the cell at the given location.
   */
  public Object getCellAt(int x, int y)
  {
    return getCellAt(x, y, true);
  }

  /**
   *
   * @param x
   * @param y
   * @param hitSwimlaneContent
   * @return Returns the cell at the given location.
   */
  public Object getCellAt(int x, int y, boolean hitSwimlaneContent)
  {
    return getCellAt(x, y, hitSwimlaneContent, null);
  }

  /**
   * Returns the bottom-most cell that intersects the given point (x, y) in
   * the cell hierarchy starting at the given parent.
   *
   * @param x
   *            X-coordinate of the location to be checked.
   * @param y
   *            Y-coordinate of the location to be checked.
   * @param parent
   *            <mxCell> that should be used as the root of the recursion.
   *            Default is <defaultParent>.
   * @return Returns the child at the given location.
   */
  public Object getCellAt(int x, int y, boolean hitSwimlaneContent,
      Object parent)
  {
    if (parent == null)
    {
      parent = graph.getDefaultParent();
    }

    if (parent != null)
    {
      Point previousTranslate = canvas.getTranslate();
      double previousScale = canvas.getScale();

      try
      {
        canvas.setScale(graph.getView().getScale());
        canvas.setTranslate(0, 0);

        mxIGraphModel model = graph.getModel();
        mxGraphView view = graph.getView();

        Rectangle hit = new Rectangle(x, y, 1, 1);
        int childCount = model.getChildCount(parent);

        for (int i = childCount - 1; i >= 0; i--)
        {
          Object cell = model.getChildAt(parent, i);
          Object result = getCellAt(x, y, hitSwimlaneContent, cell);

          if (result != null)
          {
            return result;
          }
          else if (graph.isCellVisible(cell))
          {
            mxCellState state = view.getState(cell);

            if (state != null
                && canvas.intersects(this, hit, state)
                && (!graph.isSwimlane(cell)
                    || hitSwimlaneContent || (transparentSwimlaneContent && !canvas
                    .hitSwimlaneContent(this, state, x, y))))
            {
              return cell;
            }
          }
        }
      }
      finally
      {
        canvas.setScale(previousScale);
        canvas.setTranslate(previousTranslate.x, previousTranslate.y);
      }
    }

    return null;
  }

  /**
   *
   */
  public void setSwimlaneSelectionEnabled(boolean value)
  {
    boolean oldValue = swimlaneSelectionEnabled;
    swimlaneSelectionEnabled = value;

    firePropertyChange("swimlaneSelectionEnabled", oldValue,
        swimlaneSelectionEnabled);
  }

  /**
   *
   */
  public boolean isSwimlaneSelectionEnabled()
  {
    return swimlaneSelectionEnabled;
  }

  /**
   *
   */
  public Object[] selectRegion(Rectangle rect, MouseEvent e)
  {
    Object[] cells = getCells(rect);

    if (cells.length > 0)
    {
      selectCellsForEvent(cells, e);
    }
    else if (!graph.isSelectionEmpty() && !e.isConsumed())
    {
      graph.clearSelection();
    }

    return cells;
  }

  /**
   * Returns the cells inside the given rectangle.
   *
   * @return Returns the cells inside the given rectangle.
   */
  public Object[] getCells(Rectangle rect)
  {
    return getCells(rect, null);
  }

  /**
   * Returns the children of the given parent that are contained in the given
   * rectangle (x, y, width, height). The result is added to the optional
   * result array, which is returned from the function. If no result array is
   * specified then a new array is created and returned.
   *
   * @return Returns the children inside the given rectangle.
   */
  public Object[] getCells(Rectangle rect, Object parent)
  {
    Collection<Object> result = new ArrayList<Object>();

    if (rect.width > 0 || rect.height > 0)
    {
      if (parent == null)
      {
        parent = graph.getDefaultParent();
      }

      if (parent != null)
      {
        Point previousTranslate = canvas.getTranslate();
        double previousScale = canvas.getScale();

        try
        {
          canvas.setScale(graph.getView().getScale());
          canvas.setTranslate(0, 0);

          mxIGraphModel model = graph.getModel();
          mxGraphView view = graph.getView();

          int childCount = model.getChildCount(parent);

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

            if (graph.isCellVisible(cell) && state != null)
            {
              if (canvas.contains(this, rect, state))
              {
                result.add(cell);
              }
              else
              {
                result.addAll(Arrays
                    .asList(getCells(rect, cell)));
              }
            }
          }
        }
        finally
        {
          canvas.setScale(previousScale);
          canvas.setTranslate(previousTranslate.x,
              previousTranslate.y);
        }
      }
    }

    return result.toArray();
  }

  /**
   * Selects the cells for the given event.
   */
  public void selectCellsForEvent(Object[] cells, MouseEvent event)
  {
    if (isToggleEvent(event))
    {
      graph.addSelectionCells(cells);
    }
    else
    {
      graph.setSelectionCells(cells);
    }
  }

  /**
   * Selects the cell for the given event.
   */
  public void selectCellForEvent(Object cell, MouseEvent e)
  {
    boolean isSelected = graph.isCellSelected(cell);

    if (isToggleEvent(e))
    {
      if (isSelected)
      {
        graph.removeSelectionCell(cell);
      }
      else
      {
        graph.addSelectionCell(cell);
      }
    }
    else if (!isSelected || graph.getSelectionCount() != 1)
    {
      graph.setSelectionCell(cell);
    }
  }

  /**
   * Returns true if the absolute value of one of the given parameters is
   * greater than the tolerance.
   */
  public boolean isSignificant(double dx, double dy)
  {
    return Math.abs(dx) > tolerance || Math.abs(dy) > tolerance;
  }

  /**
   * Returns the icon used to display the collapsed state of the specified
   * cell state. This returns null for all edges.
   */
  public ImageIcon getFoldingIcon(mxCellState state)
  {
    if (state != null && isFoldingEnabled()
        && !getGraph().getModel().isEdge(state.getCell()))
    {
      Object cell = state.getCell();
      boolean tmp = graph.isCellCollapsed(cell);

      if (graph.isCellFoldable(cell, !tmp))
      {
        return (tmp) ? collapsedIcon : expandedIcon;
      }
    }

    return null;
  }

  /**
   *
   */
  public Rectangle getFoldingIconBounds(mxCellState state, ImageIcon icon)
  {
    mxIGraphModel model = graph.getModel();
    boolean isEdge = model.isEdge(state.getCell());
    double scale = getGraph().getView().getScale();

    int x = (int) Math.round(state.getX() + 4 * scale);
    int y = (int) Math.round(state.getY() + 4 * scale);
    int w = (int) Math.max(8, icon.getIconWidth() * scale);
    int h = (int) Math.max(8, icon.getIconHeight() * scale);

    if (isEdge)
    {
      mxPoint pt = graph.getView().getPoint(state);

      x = (int) pt.getX() - w / 2;
      y = (int) pt.getY() - h / 2;
    }

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

  /**
   *
   */
  public boolean hitFoldingIcon(Object cell, int x, int y)
  {
    if (cell != null)
    {
      mxIGraphModel model = graph.getModel();

      // Draws the collapse/expand icons
      boolean isEdge = model.isEdge(cell);

      if (foldingEnabled && (model.isVertex(cell) || isEdge))
      {
        mxCellState state = graph.getView().getState(cell);

        if (state != null)
        {
          ImageIcon icon = getFoldingIcon(state);

          if (icon != null)
          {
            return getFoldingIconBounds(state, icon).contains(x, y);
          }
        }
      }
    }

    return false;
  }

  /**
   *
   * @param enabled
   */
  public void setToolTips(boolean enabled)
  {
    if (enabled)
    {
      ToolTipManager.sharedInstance().registerComponent(graphControl);
    }
    else
    {
      ToolTipManager.sharedInstance().unregisterComponent(graphControl);
    }
  }

  /**
   *
   */
  public boolean isConnectable()
  {
    return connectionHandler.isEnabled();
  }

  /**
   * @param connectable
   */
  public void setConnectable(boolean connectable)
  {
    connectionHandler.setEnabled(connectable);
  }

  /**
   *
   */
  public boolean isPanning()
  {
    return panningHandler.isEnabled();
  }

  /**
   * @param enabled
   */
  public void setPanning(boolean enabled)
  {
    panningHandler.setEnabled(enabled);
  }

  /**
   * @return the autoScroll
   */
  public boolean isAutoScroll()
  {
    return autoScroll;
  }

  /**
   * @param value
   *            the autoScroll to set
   */
  public void setAutoScroll(boolean value)
  {
    autoScroll = value;
  }

  /**
   * @return the autoExtend
   */
  public boolean isAutoExtend()
  {
    return autoExtend;
  }

  /**
   * @param value
   *            the autoExtend to set
   */
  public void setAutoExtend(boolean value)
  {
    autoExtend = value;
  }

  /**
   * @return the escapeEnabled
   */
  public boolean isEscapeEnabled()
  {
    return escapeEnabled;
  }

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

    firePropertyChange("escapeEnabled", oldValue, escapeEnabled);
  }

  /**
   * @return the escapeEnabled
   */
  public boolean isInvokesStopCellEditing()
  {
    return invokesStopCellEditing;
  }

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

    firePropertyChange("invokesStopCellEditing", oldValue,
        invokesStopCellEditing);
  }

  /**
   * @return the enterStopsCellEditing
   */
  public boolean isEnterStopsCellEditing()
  {
    return enterStopsCellEditing;
  }

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

    firePropertyChange("enterStopsCellEditing", oldValue,
        enterStopsCellEditing);
  }

  /**
   * @return the dragEnabled
   */
  public boolean isDragEnabled()
  {
    return dragEnabled;
  }

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

    firePropertyChange("dragEnabled", oldValue, dragEnabled);
  }

  /**
   * @return the gridVisible
   */
  public boolean isGridVisible()
  {
    return gridVisible;
  }

  /**
   * Fires a property change event for <code>gridVisible</code>.
   *
   * @param value
   *            the gridVisible to set
   */
  public void setGridVisible(boolean value)
  {
    boolean oldValue = gridVisible;
    gridVisible = value;

    firePropertyChange("gridVisible", oldValue, gridVisible);
  }

  /**
   * @return the gridVisible
   */
  public boolean isAntiAlias()
  {
    return antiAlias;
  }

  /**
   * Fires a property change event for <code>antiAlias</code>.
   *
   * @param value
   *            the antiAlias to set
   */
  public void setAntiAlias(boolean value)
  {
    boolean oldValue = antiAlias;
    antiAlias = value;

    firePropertyChange("antiAlias", oldValue, antiAlias);
  }

  /**
   * @return the gridVisible
   */
  public boolean isTextAntiAlias()
  {
    return antiAlias;
  }

  /**
   * Fires a property change event for <code>textAntiAlias</code>.
   *
   * @param value
   *            the textAntiAlias to set
   */
  public void setTextAntiAlias(boolean value)
  {
    boolean oldValue = textAntiAlias;
    textAntiAlias = value;

    firePropertyChange("textAntiAlias", oldValue, textAntiAlias);
  }

  /**
   *
   */
  public float getPreviewAlpha()
  {
    return previewAlpha;
  }

  /**
   *
   */
  public void setPreviewAlpha(float value)
  {
    float oldValue = previewAlpha;
    previewAlpha = value;

    firePropertyChange("previewAlpha", oldValue, previewAlpha);
  }

  /**
   * @return the tripleBuffered
   */
  public boolean isTripleBuffered()
  {
    return tripleBuffered;
  }

  /**
   * Hook for dynamic triple buffering condition.
   */
  public boolean isForceTripleBuffered()
  {
    // LATER: Dynamic condition (cell density) to use triple
    // buffering for a large number of cells on a small rect
    return false;
  }

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

    firePropertyChange("tripleBuffered", oldValue, tripleBuffered);
  }

  /**
   * @return the gridColor
   */
  public Color getGridColor()
  {
    return gridColor;
  }

  /**
   * Fires a property change event for <code>gridColor</code>.
   *
   * @param value
   *            the gridColor to set
   */
  public void setGridColor(Color value)
  {
    Color oldValue = gridColor;
    gridColor = value;

    firePropertyChange("gridColor", oldValue, gridColor);
  }

  /**
   * @return the gridStyle
   */
  public int getGridStyle()
  {
    return gridStyle;
  }

  /**
   * Fires a property change event for <code>gridStyle</code>.
   *
   * @param value
   *            the gridStyle to set
   */
  public void setGridStyle(int value)
  {
    int oldValue = gridStyle;
    gridStyle = value;

    firePropertyChange("gridStyle", oldValue, gridStyle);
  }

  /**
   * Returns importEnabled.
   */
  public boolean isImportEnabled()
  {
    return importEnabled;
  }

  /**
   * Sets importEnabled.
   */
  public void setImportEnabled(boolean value)
  {
    boolean oldValue = importEnabled;
    importEnabled = value;

    firePropertyChange("importEnabled", oldValue, importEnabled);
  }

  /**
   * Returns all cells which may be imported via datatransfer.
   */
  public Object[] getImportableCells(Object[] cells)
  {
    return mxGraphModel.filterCells(cells, new Filter()
    {
      public boolean filter(Object cell)
      {
        return canImportCell(cell);
      }
    });
  }

  /**
   * Returns true if the given cell can be imported via datatransfer. This
   * returns importEnabled.
   */
  public boolean canImportCell(Object cell)
  {
    return isImportEnabled();
  }

  /**
   * @return the exportEnabled
   */
  public boolean isExportEnabled()
  {
    return exportEnabled;
  }

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

    firePropertyChange("exportEnabled", oldValue, exportEnabled);
  }

  /**
   * Returns all cells which may be exported via datatransfer.
   */
  public Object[] getExportableCells(Object[] cells)
  {
    return mxGraphModel.filterCells(cells, new Filter()
    {
      public boolean filter(Object cell)
      {
        return canExportCell(cell);
      }
    });
  }

  /**
   * Returns true if the given cell can be exported via datatransfer.
   */
  public boolean canExportCell(Object cell)
  {
    return isExportEnabled();
  }

  /**
   * @return the foldingEnabled
   */
  public boolean isFoldingEnabled()
  {
    return foldingEnabled;
  }

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

    firePropertyChange("foldingEnabled", oldValue, foldingEnabled);
  }

  /**
   *
   */
  public boolean isEditEvent(MouseEvent e)
  {
    return (e != null) ? e.getClickCount() == 2 : false;
  }

  /**
   *
   * @param event
   * @return Returns true if the given event should toggle selected cells.
   */
  public boolean isCloneEvent(MouseEvent event)
  {
    return (event != null) ? event.isControlDown() : false;
  }

  /**
   *
   * @param event
   * @return Returns true if the given event should toggle selected cells.
   */
  public boolean isToggleEvent(MouseEvent event)
  {
    // NOTE: IsMetaDown always returns true for right-clicks on the Mac, so
    // toggle selection for left mouse buttons requires CMD key to be pressed,
    // but toggle for right mouse buttons requires CTRL to be pressed.
    return (event != null) ? ((mxUtils.IS_MAC) ? ((SwingUtilities
        .isLeftMouseButton(event) && event.isMetaDown()) || (SwingUtilities
        .isRightMouseButton(event) && event.isControlDown()))
        : event.isControlDown())
        : false;
  }

  /**
   *
   * @param event
   * @return Returns true if the given event allows the grid to be applied.
   */
  public boolean isGridEnabledEvent(MouseEvent event)
  {
    return (event != null) ? !event.isAltDown() : false;
  }

  /**
   * Note: This is not used during drag and drop operations due to limitations
   * of the underlying API. To enable this for move operations set dragEnabled
   * to false.
   *
   * @param event
   * @return Returns true if the given event is a panning event.
   */
  public boolean isPanningEvent(MouseEvent event)
  {
    return (event != null) ? event.isShiftDown() && event.isControlDown()
        : false;
  }

  /**
   * Note: This is not used during drag and drop operations due to limitations
   * of the underlying API. To enable this for move operations set dragEnabled
   * to false.
   *
   * @param event
   * @return Returns true if the given event is constrained.
   */
  public boolean isConstrainedEvent(MouseEvent event)
  {
    return (event != null) ? event.isShiftDown() : false;
  }

  /**
   * Note: This is not used during drag and drop operations due to limitations
   * of the underlying API. To enable this for move operations set dragEnabled
   * to false.
   *
   * @param event
   * @return Returns true if the given event is constrained.
   */
  public boolean isForceMarqueeEvent(MouseEvent event)
  {
    return (event != null) ? event.isAltDown() : false;
  }

  /**
   *
   */
  public mxPoint snapScaledPoint(mxPoint pt)
  {
    return snapScaledPoint(pt, 0, 0);
  }

  /**
   *
   */
  public mxPoint snapScaledPoint(mxPoint pt, double dx, double dy)
  {
    if (pt != null)
    {
      double scale = graph.getView().getScale();
      mxPoint trans = graph.getView().getTranslate();

      pt.setX((graph.snap(pt.getX() / scale - trans.getX() + dx / scale) + trans
          .getX()) * scale - dx);
      pt.setY((graph.snap(pt.getY() / scale - trans.getY() + dy / scale) + trans
          .getY()) * scale - dy);
    }

    return pt;
  }

  /**
   * Prints the specified page on the specified graphics using
   * <code>pageFormat</code> for the page format.
   *
   * @param g
   *            The graphics to paint the graph on.
   * @param printFormat
   *            The page format to use for printing.
   * @param page
   *            The page to print
   * @return Returns {@link Printable#PAGE_EXISTS} or
   *         {@link Printable#NO_SUCH_PAGE}.
   */
  public int print(Graphics g, PageFormat printFormat, int page)
  {
    int result = NO_SUCH_PAGE;

    // Disables double-buffering before printing
    RepaintManager currentManager = RepaintManager
        .currentManager(mxGraphComponent.this);
    currentManager.setDoubleBufferingEnabled(false);

    // Gets the current state of the view
    mxGraphView view = graph.getView();

    // Stores the old state of the view
    boolean eventsEnabled = view.isEventsEnabled();
    mxPoint translate = view.getTranslate();

    // Disables firing of scale events so that there is no
    // repaint or update of the original graph while pages
    // are being printed
    view.setEventsEnabled(false);

    // Uses the view to create temporary cell states for each cell
    mxTemporaryCellStates tempStates = new mxTemporaryCellStates(view,
        1 / pageScale);

    try
    {
      view.setTranslate(new mxPoint(0, 0));

      mxGraphics2DCanvas canvas = createCanvas();
      canvas.setGraphics((Graphics2D) g);
      canvas.setScale(1 / pageScale);

      view.revalidate();

      mxRectangle graphBounds = graph.getGraphBounds();
      Dimension pSize = new Dimension((int) Math.ceil(graphBounds.getX()
          + graphBounds.getWidth()) + 1, (int) Math.ceil(graphBounds
          .getY() + graphBounds.getHeight()) + 1);

      int w = (int) (printFormat.getImageableWidth());
      int h = (int) (printFormat.getImageableHeight());
      int cols = (int) Math.max(
          Math.ceil((double) (pSize.width - 5) / (double) w), 1);
      int rows = (int) Math.max(
          Math.ceil((double) (pSize.height - 5) / (double) h), 1);

      if (page < cols * rows)
      {
        int dx = (int) ((page % cols) * printFormat.getImageableWidth());
        int dy = (int) (Math.floor(page / cols) * printFormat
            .getImageableHeight());

        g.translate(-dx + (int) printFormat.getImageableX(), -dy
            + (int) printFormat.getImageableY());
        g.setClip(dx, dy, (int) (dx + printFormat.getWidth()),
            (int) (dy + printFormat.getHeight()));

        graph.drawGraph(canvas);

        result = PAGE_EXISTS;
      }
    }
    finally
    {
      view.setTranslate(translate);

      tempStates.destroy();
      view.setEventsEnabled(eventsEnabled);

      // Enables double-buffering after printing
      currentManager.setDoubleBufferingEnabled(true);
    }

    return result;
  }

  /**
   *
   */
  public mxInteractiveCanvas getCanvas()
  {
    return canvas;
  }

  /**
   *
   */
  public BufferedImage getTripleBuffer()
  {
    return tripleBuffer;
  }

  /**
   * Hook for subclassers to replace the graphics canvas for rendering and and
   * printing. This must be overridden to return a custom canvas if there are
   * any custom shapes.
   */
  public mxInteractiveCanvas createCanvas()
  {
    // NOTE: http://forum.jgraph.com/questions/3354/ reports that we should not
    // pass image observer here as it will cause JVM to enter infinite loop.
    return new mxInteractiveCanvas();
  }

  /**
   *
   * @param state
   *            Cell state for which a handler should be created.
   * @return Returns the handler to be used for the given cell state.
   */
  public mxCellHandler createHandler(mxCellState state)
  {
    if (graph.getModel().isVertex(state.getCell()))
    {
      return new mxVertexHandler(this, state);
    }
    else if (graph.getModel().isEdge(state.getCell()))
    {
      mxEdgeStyleFunction style = graph.getView().getEdgeStyle(state,
          null, null, null);

      if (graph.isLoop(state) || style == mxEdgeStyle.ElbowConnector
          || style == mxEdgeStyle.SideToSide
          || style == mxEdgeStyle.TopToBottom)
      {
        return new mxElbowEdgeHandler(this, state);
      }

      return new mxEdgeHandler(this, state);
    }

    return new mxCellHandler(this, state);
  }

  //
  // Heavyweights
  //

  /**
   * Hook for subclassers to create the array of heavyweights for the given
   * state.
   */
  public Component[] createComponents(mxCellState state)
  {
    return null;
  }

  /**
   *
   */
  public void insertComponent(mxCellState state, Component c)
  {
    getGraphControl().add(c, 0);
  }

  /**
   *
   */
  public void removeComponent(Component c, Object cell)
  {
    if (c.getParent() != null)
    {
      c.getParent().remove(c);
    }
  }

  /**
   *
   */
  public void updateComponent(mxCellState state, Component c)
  {
    int x = (int) state.getX();
    int y = (int) state.getY();
    int width = (int) state.getWidth();
    int height = (int) state.getHeight();

    Dimension s = c.getMinimumSize();

    if (s.width > width)
    {
      x -= (s.width - width) / 2;
      width = s.width;
    }

    if (s.height > height)
    {
      y -= (s.height - height) / 2;
      height = s.height;
    }

    c.setBounds(x, y, width, height);
  }

  /**
   *
   */
  public void updateComponents()
  {
    Object root = graph.getModel().getRoot();
    Hashtable<Object, Component[]> result = updateComponents(root);

    // Components now contains the mappings which are no
    // longer used, the result contains the new mappings
    removeAllComponents(components);
    components = result;

    if (!overlays.isEmpty())
    {
      Hashtable<Object, mxICellOverlay[]> result2 = updateCellOverlays(root);

      // Overlays now contains the mappings from cells which
      // are no longer in the model, the result contains the
      // mappings from cells which still exists, regardless
      // from whether a state exists for a particular cell
      removeAllOverlays(overlays);
      overlays = result2;
    }
  }

  /**
   *
   */
  public void removeAllComponents(Hashtable<Object, Component[]> map)
  {
    Iterator<Map.Entry<Object, Component[]>> it = map.entrySet().iterator();

    while (it.hasNext())
    {
      Map.Entry<Object, Component[]> entry = it.next();
      Component[] c = entry.getValue();

      for (int i = 0; i < c.length; i++)
      {
        removeComponent(c[i], entry.getKey());
      }
    }
  }

  /**
   *
   */
  public void removeAllOverlays(Hashtable<Object, mxICellOverlay[]> map)
  {
    Iterator<Map.Entry<Object, mxICellOverlay[]>> it = map.entrySet()
        .iterator();

    while (it.hasNext())
    {
      Map.Entry<Object, mxICellOverlay[]> entry = it.next();
      mxICellOverlay[] c = entry.getValue();

      for (int i = 0; i < c.length; i++)
      {
        removeCellOverlayComponent(c[i], entry.getKey());
      }
    }
  }

  /**
   *
   */
  public Hashtable<Object, Component[]> updateComponents(Object cell)
  {
    Hashtable<Object, Component[]> result = new Hashtable<Object, Component[]>();
    Component[] c = components.remove(cell);
    mxCellState state = getGraph().getView().getState(cell);

    if (state != null)
    {
      if (c == null)
      {
        c = createComponents(state);

        if (c != null)
        {
          for (int i = 0; i < c.length; i++)
          {
            insertComponent(state, c[i]);
          }
        }
      }

      if (c != null)
      {
        result.put(cell, c);

        for (int i = 0; i < c.length; i++)
        {
          updateComponent(state, c[i]);
        }
      }
    }
    // Puts the component back into the map so that it will be removed
    else if (c != null)
    {
      components.put(cell, c);
    }

    int childCount = getGraph().getModel().getChildCount(cell);

    for (int i = 0; i < childCount; i++)
    {
      result.putAll(updateComponents(getGraph().getModel().getChildAt(
          cell, i)));
    }

    return result;
  }

  //
  // Validation and overlays
  //

  /**
   * Validates the graph by validating each descendant of the given cell or
   * the root of the model. Context is an object that contains the validation
   * state for the complete validation run. The validation errors are attached
   * to their cells using <setWarning>. This function returns true if no
   * validation errors exist in the graph.
   */
  public String validateGraph()
  {
    return validateGraph(graph.getModel().getRoot(),
        new Hashtable<Object, Object>());
  }

  /**
   * Validates the graph by validating each descendant of the given cell or
   * the root of the model. Context is an object that contains the validation
   * state for the complete validation run. The validation errors are attached
   * to their cells using <setWarning>. This function returns true if no
   * validation errors exist in the graph.
   *
   * @param cell
   *            Cell to start the validation recursion.
   * @param context
   *            Object that represents the global validation state.
   */
  public String validateGraph(Object cell, Hashtable<Object, Object> context)
  {
    mxIGraphModel model = graph.getModel();
    mxGraphView view = graph.getView();
    boolean isValid = true;
    int childCount = model.getChildCount(cell);

    for (int i = 0; i < childCount; i++)
    {
      Object tmp = model.getChildAt(cell, i);
      Hashtable<Object, Object> ctx = context;

      if (graph.isValidRoot(tmp))
      {
        ctx = new Hashtable<Object, Object>();
      }

      String warn = validateGraph(tmp, ctx);

      if (warn != null)
      {
        String html = warn.replaceAll("\n", "<br>");
        int len = html.length();
        setCellWarning(tmp, html.substring(0, Math.max(0, len - 4)));
      }
      else
      {
        setCellWarning(tmp, null);
      }

      isValid = isValid && warn == null;
    }

    StringBuffer warning = new StringBuffer();

    // Adds error for invalid children if collapsed (children invisible)
    if (graph.isCellCollapsed(cell) && !isValid)
    {
      warning.append(mxResources.get("containsValidationErrors",
          "Contains Validation Errors") + "\n");
    }

    // Checks edges and cells using the defined multiplicities
    if (model.isEdge(cell))
    {
      String tmp = graph.getEdgeValidationError(cell,
          model.getTerminal(cell, true),
          model.getTerminal(cell, false));

      if (tmp != null)
      {
        warning.append(tmp);
      }
    }
    else
    {
      String tmp = graph.getCellValidationError(cell);

      if (tmp != null)
      {
        warning.append(tmp);
      }
    }

    // Checks custom validation rules
    String err = graph.validateCell(cell, context);

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

    // Updates the display with the warning icons before any potential
    // alerts are displayed
    if (model.getParent(cell) == null)
    {
      view.validate();
    }

    return (warning.length() > 0 || !isValid) ? warning.toString() : null;
  }

  /**
   * Adds an overlay for the specified cell. This method fires an addoverlay
   * event and returns the new overlay.
   *
   * @param cell
   *            Cell to add the overlay for.
   * @param overlay
   *            Overlay to be added for the cell.
   */
  public mxICellOverlay addCellOverlay(Object cell, mxICellOverlay overlay)
  {
    mxICellOverlay[] arr = getCellOverlays(cell);

    if (arr == null)
    {
      arr = new mxICellOverlay[] { overlay };
    }
    else
    {
      mxICellOverlay[] arr2 = new mxICellOverlay[arr.length + 1];
      System.arraycopy(arr, 0, arr2, 0, arr.length);
      arr2[arr.length] = overlay;
      arr = arr2;
    }

    overlays.put(cell, arr);
    mxCellState state = graph.getView().getState(cell);

    if (state != null)
    {
      updateCellOverlayComponent(state, overlay);
    }

    eventSource.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY, "cell",
        cell, "overlay", overlay));

    return overlay;
  }

  /**
   * Returns the array of overlays for the given cell or null, if no overlays
   * are defined.
   *
   * @param cell
   *            Cell whose overlays should be returned.
   */
  public mxICellOverlay[] getCellOverlays(Object cell)
  {
    return overlays.get(cell);
  }

  /**
   * Removes and returns the given overlay from the given cell. This method
   * fires a remove overlay event. If no overlay is given, then all overlays
   * are removed using removeOverlays.
   *
   * @param cell
   *            Cell whose overlay should be removed.
   * @param overlay
   *            Optional overlay to be removed.
   */
  public mxICellOverlay removeCellOverlay(Object cell, mxICellOverlay overlay)
  {
    if (overlay == null)
    {
      removeCellOverlays(cell);
    }
    else
    {
      mxICellOverlay[] arr = getCellOverlays(cell);

      if (arr != null)
      {
        // TODO: Use arraycopy from/to same array to speed this up
        List<mxICellOverlay> list = new ArrayList<mxICellOverlay>(
            Arrays.asList(arr));

        if (list.remove(overlay))
        {
          removeCellOverlayComponent(overlay, cell);
        }

        arr = list.toArray(new mxICellOverlay[list.size()]);
        overlays.put(cell, arr);
      }
    }

    return overlay;
  }

  /**
   * Removes all overlays from the given cell. This method fires a
   * removeoverlay event for each removed overlay and returns the array of
   * overlays that was removed from the cell.
   *
   * @param cell
   *            Cell whose overlays should be removed.
   */
  public mxICellOverlay[] removeCellOverlays(Object cell)
  {
    mxICellOverlay[] ovls = overlays.remove(cell);

    if (ovls != null)
    {
      // Removes the overlays from the cell hierarchy
      for (int i = 0; i < ovls.length; i++)
      {
        removeCellOverlayComponent(ovls[i], cell);
      }
    }

    return ovls;
  }

  /**
   * Notified when an overlay has been removed from the graph. This
   * implementation removes the given overlay from its parent if it is a
   * component inside a component hierarchy.
   */
  protected void removeCellOverlayComponent(mxICellOverlay overlay,
      Object cell)
  {
    if (overlay instanceof Component)
    {
      Component comp = (Component) overlay;

      if (comp.getParent() != null)
      {
        comp.setVisible(false);
        comp.getParent().remove(comp);
        eventSource.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
            "cell", cell, "overlay", overlay));
      }
    }
  }

  /**
   * Notified when an overlay has been removed from the graph. This
   * implementation removes the given overlay from its parent if it is a
   * component inside a component hierarchy.
   */
  protected void updateCellOverlayComponent(mxCellState state,
      mxICellOverlay overlay)
  {
    if (overlay instanceof Component)
    {
      Component comp = (Component) overlay;

      if (comp.getParent() == null)
      {
        getGraphControl().add(comp, 0);
      }

      mxRectangle rect = overlay.getBounds(state);

      if (rect != null)
      {
        comp.setBounds(rect.getRectangle());
        comp.setVisible(true);
      }
      else
      {
        comp.setVisible(false);
      }
    }
  }

  /**
   * Removes all overlays in the graph.
   */
  public void clearCellOverlays()
  {
    clearCellOverlays(null);
  }

  /**
   * Removes all overlays in the graph for the given cell and all its
   * descendants. If no cell is specified then all overlays are removed from
   * the graph. This implementation uses removeOverlays to remove the overlays
   * from the individual cells.
   *
   * @param cell
   *            Optional cell that represents the root of the subtree to
   *            remove the overlays from. Default is the root in the model.
   */
  public void clearCellOverlays(Object cell)
  {
    mxIGraphModel model = graph.getModel();

    if (cell == null)
    {
      cell = model.getRoot();
    }

    removeCellOverlays(cell);

    // Recursively removes all overlays from the children
    int childCount = model.getChildCount(cell);

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

  /**
   * Creates an overlay for the given cell using the warning and image or
   * warningImage and returns the new overlay. If the warning is null or a
   * zero length string, then all overlays are removed from the cell instead.
   *
   * @param cell
   *            Cell whose warning should be set.
   * @param warning
   *            String that represents the warning to be displayed.
   */
  public mxICellOverlay setCellWarning(Object cell, String warning)
  {
    return setCellWarning(cell, warning, null, false);
  }

  /**
   * Creates an overlay for the given cell using the warning and image or
   * warningImage and returns the new overlay. If the warning is null or a
   * zero length string, then all overlays are removed from the cell instead.
   *
   * @param cell
   *            Cell whose warning should be set.
   * @param warning
   *            String that represents the warning to be displayed.
   * @param icon
   *            Optional image to be used for the overlay. Default is
   *            warningImageBasename.
   */
  public mxICellOverlay setCellWarning(Object cell, String warning,
      ImageIcon icon)
  {
    return setCellWarning(cell, warning, icon, false);
  }

  /**
   * Creates an overlay for the given cell using the warning and image or
   * warningImage and returns the new overlay. If the warning is null or a
   * zero length string, then all overlays are removed from the cell instead.
   *
   * @param cell
   *            Cell whose warning should be set.
   * @param warning
   *            String that represents the warning to be displayed.
   * @param icon
   *            Optional image to be used for the overlay. Default is
   *            warningImageBasename.
   * @param select
   *            Optional boolean indicating if a click on the overlay should
   *            select the corresponding cell. Default is false.
   */
  public mxICellOverlay setCellWarning(final Object cell, String warning,
      ImageIcon icon, boolean select)
  {
    if (warning != null && warning.length() > 0)
    {
      icon = (icon != null) ? icon : warningIcon;

      // Creates the overlay with the image and warning
      mxCellOverlay overlay = new mxCellOverlay(icon, warning);

      // Adds a handler for single mouseclicks to select the cell
      if (select)
      {
        overlay.addMouseListener(new MouseAdapter()
        {
          /**
           * Selects the associated cell in the graph
           */
          public void mousePressed(MouseEvent e)
          {
            if (getGraph().isEnabled())
            {
              getGraph().setSelectionCell(cell);
            }
          }
        });

        overlay.setCursor(new Cursor(Cursor.HAND_CURSOR));
      }

      // Sets and returns the overlay in the graph
      return addCellOverlay(cell, overlay);
    }
    else
    {
      removeCellOverlays(cell);
    }

    return null;
  }

  /**
   * Returns a hashtable with all entries from the overlays variable where a
   * cell still exists in the model. The entries are removed from the global
   * hashtable so that the remaining entries reflect those whose cell have
   * been removed from the model. If no state is available for a given cell
   * then its overlays are temporarly removed from the rendering control, but
   * kept in the result.
   */
  public Hashtable<Object, mxICellOverlay[]> updateCellOverlays(Object cell)
  {
    Hashtable<Object, mxICellOverlay[]> result = new Hashtable<Object, mxICellOverlay[]>();
    mxICellOverlay[] c = overlays.remove(cell);
    mxCellState state = getGraph().getView().getState(cell);

    if (c != null)
    {
      if (state != null)
      {
        for (int i = 0; i < c.length; i++)
        {
          updateCellOverlayComponent(state, c[i]);
        }
      }
      else
      {
        for (int i = 0; i < c.length; i++)
        {
          removeCellOverlayComponent(c[i], cell);
        }
      }

      result.put(cell, c);
    }

    int childCount = getGraph().getModel().getChildCount(cell);

    for (int i = 0; i < childCount; i++)
    {
      result.putAll(updateCellOverlays(getGraph().getModel().getChildAt(
          cell, i)));
    }

    return result;
  }

  /**
   *
   */
  protected void paintBackground(Graphics g)
  {
    Rectangle clip = g.getClipBounds();
    Rectangle rect = paintBackgroundPage(g);

    if (isPageVisible())
    {
      g.clipRect(rect.x + 1, rect.y + 1, rect.width - 1, rect.height - 1);
    }

    // Paints the clipped background image
    paintBackgroundImage(g);

    // Paints the grid directly onto the graphics
    paintGrid(g);
    g.setClip(clip);
  }

  /**
   *
   */
  protected Rectangle paintBackgroundPage(Graphics g)
  {
    mxPoint translate = graph.getView().getTranslate();
    double scale = graph.getView().getScale();

    int x0 = (int) Math.round(translate.getX() * scale) - 1;
    int y0 = (int) Math.round(translate.getY() * scale) - 1;

    Dimension d = getPreferredSizeForPage();
    int w = (int) Math.round(d.width * scale) + 2;
    int h = (int) Math.round(d.height * scale) + 2;

    if (isPageVisible())
    {
      // Draws the background behind the page
      Color c = getPageBackgroundColor();
     
      if (c != null)
      {
        g.setColor(c);
        mxUtils.fillClippedRect(g, 0, 0, getGraphControl().getWidth(),
            getGraphControl().getHeight());
      }

      // Draws the page drop shadow
      c = getPageShadowColor();
     
      if (c != null)
      {
        g.setColor(c);
        mxUtils.fillClippedRect(g, x0 + w, y0 + 6, 6, h - 6);
        mxUtils.fillClippedRect(g, x0 + 8, y0 + h, w - 2, 6);
      }

      // Draws the page
      Color bg = getBackground();

      if (getViewport().isOpaque())
      {
        bg = getViewport().getBackground();
      }

      g.setColor(bg);
      mxUtils.fillClippedRect(g, x0 + 1, y0 + 1, w, h);

      // Draws the page border
      c = getPageBorderColor();
     
      if (c != null)
      {
        g.setColor(c);
        g.drawRect(x0, y0, w, h);
      }
    }

    if (isPageBreaksVisible()
        && (horizontalPageCount > 1 || verticalPageCount > 1))
    {
      // Draws the pagebreaks
      // TODO: Use clipping
      Graphics2D g2 = (Graphics2D) g;
      Stroke previousStroke = g2.getStroke();

      g2.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
          BasicStroke.JOIN_MITER, 10.0f, new float[] { 1, 2 }, 0));
      g2.setColor(pageBreakColor);

      for (int i = 1; i <= horizontalPageCount - 1; i++)
      {
        int dx = i * w / horizontalPageCount;
        g2.drawLine(x0 + dx, y0 + 1, x0 + dx, y0 + h);
      }

      for (int i = 1; i <= verticalPageCount - 1; i++)
      {
        int dy = i * h / verticalPageCount;
        g2.drawLine(x0 + 1, y0 + dy, x0 + w, y0 + dy);
      }

      // Restores the graphics
      g2.setStroke(previousStroke);
    }

    return new Rectangle(x0, y0, w, h);
  }

  /**
   *
   */
  protected void paintBackgroundImage(Graphics g)
  {
    if (backgroundImage != null)
    {
      mxPoint translate = graph.getView().getTranslate();
      double scale = graph.getView().getScale();

      g.drawImage(backgroundImage.getImage(),
          (int) (translate.getX() * scale),
          (int) (translate.getY() * scale),
          (int) (backgroundImage.getIconWidth() * scale),
          (int) (backgroundImage.getIconHeight() * scale), this);
    }
  }

  /**
   * Paints the grid onto the given graphics object.
   */
  protected void paintGrid(Graphics g)
  {
    if (isGridVisible())
    {
      g.setColor(getGridColor());
      Rectangle clip = g.getClipBounds();

      if (clip == null)
      {
        clip = getGraphControl().getBounds();
      }

      double left = clip.getX();
      double top = clip.getY();
      double right = left + clip.getWidth();
      double bottom = top + clip.getHeight();

      // Double the grid line spacing if smaller than half the gridsize
      int style = getGridStyle();
      int gridSize = graph.getGridSize();
      int minStepping = gridSize;

      // Smaller stepping for certain styles
      if (style == GRID_STYLE_CROSS || style == GRID_STYLE_DOT)
      {
        minStepping /= 2;
      }

      // Fetches some global display state information
      mxPoint trans = graph.getView().getTranslate();
      double scale = graph.getView().getScale();
      double tx = trans.getX() * scale;
      double ty = trans.getY() * scale;

      // Sets the distance of the grid lines in pixels
      double stepping = gridSize * scale;

      if (stepping < minStepping)
      {
        int count = (int) Math
            .round(Math.ceil(minStepping / stepping) / 2) * 2;
        stepping = count * stepping;
      }

      double xs = Math.floor((left - tx) / stepping) * stepping + tx;
      double xe = Math.ceil(right / stepping) * stepping;
      double ys = Math.floor((top - ty) / stepping) * stepping + ty;
      double ye = Math.ceil(bottom / stepping) * stepping;

      switch (style)
      {
        case GRID_STYLE_CROSS:
        {
          // Sets the dot size
          int cs = (stepping > 16.0) ? 2 : 1;

          for (double x = xs; x <= xe; x += stepping)
          {
            for (double y = ys; y <= ye; y += stepping)
            {
              // FIXME: Workaround for rounding errors when adding
              // stepping to
              // xs or ys multiple times (leads to double grid lines
              // when zoom
              // is set to eg. 121%)
              x = Math.round((x - tx) / stepping) * stepping + tx;
              y = Math.round((y - ty) / stepping) * stepping + ty;

              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 GRID_STYLE_LINE:
        {
          xe += (int) Math.ceil(stepping);
          ye += (int) Math.ceil(stepping);

          int ixs = (int) Math.round(xs);
          int ixe = (int) Math.round(xe);
          int iys = (int) Math.round(ys);
          int iye = (int) Math.round(ye);

          for (double x = xs; x <= xe; x += stepping)
          {
            // FIXME: Workaround for rounding errors when adding
            // stepping to
            // xs or ys multiple times (leads to double grid lines when
            // zoom
            // is set to eg. 121%)
            x = Math.round((x - tx) / stepping) * stepping + tx;

            int ix = (int) Math.round(x);
            g.drawLine(ix, iys, ix, iye);
          }

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

            // FIXME: Workaround for rounding errors when adding
            // stepping to
            // xs or ys multiple times (leads to double grid lines when
            // zoom
            // is set to eg. 121%)
            y = Math.round((y - ty) / stepping) * stepping + ty;

            int iy = (int) Math.round(y);
            g.drawLine(ixs, iy, ixe, iy);
          }

          break;
        }
        case GRID_STYLE_DASHED:
        {
          Graphics2D g2 = (Graphics2D) g;
          Stroke stroke = g2.getStroke();

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

          int ixs = (int) Math.round(xs);
          int ixe = (int) Math.round(xe);
          int iys = (int) Math.round(ys);
          int iye = (int) Math.round(ye);

          // Creates a set of strokes with individual dash offsets
          // for each direction
          Stroke[] strokes = new Stroke[] {
              new BasicStroke(1, BasicStroke.CAP_BUTT,
                  BasicStroke.JOIN_MITER, 1, new float[] { 3,
                      1 }, Math.max(0, iys) % 4),
              new BasicStroke(1, BasicStroke.CAP_BUTT,
                  BasicStroke.JOIN_MITER, 1, new float[] { 2,
                      2 }, Math.max(0, iys) % 4),
              new BasicStroke(1, BasicStroke.CAP_BUTT,
                  BasicStroke.JOIN_MITER, 1, new float[] { 1,
                      1 }, 0),
              new BasicStroke(1, BasicStroke.CAP_BUTT,
                  BasicStroke.JOIN_MITER, 1, new float[] { 2,
                      2 }, Math.max(0, iys) % 4) };

          for (double x = xs; x <= xe; x += stepping)
          {
            g2.setStroke(strokes[((int) (x / stepping))
                % strokes.length]);

            // FIXME: Workaround for rounding errors when adding
            // stepping to
            // xs or ys multiple times (leads to double grid lines when
            // zoom
            // is set to eg. 121%)
            double xx = Math.round((x - tx) / stepping) * stepping
                + tx;

            int ix = (int) Math.round(xx);
            g.drawLine(ix, iys, ix, iye);
          }

          strokes = new Stroke[] {
              new BasicStroke(1, BasicStroke.CAP_BUTT,
                  BasicStroke.JOIN_MITER, 1, new float[] { 3,
                      1 }, Math.max(0, ixs) % 4),
              new BasicStroke(1, BasicStroke.CAP_BUTT,
                  BasicStroke.JOIN_MITER, 1, new float[] { 2,
                      2 }, Math.max(0, ixs) % 4),
              new BasicStroke(1, BasicStroke.CAP_BUTT,
                  BasicStroke.JOIN_MITER, 1, new float[] { 1,
                      1 }, 0),
              new BasicStroke(1, BasicStroke.CAP_BUTT,
                  BasicStroke.JOIN_MITER, 1, new float[] { 2,
                      2 }, Math.max(0, ixs) % 4) };

          for (double y = ys; y <= ye; y += stepping)
          {
            g2.setStroke(strokes[((int) (y / stepping))
                % strokes.length]);

            // FIXME: Workaround for rounding errors when adding
            // stepping to
            // xs or ys multiple times (leads to double grid lines when
            // zoom
            // is set to eg. 121%)
            double yy = Math.round((y - ty) / stepping) * stepping
                + ty;

            int iy = (int) Math.round(yy);
            g.drawLine(ixs, iy, ixe, iy);
          }

          g2.setStroke(stroke);

          break;
        }
        default: // DOT_GRID_MODE
        {
          for (double x = xs; x <= xe; x += stepping)
          {

            for (double y = ys; y <= ye; y += stepping)
            {
              // FIXME: Workaround for rounding errors when adding
              // stepping to
              // xs or ys multiple times (leads to double grid lines
              // when zoom
              // is set to eg. 121%)
              x = Math.round((x - tx) / stepping) * stepping + tx;
              y = Math.round((y - ty) / stepping) * stepping + ty;

              int ix = (int) Math.round(x);
              int iy = (int) Math.round(y);
              g.drawLine(ix, iy, ix, iy);
            }
          }
        }
      }
    }
  }

  //
  // Triple Buffering
  //

  /**
   * Updates the buffer (if one exists) and repaints the given cell state.
   */
  public void redraw(mxCellState state)
  {
    if (state != null)
    {
      Rectangle dirty = state.getBoundingBox().getRectangle();
      repaintTripleBuffer(new Rectangle(dirty));
      dirty = SwingUtilities.convertRectangle(graphControl, dirty, this);
      repaint(dirty);
    }
  }

  /**
   * Checks if the triple buffer exists and creates a new one if it does not.
   * Also compares the size of the buffer with the size of the graph and drops
   * the buffer if it has a different size.
   */
  public void checkTripleBuffer()
  {
    mxRectangle bounds = graph.getGraphBounds();
    int width = (int) Math.ceil(bounds.getX() + bounds.getWidth() + 2);
    int height = (int) Math.ceil(bounds.getY() + bounds.getHeight() + 2);

    if (tripleBuffer != null)
    {
      if (tripleBuffer.getWidth() != width
          || tripleBuffer.getHeight() != height)
      {
        // Resizes the buffer (destroys existing and creates new)
        destroyTripleBuffer();
      }
    }

    if (tripleBuffer == null)
    {
      createTripleBuffer(width, height);
    }
  }

  /**
   * Creates the tripleBufferGraphics and tripleBuffer for the given dimension
   * and draws the complete graph onto the triplebuffer.
   *
   * @param width
   * @param height
   */
  protected void createTripleBuffer(int width, int height)
  {
    try
    {
      tripleBuffer = mxUtils.createBufferedImage(width, height, null);
      tripleBufferGraphics = tripleBuffer.createGraphics();
      mxUtils.setAntiAlias(tripleBufferGraphics, antiAlias, textAntiAlias);

      // Repaints the complete buffer
      repaintTripleBuffer(null);
    }
    catch (OutOfMemoryError error)
    {
      // ignore
    }
  }

  /**
   * Destroys the tripleBuffer and tripleBufferGraphics objects.
   */
  public void destroyTripleBuffer()
  {
    if (tripleBuffer != null)
    {
      tripleBuffer = null;
      tripleBufferGraphics.dispose();
      tripleBufferGraphics = null;
    }
  }

  /**
   * Clears and repaints the triple buffer at the given rectangle or repaints
   * the complete buffer if no rectangle is specified.
   *
   * @param dirty
   */
  public void repaintTripleBuffer(Rectangle dirty)
  {
    if (tripleBuffered && tripleBufferGraphics != null)
    {
      if (dirty == null)
      {
        dirty = new Rectangle(tripleBuffer.getWidth(),
            tripleBuffer.getHeight());
      }

      // Clears and repaints the dirty rectangle using the
      // graphics canvas as a renderer
      mxUtils.clearRect(tripleBufferGraphics, dirty, null);
      tripleBufferGraphics.setClip(dirty);
      graphControl.drawGraph(tripleBufferGraphics, true);
      tripleBufferGraphics.setClip(null);
    }
  }

  //
  // Redirected to event source
  //

  /**
   * @return Returns true if event dispatching is enabled in the event source.
   * @see com.mxgraph.util.mxEventSource#isEventsEnabled()
   */
  public boolean isEventsEnabled()
  {
    return eventSource.isEventsEnabled();
  }

  /**
   * @param eventsEnabled
   * @see com.mxgraph.util.mxEventSource#setEventsEnabled(boolean)
   */
  public void setEventsEnabled(boolean eventsEnabled)
  {
    eventSource.setEventsEnabled(eventsEnabled);
  }

  /**
   * @param eventName
   * @param listener
   * @see com.mxgraph.util.mxEventSource#addListener(java.lang.String,
   *      com.mxgraph.util.mxEventSource.mxIEventListener)
   */
  public void addListener(String eventName, mxIEventListener listener)
  {
    eventSource.addListener(eventName, listener);
  }

  /**
   * @param listener
   *            Listener instance.
   */
  public void removeListener(mxIEventListener listener)
  {
    eventSource.removeListener(listener);
  }

  /**
   * @param eventName
   *            Name of the event.
   * @param listener
   *            Listener instance.
   */
  public void removeListener(mxIEventListener listener, String eventName)
  {
    eventSource.removeListener(listener, eventName);
  }

  /**
   *
   * @author gaudenz
   *
   */
  public class mxGraphControl extends JComponent
  {

    /**
     *
     */
    private static final long serialVersionUID = -8916603170766739124L;

    /**
     * Specifies a translation for painting. This should only be used during
     * mouse drags and must be reset after any interactive repaints. Default
     * is (0,0). This should not be null.
     */
    protected Point translate = new Point(0, 0);

    /**
     *
     */
    public mxGraphControl()
    {
      addMouseListener(new MouseAdapter()
      {
        public void mouseReleased(MouseEvent e)
        {
          if (translate.x != 0 || translate.y != 0)
          {
            translate = new Point(0, 0);
            repaint();
          }
        }
      });
    }

    /**
     * Returns the translate.
     */
    public Point getTranslate()
    {
      return translate;
    }

    /**
     * Sets the translate.
     */
    public void setTranslate(Point value)
    {
      translate = value;
    }

    /**
     *
     */
    public mxGraphComponent getGraphContainer()
    {
      return mxGraphComponent.this;
    }

    /**
     * Overrides parent method to add extend flag for making the control
     * larger during previews.
     */
    public void scrollRectToVisible(Rectangle aRect, boolean extend)
    {
      super.scrollRectToVisible(aRect);

      if (extend)
      {
        extendComponent(aRect);
      }
    }

    /**
     * Implements extension of the component in all directions. For
     * extension below the origin (into negative space) the translate will
     * temporaly be used and reset with the next mouse released event.
     */
    protected void extendComponent(Rectangle rect)
    {
      int right = rect.x + rect.width;
      int bottom = rect.y + rect.height;

      Dimension d = new Dimension(getPreferredSize());
      Dimension sp = getScaledPreferredSizeForGraph();
      mxRectangle min = graph.getMinimumGraphSize();
      double scale = graph.getView().getScale();
      boolean update = false;

      if (rect.x < 0)
      {
        translate.x = Math.max(translate.x, Math.max(0, -rect.x));
        d.width = sp.width;

        if (min != null)
        {
          d.width = (int) Math.max(d.width,
              Math.round(min.getWidth() * scale));
        }

        d.width += translate.x;
        update = true;
      }
      else if (right > getWidth())
      {
        d.width = Math.max(right, getWidth());
        update = true;
      }

      if (rect.y < 0)
      {
        translate.y = Math.max(translate.y, Math.max(0, -rect.y));
        d.height = sp.height;

        if (min != null)
        {
          d.height = (int) Math.max(d.height,
              Math.round(min.getHeight() * scale));
        }

        d.height += translate.y;
        update = true;
      }
      else if (bottom > getHeight())
      {
        d.height = Math.max(bottom, getHeight());
        update = true;
      }

      if (update)
      {
        setPreferredSize(d);
        setMinimumSize(d);
        revalidate();
      }
    }

    /**
     *
     */
    public String getToolTipText(MouseEvent e)
    {
      String tip = getSelectionCellsHandler().getToolTipText(e);

      if (tip == null)
      {
        Object cell = getCellAt(e.getX(), e.getY());

        if (cell != null)
        {
          if (hitFoldingIcon(cell, e.getX(), e.getY()))
          {
            tip = mxResources.get("collapse-expand");
          }
          else
          {
            tip = graph.getToolTipForCell(cell);
          }
        }
      }

      if (tip != null && tip.length() > 0)
      {
        return tip;
      }

      return super.getToolTipText(e);
    }

    /**
     * Updates the preferred size for the given scale if the page size
     * should be preferred or the page is visible.
     */
    public void updatePreferredSize()
    {
      double scale = graph.getView().getScale();
      Dimension d = null;

      if (preferPageSize || pageVisible)
      {
        Dimension page = getPreferredSizeForPage();

        if (!preferPageSize)
        {
          page.width += 2 * getHorizontalPageBorder();
          page.height += 2 * getVerticalPageBorder();
        }

        d = new Dimension((int) (page.width * scale),
            (int) (page.height * scale));
      }
      else
      {
        d = getScaledPreferredSizeForGraph();
      }

      mxRectangle min = graph.getMinimumGraphSize();

      if (min != null)
      {
        d.width = (int) Math.max(d.width,
            Math.round(min.getWidth() * scale));
        d.height = (int) Math.max(d.height,
            Math.round(min.getHeight() * scale));
      }

      if (!getPreferredSize().equals(d))
      {
        setPreferredSize(d);
        setMinimumSize(d);
        revalidate();
      }
    }

    /**
     *
     */
    public void paint(Graphics g)
    {
      g.translate(translate.x, translate.y);
      eventSource.fireEvent(new mxEventObject(mxEvent.BEFORE_PAINT, "g",
          g));
      super.paint(g);
      eventSource
          .fireEvent(new mxEventObject(mxEvent.AFTER_PAINT, "g", g));
      g.translate(-translate.x, -translate.y);
    }

    /**
     *
     */
    public void paintComponent(Graphics g)
    {
      super.paintComponent(g);

      // Draws the background
      paintBackground(g);

      // Creates or destroys the triple buffer as needed
      if (tripleBuffered)
      {
        checkTripleBuffer();
      }
      else if (tripleBuffer != null)
      {
        destroyTripleBuffer();
      }

      // Paints the buffer in the canvas onto the dirty region
      if (tripleBuffer != null)
      {
        mxUtils.drawImageClip(g, tripleBuffer, this);
      }

      // Paints the graph directly onto the graphics
      else
      {
        Graphics2D g2 = (Graphics2D) g;
        RenderingHints tmp = g2.getRenderingHints();

        // Sets the graphics in the canvas
        try
        {
          mxUtils.setAntiAlias(g2, antiAlias, textAntiAlias);
          drawGraph(g2, true);
        }
        finally
        {
          // Restores the graphics state
          g2.setRenderingHints(tmp);
        }
      }

      eventSource.fireEvent(new mxEventObject(mxEvent.PAINT, "g", g));
    }

    /**
     *
     */
    public void drawGraph(Graphics2D g, boolean drawLabels)
    {
      Graphics2D previousGraphics = canvas.getGraphics();
      boolean previousDrawLabels = canvas.isDrawLabels();
      Point previousTranslate = canvas.getTranslate();
      double previousScale = canvas.getScale();

      try
      {
        canvas.setScale(graph.getView().getScale());
        canvas.setDrawLabels(drawLabels);
        canvas.setTranslate(0, 0);
        canvas.setGraphics(g);

        // Draws the graph using the graphics canvas
        drawFromRootCell();
      }
      finally
      {
        canvas.setScale(previousScale);
        canvas.setTranslate(previousTranslate.x, previousTranslate.y);
        canvas.setDrawLabels(previousDrawLabels);
        canvas.setGraphics(previousGraphics);
      }
    }

    /**
     * Hook to draw the root cell into the canvas.
     */
    protected void drawFromRootCell()
    {
      drawCell(canvas, graph.getModel().getRoot());
    }

    /**
     *
     */
    protected boolean hitClip(mxGraphics2DCanvas canvas, mxCellState state)
    {
      Rectangle rect = getExtendedCellBounds(state);

      return (rect == null || canvas.getGraphics().hitClip(rect.x,
          rect.y, rect.width, rect.height));
    }

    /**
     * @param state the cached state of the cell whose extended bounds are to be calculated
     * @return the bounds of the cell, including the label and shadow and allowing for rotation
     */
    protected Rectangle getExtendedCellBounds(mxCellState state)
    {
      Rectangle rect = null;

      // Takes rotation into account
      double rotation = mxUtils.getDouble(state.getStyle(),
          mxConstants.STYLE_ROTATION);
      mxRectangle tmp = mxUtils.getBoundingBox(new mxRectangle(state),
          rotation);

      // Adds scaled stroke width
      int border = (int) Math
          .ceil(mxUtils.getDouble(state.getStyle(),
              mxConstants.STYLE_STROKEWIDTH)
              * graph.getView().getScale()) + 1;
      tmp.grow(border);

      if (mxUtils.isTrue(state.getStyle(), mxConstants.STYLE_SHADOW))
      {
        tmp.setWidth(tmp.getWidth() + mxConstants.SHADOW_OFFSETX);
        tmp.setHeight(tmp.getHeight() + mxConstants.SHADOW_OFFSETX);
      }

      // Adds the bounds of the label
      if (state.getLabelBounds() != null)
      {
        tmp.add(state.getLabelBounds());
      }

      rect = tmp.getRectangle();
      return rect;
    }

    /**
     * Draws the given cell onto the specified canvas. This is a modified
     * version of mxGraph.drawCell which paints the label only if the
     * corresponding cell is not being edited and invokes the cellDrawn hook
     * after all descendants have been painted.
     *
     * @param canvas
     *            Canvas onto which the cell should be drawn.
     * @param cell
     *            Cell that should be drawn onto the canvas.
     */
    public void drawCell(mxICanvas canvas, Object cell)
    {
      mxCellState state = graph.getView().getState(cell);

      if (state != null
          && isCellDisplayable(state.getCell())
          && (!(canvas instanceof mxGraphics2DCanvas) || hitClip(
              (mxGraphics2DCanvas) canvas, state)))
      {
        graph.drawState(canvas, state,
            cell != cellEditor.getEditingCell());
      }

      // Handles special ordering for edges (all in foreground
      // or background) or draws all children in order
      boolean edgesFirst = graph.isKeepEdgesInBackground();
      boolean edgesLast = graph.isKeepEdgesInForeground();

      if (edgesFirst)
      {
        drawChildren(cell, true, false);
      }

      drawChildren(cell, !edgesFirst && !edgesLast, true);

      if (edgesLast)
      {
        drawChildren(cell, true, false);
      }

      if (state != null)
      {
        cellDrawn(canvas, state);
      }
    }

    /**
     * Draws the child edges and/or all other children in the given cell
     * depending on the boolean arguments.
     */
    protected void drawChildren(Object cell, boolean edges, boolean others)
    {
      mxIGraphModel model = graph.getModel();
      int childCount = model.getChildCount(cell);

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

        if ((others && !isEdge) || (edges && isEdge))
        {
          drawCell(canvas, model.getChildAt(cell, i));
        }
      }
    }

    /**
     *
     */
    protected void cellDrawn(mxICanvas canvas, mxCellState state)
    {
      if (isFoldingEnabled() && canvas instanceof mxGraphics2DCanvas)
      {
        mxIGraphModel model = graph.getModel();
        mxGraphics2DCanvas g2c = (mxGraphics2DCanvas) canvas;
        Graphics2D g2 = g2c.getGraphics();

        // Draws the collapse/expand icons
        boolean isEdge = model.isEdge(state.getCell());

        if (state.getCell() != graph.getCurrentRoot()
            && (model.isVertex(state.getCell()) || isEdge))
        {
          ImageIcon icon = getFoldingIcon(state);

          if (icon != null)
          {
            Rectangle bounds = getFoldingIconBounds(state, icon);
            g2.drawImage(icon.getImage(), bounds.x, bounds.y,
                bounds.width, bounds.height, this);
          }
        }
      }
    }

    /**
     * Returns true if the given cell is not the current root or the root in
     * the model. This can be overridden to not render certain cells in the
     * graph display.
     */
    protected boolean isCellDisplayable(Object cell)
    {
      return cell != graph.getView().getCurrentRoot()
          && cell != graph.getModel().getRoot();
    }

  }

  /**
   *
   */
  public static class mxMouseRedirector implements MouseListener,
      MouseMotionListener
  {

    /**
     *
     */
    protected mxGraphComponent graphComponent;

    /**
     *
     */
    public mxMouseRedirector(mxGraphComponent graphComponent)
    {
      this.graphComponent = graphComponent;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
     */
    public void mouseClicked(MouseEvent e)
    {
      graphComponent.getGraphControl().dispatchEvent(
          SwingUtilities.convertMouseEvent(e.getComponent(), e,
              graphComponent.getGraphControl()));
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
     */
    public void mouseEntered(MouseEvent e)
    {
      // Redirecting this would cause problems on the Mac
      // and is technically incorrect anyway
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
     */
    public void mouseExited(MouseEvent e)
    {
      mouseClicked(e);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
     */
    public void mousePressed(MouseEvent e)
    {
      mouseClicked(e);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
     */
    public void mouseReleased(MouseEvent e)
    {
      mouseClicked(e);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent
     * )
     */
    public void mouseDragged(MouseEvent e)
    {
      mouseClicked(e);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent
     * )
     */
    public void mouseMoved(MouseEvent e)
    {
      mouseClicked(e);
    }

  }

}
TOP

Related Classes of com.mxgraph.swing.mxGraphComponent$mxMouseRedirector

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.