All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgraph.plaf.basic.BasicGraphUI Maven / Gradle / Ivy

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

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

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

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

import java.awt.*;
import java.awt.geom.*;

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

public class BasicGraphUI extends GraphUI implements Serializable {

	static Class defaultPanel;
	static Class nAryEdge;
	static Class withoutPortsPanel;
	static {
		try {
			defaultPanel = Class.forName("ingenias.editor.cell.DefaultPanel");
			nAryEdge = Class.forName("ingenias.editor.cell.NAryEdge");
			withoutPortsPanel = Class
					.forName("ingenias.editor.rendererxml.WithoutPortsPanel");
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			final static BasicStroke thickline = new BasicStroke(3.0f);

			private Vector highlighted = new Vector();

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

			public BasicGraphUI() {
				super();
			}

			public boolean isHiglighted(DefaultGraphCell dgc) {
				return highlighted.contains(dgc);
			}

			public void higlight(DefaultGraphCell dgc) {
				highlighted.add(dgc);
			}

			public void unhiglight(DefaultGraphCell dgc) {
				highlighted.remove(dgc);
			}

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

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

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

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

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

			//
			// GraphUI methods
			//

			/**
			 * 
			 */

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

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

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

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

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

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

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

			//
			// Selection
			//

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

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

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

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

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

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

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

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

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

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

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

			//
			// Editing
			//

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

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

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

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

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

			//
			// Install methods
			//

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

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

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

				completeUIInstall();
			}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			//
			// Create methods.
			//

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

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

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

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

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

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

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

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

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

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

			//
			// Uninstall methods
			//

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

				completeUIUninstall();
			}

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

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

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

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

			//
			// Painting routines.
			//

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

