/**
 * 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 java.util.logging.Level;
import java.util.logging.Logger;

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 Logger log = Logger.getLogger(mxGraphComponent.class.getName());

	/**
	 * 
	 */
	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();