				Rectangle2D clipBounds = g.getClipBounds();
				if (clipBounds != null) {
					clipBounds = (Rectangle2D) clipBounds.clone();
				}
				if (graph.isDoubleBuffered()) {
					Graphics offGraphics = graph.getOffgraphics();
					Image offscreen = graph.getOffscreen();
					if (offGraphics == null || offscreen == null) {
						drawGraph(g, clipBounds);
						paintOverlay(g);
						return;
					}
					if (offscreen instanceof VolatileImage) {
						int volatileContentsLostCount = 0;
						do {
							offGraphics = graph.getOffgraphics();
							volatileContentsLostCount++;
							if (volatileContentsLostCount > 10) {
								// Assume a problem with the volatile buffering
								// and move to standard buffered images
								graph.setVolatileOffscreen(false);
							}
						} while (((VolatileImage) offscreen).contentsLost());
					}
					g.drawImage(graph.getOffscreen(), 0, 0, graph);

					// Paints the handle and marquee regardless of the double buffering
					// and XOR state so that no artifacts for the marquee appear after
					// scroll
					// if (!graph.isXorEnabled()) {
					// Paint Handle
					if (handle != null) {
						handle.paint(g);
					}

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

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

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

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

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

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

				// Paint cells
				paintCells(g, realClipBounds);

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

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

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

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

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

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

			private static void setFontRecursive(Component[] components,
					java.awt.Font font) {
				for (Component c : components) {
					c.setFont(font);
					if (c instanceof java.awt.Container)
						setFontRecursive(((java.awt.Container) c).getComponents(), font);
				}
			}

			/**
			 * Paints the renderer of view to g at
			 * bounds. Recursive implementation that paints the children
			 * first.
			 * 

* The reciever should NOT modify clipBounds, or * insets. The preview flag is passed to the * renderer, and is not used here. */ public void paintCell(Graphics g, CellView view, Rectangle2D bounds, boolean preview) { // First Paint View if (view != null && bounds != null) { boolean bfocus = (view == this.focus); boolean sel = graph.isCellSelected(view.getCell()); Component component = view.getRendererComponent(graph, sel, bfocus, preview); Stroke oldstr = ((Graphics2D) g).getStroke(); Stroke currentStroke = oldstr; boolean found = false; Vector toPaint = new Vector(); for (Object selected : graph.getSelectionCells()) { if (selected instanceof DefaultGraphCell) toPaint.add((DefaultGraphCell) selected); } toPaint.addAll(highlighted); for (int k = 0; k < toPaint.size(); k++) { if (view.getCell() instanceof DefaultEdge) { found = found || ((DefaultPort) (((DefaultEdge) view.getCell()) .getTarget())).getParent().equals( toPaint.elementAt(k)) || ((DefaultPort) (((DefaultEdge) view.getCell()) .getSource())).getParent().equals( toPaint.elementAt(k)); if (!found) { if (((DefaultPort) (((DefaultEdge) view.getCell()) .getTarget())).getParent() != null && nAryEdge .isAssignableFrom(((DefaultPort) (((DefaultEdge) view .getCell()).getTarget())) .getParent().getClass())) { DefaultGraphCell dgcevaluatewith = (DefaultGraphCell) ((DefaultPort) (((DefaultEdge) view .getCell()).getTarget())).getParent(); found = found || evaluateIfDefaultGraphCellHasToBeHighlighted( dgcevaluatewith, found, toPaint.elementAt(k)); } else { if (((DefaultPort) (((DefaultEdge) view.getCell()) .getSource())).getParent() != null && nAryEdge .isAssignableFrom(((DefaultPort) (((DefaultEdge) view .getCell()).getSource())) .getParent().getClass())) { DefaultGraphCell dgcevaluatewith = (DefaultGraphCell) ((DefaultPort) (((DefaultEdge) view .getCell()).getSource())).getParent(); found = found || evaluateIfDefaultGraphCellHasToBeHighlighted( dgcevaluatewith, found, toPaint.elementAt(k)); } } } } else { if (view.getCell() instanceof DefaultGraphCell) { found = evaluateIfDefaultGraphCellHasToBeHighlighted( (DefaultGraphCell) (view.getCell()), found, toPaint.elementAt(k)); } } found = found || toPaint.elementAt(k).equals(view.getCell()); } final Font oldfont = g.getFont(); if (found) { ((Graphics2D) g).setStroke(thickline); // ((Graphics2D)g).setFont(oldfont.deriveFont(20)); // setFontRecursive(new Component[]{component}, // oldfont.deriveFont(Font.BOLD)); } rendererPane.paintComponent(g, component, graph, (int) bounds.getX(), (int) bounds.getY(), (int) bounds.getWidth(), (int) bounds.getHeight(), true); // restore stroke and font ((Graphics2D) g).setStroke(oldstr); // ((Graphics2D)g).setFont(oldfont); // component.setFont(oldfont); setFontRecursive(new Component[] { component }, oldfont); // continue painting Object cell = view.getCell(); if (cell instanceof DefaultGraphCell) { DefaultGraphCell currentCell = (DefaultGraphCell) cell; Hashtable subcomponentBounds = new Hashtable(); obtainSubComponentBounds(subcomponentBounds, component); if (this.graph.getModel().getAttributes(currentCell) != null) { this.graph.getModel().getAttributes(currentCell) .put("subcomponentbounds", subcomponentBounds); } } } // Then Paint Children if (!view.isLeaf()) { CellView[] children = view.getChildViews(); for (int i = 0; i < children.length; i++) paintCell(g, children[i], children[i].getBounds(), preview); } } private boolean evaluateIfDefaultGraphCellHasToBeHighlighted( DefaultGraphCell dgc, boolean found, DefaultGraphCell toPaint) { if (dgc.getChildCount() > 0) { for (Object objectport : dgc.getChildren()) { if (objectport instanceof DefaultPort) { DefaultPort port = (DefaultPort) objectport; Iterator edges = graph.getModel().edges(port); while (edges.hasNext() && !found) { DefaultEdge next = edges.next(); found = found || ((DefaultPort) (next.getTarget())) .getParent().equals(toPaint) || ((DefaultPort) (next.getSource())) .getParent().equals(toPaint); } } } } return found; } private void obtainSubComponentBounds( Hashtable subcomponentBounds, Component component) { if (component.getName() != null) subcomponentBounds.put(component.getName(), component.getBounds()); if (component instanceof Container) { Container componentPanel = (Container) component; for (Component subcomp : componentPanel.getComponents()) { obtainSubComponentBounds(subcomponentBounds, subcomp); } } } // // Background // /** * Paint the background of this graph. Calls paintGrid. */ protected void paintBackground(Graphics g) { Rectangle clip = g.getClipBounds(); paintBackgroundImage(g, clip); if (graph.isGridVisible()) { paintGrid(graph.getGridSize(), g, clip); } } /** * Hook for subclassers to paint the background image. * * @param g * The graphics object to paint the image on. * @param clip * The clipping region to draw into */ protected void paintBackgroundImage(Graphics g, Rectangle clip) { Component component = graph.getBackgroundComponent(); if (component != null) { paintBackgroundComponent(g, component, clip); } ImageIcon icon = graph.getBackgroundImage(); if (icon == null) { return; } Image backgroundImage = icon.getImage(); if (backgroundImage == null) { return; } Graphics2D g2 = (Graphics2D) g; AffineTransform transform = null; if (graph.isBackgroundScaled()) { transform = g2.getTransform(); g2.scale(graph.getScale(), graph.getScale()); } g2.drawImage(backgroundImage, 0, 0, graph); if (transform != null) { g2.setTransform(transform); } } /** * Requests that the component responsible for painting the background paint * itself * * @param g * The graphics object to paint the image on. * @param component * the component to be painted onto the background image */ protected void paintBackgroundComponent(Graphics g, Component component) { try { g.setPaintMode(); Dimension dim = component.getPreferredSize(); rendererPane.paintComponent(g, component, graph, 0, 0, (int) dim.getWidth(), (int) dim.getHeight(), true); } catch (Exception e) { } catch (Error e) { } } /** * Requests that the component responsible for painting the background paint * itself * * @param g * The graphics object to paint the image on. * @param component * the component to be painted onto the background image * @param clip * The clipping region to draw into */ protected void paintBackgroundComponent(Graphics g, Component component, Rectangle clip) { try { g.setPaintMode(); Dimension dim = component.getPreferredSize(); rendererPane.paintComponent(g, component, graph, 0, 0, (int) dim.getWidth(), (int) dim.getHeight(), true); } catch (Exception e) { } catch (Error e) { } } /** * Paint the grid. */ protected void paintGrid(double gs, Graphics g, Rectangle2D clipBounds) { if (clipBounds == null) { Rectangle2D graphBounds = graph.getBounds(); clipBounds = new Rectangle2D.Double(0, 0, graphBounds.getWidth(), graphBounds.getHeight()); } double xl = clipBounds.getX(); double yt = clipBounds.getY(); double xr = xl + clipBounds.getWidth(); double yb = yt + clipBounds.getHeight(); double sgs = Math.max(2, gs * graph.getScale()); int xs = (int) (Math.floor(xl / sgs) * sgs); int xe = (int) (Math.ceil(xr / sgs) * sgs); int ys = (int) (Math.floor(yt / sgs) * sgs); int ye = (int) (Math.ceil(yb / sgs) * sgs); g.setColor(graph.getGridColor()); switch (graph.getGridMode()) { case JGraph.CROSS_GRID_MODE: { int cs = (sgs > 16.0) ? 2 : ((sgs < 8.0) ? 0 : 1); for (double x = xs; x <= xe; x += sgs) { for (double y = ys; y <= ye; y += sgs) { int ix = (int) Math.round(x); int iy = (int) Math.round(y); g.drawLine(ix - cs, iy, ix + cs, iy); g.drawLine(ix, iy - cs, ix, iy + cs); } } } break; case JGraph.LINE_GRID_MODE: { xe += (int) Math.ceil(sgs); ye += (int) Math.ceil(sgs); for (double x = xs; x <= xe; x += sgs) { int ix = (int) Math.round(x); g.drawLine(ix, ys, ix, ye); } for (double y = ys; y <= ye; y += sgs) { int iy = (int) Math.round(y); g.drawLine(xs, iy, xe, iy); } } break; case JGraph.DOT_GRID_MODE: default: for (double x = xs; x <= xe; x += sgs) { for (double y = ys; y <= ye; y += sgs) { int ix = (int) Math.round(x); int iy = (int) Math.round(y); g.drawLine(ix, iy, ix, iy); } } break; } } // // Foreground // /** * Paint the foreground of this graph. Calls paintPorts. */ protected void paintForeground(Graphics g) { if (graph.isPortsVisible() && graph.isPortsOnTop()) paintPorts(g, graphLayoutCache.getPorts()); } /** * Paint ports. */ public void paintPorts(Graphics g, CellView[] ports) { if (ports != null) { Rectangle r = g.getClipBounds(); for (int i = 0; i < ports.length; i++) { if (ports[i] != null) { Rectangle2D tmp = ports[i].getBounds(); Component parentRenderer = ports[i].getParentView() .getRendererComponent(graph, false, false, false); boolean withoutports = false; try { if (parentRenderer != null && defaultPanel.isAssignableFrom(parentRenderer .getClass()) && withoutPortsPanel .isAssignableFrom(parentRenderer .getClass() .getMethod("getPanelInside", new Class[] {}) .invoke(parentRenderer, new Object[] {}) .getClass())) withoutports = true; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } Rectangle2D bounds = new Rectangle2D.Double(tmp.getX(), tmp.getY(), tmp.getWidth(), tmp.getHeight()); Point2D center = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY()); if (!graph.isPortsScaled()) center = graph.toScreen(center); bounds.setFrame(center.getX() - bounds.getWidth() / 2, center.getY() - bounds.getHeight() / 2, bounds.getWidth(), bounds.getHeight()); if (!withoutports) { if (r == null || bounds.intersects(r)) paintCell(g, ports[i], bounds, false); } } } } } // // Various local methods // /** * Update the handle using createHandle. */ public void updateHandle() { if (graphLayoutCache != null) { Object[] cells = graphLayoutCache.getVisibleCells(graph .getSelectionCells()); if (cells != null && cells.length > 0) handle = createHandle(createContext(graph, cells)); else handle = null; } } protected GraphContext createContext(JGraph graph, Object[] cells) { return new GraphContext(graph, cells); } /** * Constructs the "root handle" for context. * * @param context * reference to the context of the current selection. */ public CellHandle createHandle(GraphContext context) { if (context != null && !context.isEmpty() && graph.isEnabled()) { try { return new RootHandle(context); } catch (HeadlessException e) { // Assume because of running on a server } catch (RuntimeException e) { throw e; } } return null; } /** * Messages the Graph with graphDidChange. */ public void updateSize() { if (graph!=null){ validCachedPreferredSize = false; graph.graphDidChange(); updateHandle(); } } /** * Updates the preferredSize instance variable, which is * returned from getPreferredSize(). */ protected void updateCachedPreferredSize() { // FIXME: Renderer for the views might have an old state Rectangle2D size = AbstractCellView.getBounds(graphLayoutCache .getRoots()); if (size == null) size = new Rectangle2D.Double(); Point2D psize = new Point2D.Double(size.getX() + size.getWidth(), size.getY() + size.getHeight()); Dimension d = graph.getMinimumSize(); Point2D min = (d != null) ? graph .toScreen(new Point(d.width, d.height)) : new Point(0, 0); Point2D scaled = graph.toScreen(psize); preferredSize = new Dimension( (int) Math.max(min.getX(), scaled.getX()), (int) Math.max( min.getY(), scaled.getY())); // Allow for background image ImageIcon image = graph.getBackgroundImage(); if (image != null) { int height = image.getIconHeight(); int width = image.getIconWidth(); Point2D imageSize = graph.toScreen(new Point(width, height)); preferredSize = new Dimension((int) Math.max( preferredSize.getWidth(), imageSize.getX()), (int) Math.max(preferredSize.getHeight(), imageSize.getY())); } Insets in = graph.getInsets(); if (in != null) { preferredSize.setSize( preferredSize.getWidth() + in.left + in.right, preferredSize.getHeight() + in.top + in.bottom); } validCachedPreferredSize = true; } /** * Sets the preferred minimum size. */ public void setPreferredMinSize(Dimension newSize) { preferredMinSize = newSize; } /** * Returns the minimum preferred size. */ public Dimension getPreferredMinSize() { if (preferredMinSize == null) return null; return new Dimension(preferredMinSize); } /** * Returns the preferred size to properly display the graph. */ public Dimension getPreferredSize(JComponent c) { Dimension pSize = this.getPreferredMinSize(); if (!validCachedPreferredSize) updateCachedPreferredSize(); if (graph != null) { if (pSize != null) return new Dimension( Math.max(pSize.width, preferredSize.width), Math.max( pSize.height, preferredSize.height)); return new Dimension(preferredSize.width, preferredSize.height); } else if (pSize != null) return pSize; else return new Dimension(0, 0); } /** * Returns the minimum size for this component. Which will be the min * preferred size or 0, 0. */ public Dimension getMinimumSize(JComponent c) { if (this.getPreferredMinSize() != null) return this.getPreferredMinSize(); return new Dimension(0, 0); } /** * Returns the maximum size for this component, which will be the preferred * size if the instance is currently in a JGraph, or 0, 0. */ public Dimension getMaximumSize(JComponent c) { if (graph != null) return getPreferredSize(graph); if (this.getPreferredMinSize() != null) return this.getPreferredMinSize(); return new Dimension(0, 0); } /** * Messages to stop the editing session. If the UI the receiver is providing * the look and feel for returns true from * getInvokesStopCellEditing, stopCellEditing will invoked on * the current editor. Then completeEditing will be messaged with false, * true, false to cancel any lingering editing. */ protected void completeEditing() { /* If should invoke stopCellEditing, try that */ if (graph.getInvokesStopCellEditing() && stopEditingInCompleteEditing && editingComponent != null) { cellEditor.stopCellEditing(); } /* * Invoke cancelCellEditing, this will do nothing if stopCellEditing was * succesful. */ completeEditing(false, true, false); } /** * Stops the editing session. If messageStop is true the editor is messaged * with stopEditing, if messageCancel is true the editor is messaged with * cancelEditing. If messageGraph is true the graphModel is messaged with * valueForCellChanged. */ protected void completeEditing(boolean messageStop, boolean messageCancel, boolean messageGraph) { if (stopEditingInCompleteEditing && editingComponent != null) { Component oldComponent = editingComponent; Object oldCell = editingCell; GraphCellEditor oldEditor = cellEditor; boolean requestFocus = (graph != null && (graph.hasFocus() || SwingUtilities .findFocusOwner(editingComponent) != null)); editingCell = null; editingComponent = null; if (messageStop) oldEditor.stopCellEditing(); else if (messageCancel) oldEditor.cancelCellEditing(); graph.remove(oldComponent); if (requestFocus) graph.requestFocus(); if (messageGraph) { Object newValue = oldEditor.getCellEditorValue(); graphLayoutCache.valueForCellChanged(oldCell, newValue); } updateSize(); // Remove Editor Listener if (oldEditor != null && cellEditorListener != null) oldEditor.removeCellEditorListener(cellEditorListener); cellEditor = null; } } /** * Will start editing for cell if there is a cellEditor and shouldSelectCell * returns true. *

* This assumes that cell is valid and visible. */ protected boolean startEditing(Object cell, MouseEvent event) { completeEditing(); if (graph.isCellEditable(cell)) { CellView tmp = graphLayoutCache.getMapping(cell, false); cellEditor = tmp.getEditor(); editingComponent = cellEditor.getGraphCellEditorComponent(graph, cell, graph.isCellSelected(cell)); if (cellEditor.isCellEditable(event)) { Rectangle2D cellBounds = graph.getCellBounds(cell); editingCell = cell; Dimension2D editorSize = editingComponent.getPreferredSize(); graph.add(editingComponent); Point2D p = getEditorLocation(cell, editorSize, graph.toScreen(new Point2D.Double(cellBounds.getX(), cellBounds.getY()))); editingComponent.setBounds((int) p.getX(), (int) p.getY(), (int) editorSize.getWidth(), (int) editorSize.getHeight()); editingCell = cell; editingComponent.validate(); // Add Editor Listener if (cellEditorListener == null) cellEditorListener = createCellEditorListener(); if (cellEditor != null && cellEditorListener != null) cellEditor.addCellEditorListener(cellEditorListener); Rectangle2D visRect = graph.getVisibleRect(); graph.paintImmediately((int) p.getX(), (int) p.getY(), (int) (visRect.getWidth() + visRect.getX() - cellBounds .getX()), (int) editorSize.getHeight()); if (cellEditor.shouldSelectCell(event) && graph.isSelectionEnabled()) { stopEditingInCompleteEditing = false; try { graph.setSelectionCell(cell); } catch (Exception e) { System.err.println("Editing exception: " + e); } stopEditingInCompleteEditing = true; } if (event instanceof MouseEvent) { /* * Find the component that will get forwarded all the mouse * events until mouseReleased. */ Point componentPoint = SwingUtilities.convertPoint(graph, new Point(event.getX(), event.getY()), editingComponent); /* * Create an instance of BasicTreeMouseListener to handle * passing the mouse/motion events to the necessary * component. */ // We really want similiar behavior to getMouseEventTarget, // but it is package private. Component activeComponent = SwingUtilities .getDeepestComponentAt(editingComponent, componentPoint.x, componentPoint.y); if (activeComponent != null) { new MouseInputHandler(graph, activeComponent, event); } } return true; } else editingComponent = null; } return false; } /** * Subclassers may override this to provide a better location for the * in-place editing of edges (if you do not inherit from the EdgeRenderer * class). */ protected Point2D getEditorLocation(Object cell, Dimension2D editorSize, Point2D pt) { // Edges have different editor position and size CellView view = graphLayoutCache.getMapping(cell, false); if (view instanceof EdgeView) { EdgeView edgeView = (EdgeView) view; CellViewRenderer renderer = edgeView.getRenderer(); if (renderer instanceof EdgeRenderer) { Point2D tmp = ((EdgeRenderer) renderer) .getLabelPosition(edgeView); if (tmp != null) pt = tmp; else pt = AbstractCellView.getCenterPoint(edgeView); pt.setLocation( Math.max(0, pt.getX() - editorSize.getWidth() / 2), Math.max(0, pt.getY() - editorSize.getHeight() / 2)); } graph.toScreen(pt); } return pt; } /** * Scroll the graph for an event at p. */ public static void autoscroll(JGraph graph, Point p) { Rectangle view = graph.getBounds(); if (graph.getParent() instanceof JViewport) view = ((JViewport) graph.getParent()).getViewRect(); if (view.contains(p)) { Point target = new Point(p); int dx = (int) (graph.getWidth() * SCROLLSTEP); int dy = (int) (graph.getHeight() * SCROLLSTEP); if (target.x - view.x < SCROLLBORDER) target.x -= dx; if (target.y - view.y < SCROLLBORDER) target.y -= dy; if (view.x + view.width - target.x < SCROLLBORDER) target.x += dx; if (view.y + view.height - target.y < SCROLLBORDER) target.y += dy; graph.scrollPointToVisible(target); } } /** * Updates the preferred size when scrolling (if necessary). */ public class ComponentHandler extends ComponentAdapter implements ActionListener { /** * Timer used when inside a scrollpane and the scrollbar is adjusting. */ protected Timer timer; /** ScrollBar that is being adjusted. */ protected JScrollBar scrollBar; public void componentMoved(ComponentEvent e) { if (timer == null) { JScrollPane scrollPane = getScrollPane(); if (scrollPane == null) updateSize(); else { scrollBar = scrollPane.getVerticalScrollBar(); if (scrollBar == null || !scrollBar.getValueIsAdjusting()) { // Try the horizontal scrollbar. if ((scrollBar = scrollPane.getHorizontalScrollBar()) != null && scrollBar.getValueIsAdjusting()) startTimer(); else updateSize(); } else startTimer(); } } } /** * Creates, if necessary, and starts a Timer to check if need to resize * the bounds. */ protected void startTimer() { if (timer == null) { timer = new Timer(200, this); timer.setRepeats(true); } timer.start(); } /** * Returns the JScrollPane housing the JGraph, or null if one isn't * found. */ protected JScrollPane getScrollPane() { Component c = graph.getParent(); while (c != null && !(c instanceof JScrollPane)) c = c.getParent(); if (c instanceof JScrollPane) return (JScrollPane) c; return null; } /** * Public as a result of Timer. If the scrollBar is null, or not * adjusting, this stops the timer and updates the sizing. */ public void actionPerformed(ActionEvent ae) { if (scrollBar == null || !scrollBar.getValueIsAdjusting()) { if (timer != null) timer.stop(); updateSize(); timer = null; scrollBar = null; } } } // End of BasicGraphUI.ComponentHandler /** * Listens for changes in the graph model and updates the view accordingly. */ public class GraphModelHandler implements GraphModelListener, Serializable { JGraph graph; GraphLayoutCache graphLayoutCache; public GraphModelHandler(JGraph graph) { this.graph=graph; graphLayoutCache=this.graph.getGraphLayoutCache(); } public void graphChanged(GraphModelEvent e) { try { Object[] removed = e.getChange().getRemoved(); // Remove from selection & focus if (removed != null && removed.length > 0) { // Update Focus If Necessary if (focus != null) { Object focusedCell = focus.getCell(); for (int i = 0; i < removed.length; i++) { if (removed[i] == focusedCell) { lastFocus = focus; focus = null; break; } } } // Remove from selection graph.getSelectionModel().removeSelectionCells(removed); } Rectangle2D oldDirty = null; Rectangle2D dirtyRegion = e.getChange().getDirtyRegion(); if (dirtyRegion == null) { oldDirty = graph.getClipRectangle(e.getChange()); } if (graphLayoutCache != null) { graphLayoutCache.graphChanged(e.getChange()); } // Get arrays Object[] inserted = e.getChange().getInserted(); Object[] changed = e.getChange().getChanged(); // Insert if (inserted != null && inserted.length > 0) { // Update focus to first inserted cell for (int i = 0; i < inserted.length; i++) graph.updateAutoSize(graphLayoutCache.getMapping( inserted[i], false)); } // Change (update size) if (changed != null && changed.length > 0) { for (int i = 0; i < changed.length; i++) { if (graph != null && graphLayoutCache != null) graph.updateAutoSize(graphLayoutCache.getMapping( changed[i], false)); } } if (dirtyRegion == null) { Rectangle2D newDirtyRegion = graph.getClipRectangle(e .getChange()); dirtyRegion = RectUtils.union(oldDirty, newDirtyRegion); e.getChange().setDirtyRegion(dirtyRegion); } if (dirtyRegion != null && graph != null) { graph.addOffscreenDirty(dirtyRegion); } // Select if not partial if (!graphLayoutCache.isPartial() && graphLayoutCache.isSelectsAllInsertedCells() && graph.isEnabled()) { Object[] roots = DefaultGraphModel.getRoots(graphModel, inserted); if (roots != null && roots.length > 0) { lastFocus = focus; focus = graphLayoutCache.getMapping(roots[0], false); graph.setSelectionCells(roots); } } updateSize(); } catch (Throwable t) { t.printStackTrace(); } } } // End of BasicGraphUI.GraphModelHandler /** * Listens for changes in the graph view and updates the size accordingly. */ public class GraphLayoutCacheHandler implements GraphLayoutCacheListener, Serializable { private JGraph graph; private GraphLayoutCache graphLayoutCache; public GraphLayoutCacheHandler(JGraph graph){ this.graph=graph; graphLayoutCache=graph.getGraphLayoutCache(); } /* * (non-Javadoc) * * @see * org.jgraph.event.GraphLayoutCacheListener#graphLayoutCacheChanged * (org.jgraph.event.GraphLayoutCacheEvent) */ public void graphLayoutCacheChanged(GraphLayoutCacheEvent e) { Object[] changed = e.getChange().getChanged(); if (changed != null && changed.length > 0) { for (int i = 0; i < changed.length; i++) { graph.updateAutoSize(graphLayoutCache.getMapping( changed[i], false)); } } Rectangle2D oldDirtyRegion = e.getChange().getDirtyRegion(); graph.addOffscreenDirty(oldDirtyRegion); Rectangle2D newDirtyRegion = graph.getClipRectangle(e.getChange()); graph.addOffscreenDirty(newDirtyRegion); Object[] inserted = e.getChange().getInserted(); if (inserted != null && inserted.length > 0 && graphLayoutCache.isSelectsLocalInsertedCells() && !(graphLayoutCache.isSelectsAllInsertedCells() && !graphLayoutCache .isPartial()) && graph.isEnabled()) { Object[] roots = DefaultGraphModel.getRoots(graphModel, inserted); if (roots != null && roots.length > 0) { lastFocus = focus; focus = graphLayoutCache.getMapping(roots[0], false); graph.setSelectionCells(roots); } } updateSize(); } } // End of BasicGraphUI.GraphLayoutCacheHandler /** * Listens for changes in the selection model and updates the display * accordingly. */ public class GraphSelectionHandler implements GraphSelectionListener, Serializable { /** * Messaged when the selection changes in the graph we're displaying * for. Stops editing, updates handles and displays the changed cells. */ public void valueChanged(GraphSelectionEvent event) { // cancelEditing(graph); updateHandle(); Object[] cells = event.getCells(); if (cells != null && cells.length <= MAXCLIPCELLS) { Rectangle2D r = graph.toScreen(graph.getCellBounds(cells)); // Includes dirty region of focused cell if (focus != null) { if (r != null) Rectangle2D.union(r, focus.getBounds(), r); else r = focus.getBounds(); } // And last focused cell if (lastFocus != null) { if (r != null) Rectangle2D.union(r, lastFocus.getBounds(), r); else r = lastFocus.getBounds(); } if (r != null) { Rectangle2D unscaledDirty = graph .fromScreen((Rectangle2D) r.clone()); graph.addOffscreenDirty(unscaledDirty); int hsize = (int) graph.getHandleSize() + 1; updateHandle(); Rectangle dirtyRegion = new Rectangle( (int) (r.getX() - hsize), (int) (r.getY() - hsize), (int) (r.getWidth() + 2 * hsize), (int) (r.getHeight() + 2 * hsize)); graph.repaint(dirtyRegion); } } else { Rectangle dirtyRegion = new Rectangle(graph.getSize()); graph.addOffscreenDirty(dirtyRegion); graph.repaint(); } } } /** * Listener responsible for getting cell editing events and updating the * graph accordingly. */ public class CellEditorHandler implements CellEditorListener, Serializable { /** Messaged when editing has stopped in the graph. */ public void editingStopped(ChangeEvent e) { completeEditing(false, false, true); } /** Messaged when editing has been canceled in the graph. */ public void editingCanceled(ChangeEvent e) { completeEditing(false, false, false); } } // BasicGraphUI.CellEditorHandler /** * This is used to get mutliple key down events to appropriately generate * events. */ public class KeyHandler extends KeyAdapter implements Serializable { /** Key code that is being generated for. */ protected Action repeatKeyAction; /** Set to true while keyPressed is active. */ protected boolean isKeyDown; public void keyPressed(KeyEvent e) { if (graph != null && graph.hasFocus() && graph.isEnabled()) { KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers()); if (graph.getConditionForKeyStroke(keyStroke) == JComponent.WHEN_FOCUSED) { ActionListener listener = graph .getActionForKeyStroke(keyStroke); if (listener instanceof Action) repeatKeyAction = (Action) listener; else repeatKeyAction = null; } else { repeatKeyAction = null; if (keyStroke.getKeyCode() == KeyEvent.VK_ESCAPE) { // System.out.println("Calling release on " + marquee); if (marquee != null) marquee.mouseReleased(null); if (mouseListener != null) mouseListener.mouseReleased(null); updateHandle(); graph.refresh(); } } if (isKeyDown && repeatKeyAction != null) { repeatKeyAction.actionPerformed(new ActionEvent(graph, ActionEvent.ACTION_PERFORMED, "")); e.consume(); } else isKeyDown = true; } } public void keyReleased(KeyEvent e) { isKeyDown = false; } } // End of BasicGraphUI.KeyHandler /** * TreeMouseListener is responsible for updating the selection based on * mouse events. */ public class MouseHandler extends MouseAdapter implements MouseMotionListener, Serializable { /* The cell under the mousepointer. */ protected CellView cell; /* The object that handles mouse operations. */ protected Object handler; protected transient Cursor previousCursor = null; /** * Invoked when a mouse button has been pressed on a component. */ public void mousePressed(MouseEvent e) { handler = null; if (!e.isConsumed() && graph.isEnabled()) { graph.requestFocus(); int s = graph.getTolerance(); Rectangle2D r = graph.fromScreen(new Rectangle2D.Double(e .getX() - s, e.getY() - s, 2 * s, 2 * s)); lastFocus = focus; focus = (focus != null && focus.intersects(graph, r)) ? focus : null; cell = graph.getNextSelectableViewAt(focus, e.getX(), e.getY()); if (focus == null) focus = cell; completeEditing(); boolean isForceMarquee = isForceMarqueeEvent(e); boolean isEditable = graph.isGroupsEditable() || (focus != null && focus.isLeaf()); if (!isForceMarquee) { if (e.getClickCount() == graph.getEditClickCount() && focus != null && isEditable && focus.getParentView() == null && graph.isCellEditable(focus.getCell()) && handleEditTrigger(cell.getCell(), e)) { e.consume(); cell = null; } else if (!isToggleSelectionEvent(e)) { if (handle != null) { handle.mousePressed(e); handler = handle; } // Immediate Selection if (!e.isConsumed() && cell != null && !graph.isCellSelected(cell.getCell())) { selectCellForEvent(cell.getCell(), e); focus = cell; if (handle != null) { handle.mousePressed(e); handler = handle; } e.consume(); cell = null; } } } // Marquee Selection if (!e.isConsumed() && marquee != null && (!isToggleSelectionEvent(e) || focus == null || isForceMarquee)) { marquee.mousePressed(e); handler = marquee; } } } /** * Handles edit trigger by starting the edit and return true if the * editing has already started. * * @param cell * the cell being edited * @param e * the mouse event triggering the edit * @return true if the editing has already started */ protected boolean handleEditTrigger(Object cell, MouseEvent e) { graph.scrollCellToVisible(cell); if (cell != null) startEditing(cell, e); return graph.isEditing(); } public void mouseDragged(MouseEvent e) { autoscroll(graph, e.getPoint()); if (graph.isEnabled()) { if (handler != null && handler == marquee) marquee.mouseDragged(e); else if (handler == null && !isEditing(graph) && focus != null) { if (!graph.isCellSelected(focus.getCell())) { selectCellForEvent(focus.getCell(), e); cell = null; } if (handle != null) handle.mousePressed(e); handler = handle; } if (handle != null && handler == handle) handle.mouseDragged(e); } } /** * Invoked when the mouse pointer has been moved on a component (with no * buttons down). */ public void mouseMoved(MouseEvent e) { if (previousCursor == null) { previousCursor = graph.getCursor(); } if (graph != null && graph.isEnabled()) { if (marquee != null) marquee.mouseMoved(e); if (handle != null) handle.mouseMoved(e); if (!e.isConsumed() && previousCursor != null) { Cursor currentCursor = graph.getCursor(); if (currentCursor != previousCursor) { graph.setCursor(previousCursor); } previousCursor = null; } } } // Event may be null when called to cancel the current operation. public void mouseReleased(MouseEvent e) { try { if (e != null && !e.isConsumed() && graph != null && graph.isEnabled()) { if (handler == marquee && marquee != null) marquee.mouseReleased(e); else if (handler == handle && handle != null) handle.mouseReleased(e); if (isDescendant(cell, focus) && e.getModifiers() != 0) { // Do not switch to parent if Special Selection cell = focus; } if (!e.isConsumed() && cell != null) { Object tmp = cell.getCell(); boolean wasSelected = graph.isCellSelected(tmp); if (!e.isPopupTrigger() || !wasSelected) { selectCellForEvent(tmp, e); focus = cell; postProcessSelection(e, tmp, wasSelected); } } } } finally { handler = null; cell = null; } } /** * Invoked after a cell has been selected in the mouseReleased method. * This can be used to do something interesting if the cell was already * selected, in which case this implementation selects the parent. * Override if you want different behaviour, such as start editing. */ protected void postProcessSelection(MouseEvent e, Object cell, boolean wasSelected) { if (wasSelected && graph.isCellSelected(cell) && e.getModifiers() != 0) { Object parent = cell; Object nextParent = null; while (((nextParent = graphModel.getParent(parent)) != null) && graphLayoutCache.isVisible(nextParent)) parent = nextParent; selectCellForEvent(parent, e); lastFocus = focus; focus = graphLayoutCache.getMapping(parent, false); } } protected boolean isDescendant(CellView parentView, CellView childView) { if (parentView == null || childView == null) { return false; } Object parent = parentView.getCell(); Object child = childView.getCell(); Object ancestor = child; do { if (ancestor == parent) return true; } while ((ancestor = graphModel.getParent(ancestor)) != null); return false; } } // End of BasicGraphUI.MouseHandler public class RootHandle implements CellHandle, Serializable { // x and y offset from the mouse press event to the left/top corner of a // view that is returned by a findViewForPoint(). // These are used only when the isSnapSelectedView mode is enabled. protected transient double _mouseToViewDelta_x = 0; protected transient double _mouseToViewDelta_y = 0; protected transient boolean firstDrag = true; /* Temporary views for the cells. */ protected transient CellView[] views; protected transient CellView[] contextViews; protected transient CellView[] portViews; protected transient CellView targetGroup, ignoreTargetGroup; /* Bounds of the cells. Non-null if too many cells. */ protected transient Rectangle2D cachedBounds; /* Initial top left corner of the selection */ protected transient Point2D initialLocation; /* Child handles. Null if too many handles. */ protected transient CellHandle[] handles; /* The point where the mouse was pressed. */ protected transient Point2D start = null, last, snapStart, snapLast; /** Reference to graph off screen graphics */ protected transient Graphics offgraphics; /** * Indicates whether this handle is currently moving cells. Start may be * non-null and isMoving false while the minimum movement has not been * reached. */ protected boolean isMoving = false; /** * Indicates whether this handle has started drag and drop. Note: * isDragging => isMoving. */ protected boolean isDragging = false; /** The handle that consumed the last mousePressedEvent. Initially null. */ protected transient CellHandle activeHandle = null; /* The current selection context, responsible for cloning the cells. */ protected transient GraphContext context; /* * True after the graph was repainted to block xor-ed painting of * background. */ protected boolean isContextVisible = true; protected boolean blockPaint = false; protected Point2D current; /* Defines the Disconnection if DisconnectOnMove is True */ protected transient ConnectionSet disconnect = null; /** * Creates a root handle which contains handles for the given cells. The * root handle and all its childs point to the specified JGraph * instance. The root handle is responsible for dragging the selection. */ public RootHandle(GraphContext ctx) { this.context = ctx; if (!ctx.isEmpty()) { // Temporary cells views = ctx.createTemporaryCellViews(); Rectangle2D tmpBounds = graph.toScreen(graph.getCellBounds(ctx .getCells())); if (ctx.getDescendantCount() < MAXCELLS) { contextViews = ctx.createTemporaryContextViews(); initialLocation = graph.toScreen(getInitialLocation(ctx .getCells())); } else cachedBounds = tmpBounds; if (initialLocation == null && tmpBounds != null) { initialLocation = new Point2D.Double(tmpBounds.getX(), tmpBounds.getY()); } // Sub-Handles Object[] cells = ctx.getCells(); if (cells.length < MAXHANDLES) { handles = new CellHandle[views.length]; for (int i = 0; i < views.length; i++) handles[i] = views[i].getHandle(ctx); // PortView Preview portViews = ctx.createTemporaryPortViews(); } } } /** * Returns the initial location, which is the top left corner of the * selection, ignoring all connected endpoints of edges. */ protected Point2D getInitialLocation(Object[] cells) { if (cells != null && cells.length > 0) { Rectangle2D ret = null; for (int i = 0; i < cells.length; i++) { if (graphModel != null && graphModel.isEdge(cells[i])) { CellView cellView = graphLayoutCache.getMapping( cells[i], false); if (cellView instanceof EdgeView) { EdgeView edgeView = (EdgeView) cellView; if (edgeView.getSource() == null) { Point2D pt = edgeView.getPoint(0); if (pt != null) { if (ret == null) ret = new Rectangle2D.Double(pt.getX(), pt.getY(), 0, 0); else Rectangle2D.union( ret, new Rectangle2D.Double(pt .getX(), pt.getY(), 0, 0), ret); } } if (edgeView.getTarget() == null) { Point2D pt = edgeView.getPoint(edgeView .getPointCount() - 1); if (pt != null) { if (ret == null) ret = new Rectangle2D.Double(pt.getX(), pt.getY(), 0, 0); else Rectangle2D.union( ret, new Rectangle2D.Double(pt .getX(), pt.getY(), 0, 0), ret); } } } } else { Rectangle2D r = graph.getCellBounds(cells[i]); if (r != null) { if (ret == null) ret = (Rectangle2D) r.clone(); Rectangle2D.union(ret, r, ret); } } } if (ret != null) return new Point2D.Double(ret.getX(), ret.getY()); } return null; } /* Returns the context of this root handle. */ public GraphContext getContext() { return context; } /* Paint the handles. Use overlay to paint the current state. */ public void paint(Graphics g) { if (handles != null && handles.length < MAXHANDLES) for (int i = 0; i < handles.length; i++) if (handles[i] != null) handles[i].paint(g); blockPaint = true; if (!graph.isXorEnabled() && current != null) { double dx = current.getX() - start.getX(); double dy = current.getY() - start.getY(); if (dx != 0 || dy != 0) { overlay(g); } } else { blockPaint = true; } } public void overlay(Graphics g) { if (isDragging && !DNDPREVIEW) // BUG IN 1.4.0 (FREEZE) return; if (cachedBounds != null) { // Paint Cached Bounds g.setColor(Color.black); g.drawRect((int) cachedBounds.getX(), (int) cachedBounds.getY(), (int) cachedBounds.getWidth() - 2, (int) cachedBounds.getHeight() - 2); } else { Graphics2D g2 = (Graphics2D) g; AffineTransform oldTransform = g2.getTransform(); g2.scale(graph.getScale(), graph.getScale()); if (views != null) { // Paint Temporary Views for (int i = 0; i < views.length; i++) paintCell(g, views[i], views[i].getBounds(), true); } // Paint temporary context if (contextViews != null && isContextVisible) { for (int i = 0; i < contextViews.length; i++) { paintCell(g, contextViews[i], contextViews[i].getBounds(), true); } } if (!graph.isPortsScaled()) g2.setTransform(oldTransform); if (portViews != null && graph.isPortsVisible()) paintPorts(g, portViews); g2.setTransform(oldTransform); } // Paints the target group to move into if (targetGroup != null) { Rectangle2D b = graph.toScreen((Rectangle2D) targetGroup .getBounds().clone()); g.setColor(graph.getHandleColor()); g.fillRect((int) b.getX() - 1, (int) b.getY() - 1, (int) b.getWidth() + 2, (int) b.getHeight() + 2); g.setColor(graph.getMarqueeColor()); g.draw3DRect((int) b.getX() - 2, (int) b.getY() - 2, (int) b.getWidth() + 3, (int) b.getHeight() + 3, true); } } /** * Invoked when the mouse pointer has been moved on a component (with no * buttons down). */ public void mouseMoved(MouseEvent event) { if (!event.isConsumed() && handles != null) { for (int i = handles.length - 1; i >= 0 && !event.isConsumed(); i--) if (handles[i] != null) handles[i].mouseMoved(event); } } public void mousePressed(MouseEvent event) { if (!event.isConsumed() && graph.isMoveable()) { if (handles != null) { // Find Handle for (int i = handles.length - 1; i >= 0; i--) { if (handles[i] != null) { handles[i].mousePressed(event); if (event.isConsumed()) { activeHandle = handles[i]; return; } } } } if (views != null) { // Start Move if over cell Point2D screenPoint = event.getPoint(); Point2D pt = graph .fromScreen((Point2D) screenPoint.clone()); CellView view = findViewForPoint(pt); if (view != null) { if (snapSelectedView) { Rectangle2D bounds = view.getBounds(); start = graph.toScreen(new Point2D.Double(bounds .getX(), bounds.getY())); snapStart = graph.snap((Point2D) start.clone()); _mouseToViewDelta_x = screenPoint.getX() - start.getX(); _mouseToViewDelta_y = screenPoint.getY() - start.getY(); } else { // this is the original RootHandle's mode. snapStart = graph.snap((Point2D) screenPoint .clone()); _mouseToViewDelta_x = snapStart.getX() - screenPoint.getX(); _mouseToViewDelta_y = snapStart.getY() - screenPoint.getY(); start = (Point2D) snapStart.clone(); } last = (Point2D) start.clone(); snapLast = (Point2D) snapStart.clone(); isContextVisible = contextViews != null && contextViews.length < MAXCELLS && (!event.isControlDown() || !graph .isCloneable()); event.consume(); } } // Analyze for common parent if (graph.isMoveIntoGroups() || graph.isMoveOutOfGroups()) { Object[] cells = context.getCells(); Object ignoreGroup = graph.getModel().getParent(cells[0]); for (int i = 1; i < cells.length; i++) { Object tmp = graph.getModel().getParent(cells[i]); if (ignoreGroup != tmp) { ignoreGroup = null; break; } } if (ignoreGroup != null) ignoreTargetGroup = graph.getGraphLayoutCache() .getMapping(ignoreGroup, false); } } } /** * Hook for subclassers to return a different view for a mouse click at * pt. For example, this can be used to return a leaf cell * instead of a group. */ protected CellView findViewForPoint(Point2D pt) { double snap = graph.getTolerance(); Rectangle2D r = new Rectangle2D.Double(pt.getX() - snap, pt.getY() - snap, 2 * snap, 2 * snap); for (int i = 0; i < views.length; i++) if (views[i].intersects(graph, r)) return views[i]; return null; } /** * Used for move into group to find the target group. */ protected CellView findUnselectedInnermostGroup(double x, double y) { Object[] cells = graph.getDescendants(graph.getRoots()); for (int i = cells.length - 1; i >= 0; i--) { CellView view = graph.getGraphLayoutCache().getMapping( cells[i], false); if (view != null && !view.isLeaf() && !context.contains(view.getCell()) && view.getBounds().contains(x, y)) return view; } return null; } protected void startDragging(MouseEvent event) { isDragging = true; if (graph.isDragEnabled()) { int action = (event.isControlDown() && graph.isCloneable()) ? TransferHandler.COPY : TransferHandler.MOVE; TransferHandler th = graph.getTransferHandler(); setInsertionLocation(event.getPoint()); try { th.exportAsDrag(graph, event, action); } catch (Exception ex) { // Ignore } } } /** * @return Returns the parent graph scrollpane for the specified graph. */ public Component getFirstOpaqueParent(Component component) { if (component != null) { Component parent = component; while (parent != null) { if (parent.isOpaque() && !(parent instanceof JViewport)) return parent; parent = parent.getParent(); } } return component; } protected void initOffscreen() { if (!graph.isXorEnabled()) { return; } try { offgraphics = graph.getOffgraphics(); } catch (Exception e) { offgraphics = null; } catch (Error e) { offgraphics = null; } } /** Process mouse dragged event. */ public void mouseDragged(MouseEvent event) { boolean constrained = isConstrainedMoveEvent(event); boolean spread = false; Rectangle2D dirty = null; if (firstDrag && graph.isDoubleBuffered() && cachedBounds == null) { initOffscreen(); firstDrag = false; } if (event != null && !event.isConsumed()) { if (activeHandle != null) // Paint Active Handle activeHandle.mouseDragged(event); // Invoke Mouse Dragged else if (start != null) { // Move Cells Graphics g = (offgraphics != null) ? offgraphics : graph .getGraphics(); Point ep = event.getPoint(); Point2D point = new Point2D.Double(ep.getX() - _mouseToViewDelta_x, ep.getY() - _mouseToViewDelta_y); Point2D snapCurrent = graph.snap(point); current = snapCurrent; int thresh = graph.getMinimumMove(); double dx = current.getX() - start.getX(); double dy = current.getY() - start.getY(); if (isMoving || Math.abs(dx) > thresh || Math.abs(dy) > thresh) { boolean overlayed = false; isMoving = true; if (disconnect == null && graph.isDisconnectOnMove()) disconnect = context.disconnect(graphLayoutCache .getAllDescendants(views)); // Constrained movement double totDx = current.getX() - start.getX(); double totDy = current.getY() - start.getY(); dx = current.getX() - last.getX(); dy = current.getY() - last.getY(); Point2D constrainedPosition = constrainDrag(event, totDx, totDy, dx, dy); if (constrainedPosition != null) { dx = constrainedPosition.getX(); dy = constrainedPosition.getY(); } double scale = graph.getScale(); dx = dx / scale; dy = dy / scale; // Start Drag and Drop if (graph.isDragEnabled() && !isDragging) startDragging(event); if (dx != 0 || dy != 0) { if (offgraphics != null || !graph.isXorEnabled()) { dirty = graph.toScreen(AbstractCellView .getBounds(views)); Rectangle2D t = graph.toScreen(AbstractCellView .getBounds(contextViews)); if (t != null) dirty.add(t); } if (graph.isXorEnabled()) { g.setColor(graph.getForeground()); // use 'darker' to force XOR to distinguish // between // existing background elements during drag // http://sourceforge.net/tracker/index.php?func=detail&aid=677743&group_id=43118&atid=435210 g.setXORMode(graph.getBackground().darker()); } if (!snapLast.equals(snapStart) && (offgraphics != null || !blockPaint)) { if (graph.isXorEnabled()) { overlay(g); } overlayed = true; } isContextVisible = (!event.isControlDown() || !graph .isCloneable()) && contextViews != null && (contextViews.length < MAXCELLS); blockPaint = false; if (constrained && cachedBounds == null) { // Reset Initial Positions CellView[] all = graphLayoutCache .getAllDescendants(views); for (int i = 0; i < all.length; i++) { CellView orig = graphLayoutCache .getMapping(all[i].getCell(), false); AttributeMap attr = orig.getAllAttributes(); all[i].changeAttributes( graph.getGraphLayoutCache(), (AttributeMap) attr.clone()); all[i].refresh(graph.getGraphLayoutCache(), context, false); } } if (cachedBounds != null) { if (dirty != null) { dirty.add(cachedBounds); } cachedBounds.setFrame(cachedBounds.getX() + dx * scale, cachedBounds.getY() + dy * scale, cachedBounds.getWidth(), cachedBounds.getHeight()); if (dirty != null) { dirty.add(cachedBounds); } } else { // Translate GraphLayoutCache.translateViews(views, dx, dy); if (views != null) graphLayoutCache.update(views); if (contextViews != null) graphLayoutCache.update(contextViews); } // Change preferred size of graph if (graph.isAutoResizeGraph() && (event.getX() > graph.getWidth() - SCROLLBORDER || event.getY() > graph .getHeight() - SCROLLBORDER)) { int SPREADSTEP = 25; Rectangle view = null; if (graph.getParent() instanceof JViewport) view = ((JViewport) graph.getParent()) .getViewRect(); if (view != null) { if (view.contains(event.getPoint())) { if (view.x + view.width - event.getPoint().x < SCROLLBORDER) { preferredSize.width = Math.max( preferredSize.width, (int) view.getWidth()) + SPREADSTEP; spread = true; } if (view.y + view.height - event.getPoint().y < SCROLLBORDER) { preferredSize.height = Math.max( preferredSize.height, (int) view.getHeight()) + SPREADSTEP; spread = true; } if (spread) { graph.revalidate(); autoscroll(graph, event.getPoint()); if (graph.isDoubleBuffered()) initOffscreen(); } } } } // Move into groups Rectangle2D ignoredRegion = (ignoreTargetGroup != null) ? (Rectangle2D) ignoreTargetGroup .getBounds().clone() : null; if (targetGroup != null) { Rectangle2D tmp = graph .toScreen((Rectangle2D) targetGroup .getBounds().clone()); if (dirty != null) dirty.add(tmp); else dirty = tmp; } targetGroup = null; if (graph.isMoveIntoGroups() && (ignoredRegion == null || !ignoredRegion .intersects(AbstractCellView .getBounds(views)))) { targetGroup = (event.isControlDown()) ? null : findUnselectedInnermostGroup( snapCurrent.getX() / scale, snapCurrent.getY() / scale); if (targetGroup == ignoreTargetGroup) targetGroup = null; } if (!snapCurrent.equals(snapStart) && (offgraphics != null || !blockPaint) && !spread) { if (graph.isXorEnabled()) { overlay(g); } overlayed = true; } if (constrained) last = (Point2D) start.clone(); last.setLocation(last.getX() + dx * scale, last.getY() + dy * scale); // It is better to translate last by a // scaled dx/dy // instead of making it to be the // current (as in prev version), // so that the view would be catching up with a // mouse pointer snapLast = snapCurrent; if (overlayed && (offgraphics != null || !graph .isXorEnabled())) { if (dirty == null) { dirty = new Rectangle2D.Double(); } dirty.add(graph.toScreen(AbstractCellView .getBounds(views))); Rectangle2D t = graph.toScreen(AbstractCellView .getBounds(contextViews)); if (t != null) dirty.add(t); // TODO: Should use real ports if portsVisible // and check if ports are scaled int border = PortView.SIZE + 4; if (graph.isPortsScaled()) border = (int) (graph.getScale() * border); int border2 = border / 2; dirty.setFrame(dirty.getX() - border2, dirty.getY() - border2, dirty.getWidth() + border, dirty.getHeight() + border); double sx1 = Math.max(0, dirty.getX()); double sy1 = Math.max(0, dirty.getY()); double sx2 = sx1 + dirty.getWidth(); double sy2 = sy1 + dirty.getHeight(); if (isDragging && !DNDPREVIEW) // BUG IN 1.4.0 // (FREEZE) return; if (offgraphics != null) { graph.drawImage((int) sx1, (int) sy1, (int) sx2, (int) sy2, (int) sx1, (int) sy1, (int) sx2, (int) sy2); } else { graph.repaint((int) dirty.getX(), (int) dirty.getY(), (int) dirty.getWidth() + 1, (int) dirty.getHeight() + 1); } } } } // end if (isMoving or ...) } // end if (start != null) } else if (event == null) graph.repaint(); } /** * Hook method to constrain a mouse drag * * @param event * @param totDx * @param totDy * @param dx * @param dy * @return a point describing any position constraining applied */ protected Point2D constrainDrag(MouseEvent event, double totDx, double totDy, double dx, double dy) { boolean constrained = isConstrainedMoveEvent(event); if (constrained && cachedBounds == null) { if (Math.abs(totDx) < Math.abs(totDy)) { dx = 0; dy = totDy; } else { dx = totDx; dy = 0; } } else { if (!graph.isMoveBelowZero() && last != null && initialLocation != null && start != null) { if (initialLocation.getX() + totDx < 0) { // TODO isn't dx always just 0? dx = start.getX() - last.getX() - initialLocation.getX(); } if (initialLocation.getY() + totDy < 0) { // TODO isn't dy always just 0? dy = start.getY() - last.getY() - initialLocation.getY(); } } if (!graph.isMoveBeyondGraphBounds() && last != null && initialLocation != null && start != null) { Rectangle2D graphBounds = graph.getBounds(); Rectangle2D viewBounds = AbstractCellView.getBounds(views); if (initialLocation.getX() + totDx + viewBounds.getWidth() > graphBounds .getWidth()) dx = 0; if (initialLocation.getY() + totDy + viewBounds.getHeight() > graphBounds .getHeight()) dy = 0; } } return new Point2D.Double(dx, dy); } public void mouseReleased(MouseEvent event) { try { if (event != null && !event.isConsumed()) { if (activeHandle != null) { activeHandle.mouseReleased(event); activeHandle = null; } else if (isMoving && !event.getPoint().equals(start)) { if (cachedBounds != null) { Point ep = event.getPoint(); Point2D point = new Point2D.Double(ep.getX() - _mouseToViewDelta_x, ep.getY() - _mouseToViewDelta_y); Point2D snapCurrent = graph.snap(point); double dx = snapCurrent.getX() - start.getX(); double dy = snapCurrent.getY() - start.getY(); if (!graph.isMoveBelowZero() && initialLocation.getX() + dx < 0) dx = -1 * initialLocation.getX(); if (!graph.isMoveBelowZero() && initialLocation.getY() + dy < 0) dy = -1 * initialLocation.getY(); Point2D tmp = graph.fromScreen(new Point2D.Double( dx, dy)); GraphLayoutCache.translateViews(views, tmp.getX(), tmp.getY()); } CellView[] all = graphLayoutCache .getAllDescendants(views); Map attributes = GraphConstants.createAttributes(all, null); if (event.isControlDown() && graph.isCloneable()) { // Clone // Cells Object[] cells = graph.getDescendants(graph .order(context.getCells())); // Include properties from hidden cells Map hiddenMapping = graphLayoutCache .getHiddenMapping(); for (int i = 0; i < cells.length; i++) { Object witness = attributes.get(cells[i]); if (witness == null) { CellView view = (CellView) hiddenMapping .get(cells[i]); if (view != null && !graphModel.isPort(view .getCell())) { // TODO: Clone required? Same in // GraphConstants. AttributeMap attrs = (AttributeMap) view .getAllAttributes().clone(); // Maybe translate? // attrs.translate(dx, dy); attributes.put(cells[i], attrs.clone()); } } } ConnectionSet cs = ConnectionSet.create(graphModel, cells, false); ParentMap pm = ParentMap.create(graphModel, cells, false, true); cells = graphLayoutCache.insertClones(cells, graph.cloneCells(cells), attributes, cs, pm, 0, 0); } else if (graph.isMoveable()) { // Move Cells ParentMap pm = null; // Moves into group if (targetGroup != null) { pm = new ParentMap(context.getCells(), targetGroup.getCell()); } else if (graph.isMoveOutOfGroups() && (ignoreTargetGroup != null && !ignoreTargetGroup .getBounds().intersects( AbstractCellView .getBounds(views)))) { pm = new ParentMap(context.getCells(), null); } graph.getGraphLayoutCache().edit(attributes, disconnect, pm, null); } event.consume(); } } } catch (Exception e) { e.printStackTrace(); } finally { ignoreTargetGroup = null; targetGroup = null; isDragging = false; disconnect = null; firstDrag = true; current = null; start = null; } } } /** * PropertyChangeListener for the graph. Updates the appropriate variable * and takes the appropriate actions, based on what changes. */ public class PropertyChangeHandler implements PropertyChangeListener, Serializable { public void propertyChange(PropertyChangeEvent event) { if (event.getSource() == graph) { String changeName = event.getPropertyName(); if (changeName.equals("minimumSize")) updateCachedPreferredSize(); else if (changeName.equals(JGraph.GRAPH_MODEL_PROPERTY)) setModel((GraphModel) event.getNewValue()); else if (changeName.equals(JGraph.GRAPH_LAYOUT_CACHE_PROPERTY)) { setGraphLayoutCache((GraphLayoutCache) event.getNewValue()); graph.repaint(); } else if (changeName.equals(JGraph.MARQUEE_HANDLER_PROPERTY)) setMarquee((BasicMarqueeHandler) event.getNewValue()); else if (changeName.equals("transferHandler")) { if (dropTarget != null) dropTarget .removeDropTargetListener(defaultDropTargetListener); dropTarget = graph.getDropTarget(); try { if (dropTarget != null) dropTarget .addDropTargetListener(defaultDropTargetListener); } catch (TooManyListenersException tmle) { // should not happen... swing drop target is multicast } } else if (changeName.equals(JGraph.EDITABLE_PROPERTY)) { boolean editable = ((Boolean) event.getNewValue()) .booleanValue(); if (!editable && isEditing(graph)) cancelEditing(graph); } else if (changeName.equals(JGraph.SELECTION_MODEL_PROPERTY)) setSelectionModel(graph.getSelectionModel()); else if (changeName.equals(JGraph.GRID_VISIBLE_PROPERTY) || changeName.equals(JGraph.GRID_SIZE_PROPERTY) || changeName.equals(JGraph.GRID_COLOR_PROPERTY) || changeName.equals(JGraph.HANDLE_COLOR_PROPERTY) || changeName .equals(JGraph.LOCKED_HANDLE_COLOR_PROPERTY) || changeName.equals(JGraph.HANDLE_SIZE_PROPERTY) || changeName.equals(JGraph.PORTS_VISIBLE_PROPERTY) || changeName.equals(JGraph.ANTIALIASED_PROPERTY)) graph.repaint(); else if (changeName.equals(JGraph.SCALE_PROPERTY)) { updateSize(); } else if (changeName.equals(JGraph.PROPERTY_BACKGROUNDIMAGE)) { updateSize(); } else if (changeName.equals("font")) { completeEditing(); updateSize(); } else if (changeName.equals("componentOrientation")) { if (graph != null) graph.graphDidChange(); } } } } // End of BasicGraphUI.PropertyChangeHandler /** * GraphIncrementAction is used to handle up/down actions. */ public class GraphIncrementAction extends AbstractAction { /** Specifies the direction to adjust the selection by. */ protected int direction; private GraphIncrementAction(int direction, String name) { this.direction = direction; } public void actionPerformed(ActionEvent e) { if (graph != null) { int step = 70; Rectangle rect = graph.getVisibleRect(); if (direction == 1) rect.translate(0, -step); // up else if (direction == 2) rect.translate(step, 0); // right else if (direction == 3) rect.translate(0, step); // down else if (direction == 4) rect.translate(-step, 0); // left graph.scrollRectToVisible(rect); } } public boolean isEnabled() { return (graph != null && graph.isEnabled()); } } // End of class BasicGraphUI.GraphIncrementAction /** * ActionListener that invokes cancelEditing when action performed. */ private class GraphCancelEditingAction extends AbstractAction { public GraphCancelEditingAction(String name) { } public void actionPerformed(ActionEvent e) { if (graph != null) cancelEditing(graph); } public boolean isEnabled() { return (graph != null && graph.isEnabled() && graph.isEditing()); } } // End of class BasicGraphUI.GraphCancelEditingAction /** * ActionListener invoked to start editing on the focused cell. */ private class GraphEditAction extends AbstractAction { public GraphEditAction(String name) { } public void actionPerformed(ActionEvent ae) { if (isEnabled()) { if (getFocusedCell() instanceof GraphCell) { graph.startEditingAtCell(getFocusedCell()); } } } public boolean isEnabled() { return (graph != null && graph.isEnabled()); } } // End of BasicGraphUI.GraphEditAction /** * Action to select everything in the graph. */ private class GraphSelectAllAction extends AbstractAction { private boolean selectAll; public GraphSelectAllAction(String name, boolean selectAll) { this.selectAll = selectAll; } public void actionPerformed(ActionEvent ae) { if (graph != null && graph.isSelectionEnabled()) { if (selectAll) { graph.setSelectionCells(graph.getGraphLayoutCache() .getVisibleCells(graph.getRoots())); } else graph.clearSelection(); } } public boolean isEnabled() { return (graph != null && graph.isEnabled()); } } // End of BasicGraphUI.GraphSelectAllAction /** * MouseInputHandler handles passing all mouse events, including mouse * motion events, until the mouse is released to the destination it is * constructed with. It is assumed all the events are currently target at * source. */ public class MouseInputHandler extends Object implements MouseInputListener { /** Source that events are coming from. */ protected Component source; /** Destination that receives all events. */ protected Component destination; public MouseInputHandler(Component source, Component destination, MouseEvent event) { this.source = source; this.destination = destination; this.source.addMouseListener(this); this.source.addMouseMotionListener(this); /* Dispatch the editing event */ destination.dispatchEvent(SwingUtilities.convertMouseEvent(source, event, destination)); } public void mouseClicked(MouseEvent e) { if (destination != null) destination.dispatchEvent(SwingUtilities.convertMouseEvent( source, e, destination)); } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { if (destination != null) destination.dispatchEvent(SwingUtilities.convertMouseEvent( source, e, destination)); removeFromSource(); } public void mouseEntered(MouseEvent e) { if (!SwingUtilities.isLeftMouseButton(e)) { removeFromSource(); } } public void mouseExited(MouseEvent e) { if (!SwingUtilities.isLeftMouseButton(e)) { removeFromSource(); } // insertionLocation = null; } public void mouseDragged(MouseEvent e) { if (destination != null) destination.dispatchEvent(SwingUtilities.convertMouseEvent( source, e, destination)); } public void mouseMoved(MouseEvent e) { removeFromSource(); } protected void removeFromSource() { if (source != null) { source.removeMouseListener(this); source.removeMouseMotionListener(this); } source = destination = null; } } // End of class BasicGraphUI.MouseInputHandler /** * Graph Drop Target Listener */ public class GraphDropTargetListener extends BasicGraphDropTargetListener implements Serializable { /** * called to save the state of a component in case it needs to be * restored because a drop is not performed. */ protected void saveComponentState(JComponent comp) { } /** * called to restore the state of a component because a drop was not * performed. */ protected void restoreComponentState(JComponent comp) { if (handle != null) handle.mouseDragged(null); } /** * called to set the insertion location to match the current mouse * pointer coordinates. */ protected void updateInsertionLocation(JComponent comp, Point p) { setInsertionLocation(p); if (handle != null) { // How to fetch the shift state? int mod = (dropAction == TransferHandler.COPY) ? InputEvent.CTRL_MASK : 0; handle.mouseDragged(new MouseEvent(comp, 0, 0, mod, p.x, p.y, 1, false)); } } public void dragEnter(DropTargetDragEvent e) { dropAction = e.getDropAction(); super.dragEnter(e); } public void dropActionChanged(DropTargetDragEvent e) { dropAction = e.getDropAction(); super.dropActionChanged(e); } } // End of BasicGraphUI.GraphDropTargetListener /** * @return true if snapSelectedView mode is enabled during the drag * operation. If it is enabled, the view, that is returned by the * findViewForPoint(Point pt), will be snapped to the grid lines.
* By default, findViewForPoint() returns the first view from the * GraphContext whose bounds intersect with snap proximity of a * mouse pointer. If snap-to-grid mode is disabled, views are moved * by a snap increment. */ public boolean isSnapSelectedView() { return snapSelectedView; } /** * Sets the mode of the snapSelectedView drag operation. * * @param snapSelectedView * specifies if the snap-to-grid mode should be applied during a * drag operation. If it is enabled, the view, that is returned * by the findViewForPoint(Point pt), will be snapped to the grid * lines.
* By default, findViewForPoint() returns the first view from the * GraphContext whose bounds intersect with snap proximity of a * mouse pointer. If snap-to-grid mode is disabled, views are * moved by a snap increment. */ public void setSnapSelectedView(boolean snapSelectedView) { this.snapSelectedView = snapSelectedView; } } // End of class BasicGraphUI