/*
* @(#)JGraph.java
*
* Copyright (c) 2001-2004 Gaudenz Alder
*
*/
package org.jgraph;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.accessibility.Accessible;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JViewport;
import javax.swing.RepaintManager;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
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.CellView;
import org.jgraph.graph.ConnectionSet;
import org.jgraph.graph.DefaultCellViewFactory;
import org.jgraph.graph.DefaultEdge;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.DefaultGraphModel;
import org.jgraph.graph.DefaultGraphSelectionModel;
import org.jgraph.graph.DefaultPort;
import org.jgraph.graph.GraphConstants;
import org.jgraph.graph.GraphLayoutCache;
import org.jgraph.graph.GraphModel;
import org.jgraph.graph.GraphSelectionModel;
import org.jgraph.graph.PortView;
import org.jgraph.plaf.GraphUI;
/**
* A control that displays a network of related objects using the well-known
* paradigm of a graph.
* <p>
* A JGraph object doesn't actually contain your data; it simply provides a view
* of the data. Like any non-trivial Swing component, the graph gets data by
* querying its data model.
* <p>
* JGraph displays its data by drawing individual elements. Each element
* displayed by the graph contains exactly one item of data, which is called a
* cell. A cell may either be a vertex or an edge. Vertices may have neighbours
* or not, and edges may have source and target vertices or not, depending on
* whether they are connected.
* <p>
* <strong>Creating a Graph </strong>
* <p>
* The following code creates a JGraph object:
* <p>
* JGraph graph = new JGraph(); <br>
* ... <br>
* JScrollPane graphLayoutCache = new JScrollPane(graph)
* <p>
* The code creates an instance of JGraph and puts it in a scroll pane. JGraphs
* constructor is called with no arguments in this example, which causes the
* constructor to create a sample model.
* <p>
* <strong>Editing </strong>
* <p>
* Outmoved, cloned, resized, and shaped, or connected/disconnected to or from
* other cells.
* <p>
* <strong>Keyboard Bindings </strong>
* <p>
* JGraph defines the following set of keyboard bindings:
* <p>
* <ul>
* <li>Alt-Click forces marquee selection if over a cell.
* <li>Shift- or Ctrl-Select extends or toggles the selection.
* <li>Shift-Drag constrains the offset to one direction.
* <li>Ctrl-Drag clones the selection.
* <li>Doubleclick/F2 starts editing a cell.
* </ul>
* You can change the number of clicks that triggers editing using
* setEditClickCount().
* <p>
* <strong>Customization </strong>
* <p>
* There are a number of additional methods that customize JGraph. For example,
* setMinimumMove() defines the minimum amount of pixels before a move operation
* is initiated. setSnapSize() defines the maximum distance for a cell to be
* selected. setFloatEnabled() enables/disables port floating.
* <p>
* With setDisconnectOnMove() you can indicate if the selected subgraph should
* be disconnected from the unselected rest when a move operation is initiated.
* setDragEnabled() enables/disables the use of Drag And Drop, and
* setDropEnabled() sets if the graph accepts Drops from external sources.
* <p>
* <strong>Customizing a graphs display </strong>
* <p>
* JGraph performs some look-and-feel specific painting. You can customize this
* painting in a limited way. For example, you can modify the grid using
* setGridColor() and setGridSize(), and you can change the handle colors using
* setHandleColor() and setLockedHandleColor().
* <p>
* If you want finer control over the rendering, you can subclass one of the
* default renderers, and extend its paint()-method. A renderer is a
* Component-extension that paints a cell based on its attributes. Thus, neither
* the JGraph nor its look-and-feel-specific implementation actually contain the
* code that paints the cell. Instead, the graph uses the cell renderers
* painting code.
* <p>
* <strong>Selection </strong>
* <p>
* Apart from the single-cell and marquee-selection, JGraphs selection model
* also allows to "step-into" groups, and select children. This feature can be
* disabled using the setAllowsChildSelection() method of the selection model
* instance.
* <p>
* If you are interested in knowing when the selection changes implement the
* <code>GraphSelectionListener</code> interface and add the instance using
* the method <code>addGraphSelectionListener</code>.
* <code>valueChanged</code> will be invoked when the selection changes, that
* is if the user clicks twice on the same vertex <code>valueChanged</code>
* will only be invoked once.
* <p>
* <strong>Change Notification </strong>
* <p>
* If you are interested in handling modifications, implement the
* <code>GraphEventHandler</code> interface and add the instance using the
* method <code>addGraphEventHandler</code>.
* <p>
* For detection of double-clicks or when a user clicks on a cell, regardless of
* whether or not it was selected, I recommend you implement a MouseListener and
* use <code>getFirstCellForLocation</code>.
* <p>
* <strong>Undo Support </strong>
* <p>
* To enable Undo-Support, a <code>GraphUndoManager</code> must be added using
* <code>addGraphSelectionListener</code>. The GraphUndoManager is an
* extension of Swing's <code>GraphUndoManager</code> that maintains a command
* history in the context of multiple views. In this setup, a cell may have a
* set of attributes in each view attached to the model.
* <p>
* For example, consider a position that is stored separately in each view. If a
* node is inserted, the change will be visible in all attached views, resulting
* in a new node that pops-up at the initial position. If the node is
* subsequently moved, say, in view1, this does not constitute a change in
* view2. If view2 does an "undo", the move <i>and </i> the insertion must be
* undone, whereas an "undo" in view1 will only undo the previous move
* operation.
* <p>
* Like all <code>JComponent</code> classes, you can use
* {@link javax.swing.InputMap}and {@link javax.swing.ActionMap}to associate
* an {@link javax.swing.Action}object with a {@link javax.swing.KeyStroke}and
* execute the action under specified conditions.
*
* @author Gaudenz Alder
* @version 2.1 16/03/03
*
*/
public class JGraph
// DO NOT REMOVE OR MODIFY THIS LINE!
extends JComponent // JAVA13:
// org.jgraph.plaf.basic.TransferHandler.JAdapterComponent
implements Scrollable, Accessible, Serializable {
public static final String VERSION = "JGraph (v5.7.3)";
public static final int DOT_GRID_MODE = 0;
public static final int CROSS_GRID_MODE = 1;
public static final int LINE_GRID_MODE = 2;
/**
* @see #getUIClassID
* @see #readObject
*/
private static final String uiClassID = "GraphUI";
/** Creates a new event and passes it off the <code>selectionListeners</code>. */
protected transient GraphSelectionRedirector selectionRedirector;
//
// Bound Properties
//
/**
* The model that defines the graph displayed by this object. Bound
* property.
*/
transient protected GraphModel graphModel;
/**
* The view that defines the display properties of the model. Bound
* property.
*/
transient protected GraphLayoutCache graphLayoutCache;
/** Handler for marquee selection. */
transient protected BasicMarqueeHandler marquee;
/** Models the set of selected objects in this graph. Bound property. */
transient protected GraphSelectionModel selectionModel;
/** Scale of the graph. Default is 1. Bound property. */
protected double scale = 1.0;
/** True if the graph is anti-aliased. Default is false. Bound property. */
protected boolean antiAliased = false;
/** True if the graph allows editing the value of a cell. Bound property. */
protected boolean editable = true;
/**
* True if the graph allows selection of cells. Note: You must also disable
* selectNewCells if you disable this. Bound property.
*/
protected boolean selectionEnabled = true;
/**
* True if the graph allows invalid null ports during previews (aka flip
* back edges). Default is true.
*/
protected boolean previewInvalidNullPorts = true;
/** True if the grid is visible. Bound property. */
protected boolean gridVisible = false;
/** The size of the grid in points. Default is 10. Bound property. */
protected double gridSize = 10;
/** The style of the grid. Use one of the _GRID_MODE constants. */
protected int gridMode = DOT_GRID_MODE;
/** True if the ports are visible. Bound property. */
protected boolean portsVisible = false;
/** True if the ports are scaled. Bound property. */
protected boolean portsScaled = true;
/** True if the graph allows to move cells below zero. */
protected boolean moveBelowZero = false;
//
// Look-And-Feel dependent
//
/** Highlight Color. Changes when the Look-and-Feel changes. */
protected Color highlightColor = Color.green;
/**
* Color of the handles and locked handles. Changes when the Look-and-Feel
* changes.
*/
protected Color handleColor, lockedHandleColor;
/** Color of the marquee. Changes when the Look-and-Feel changes. */
protected Color marqueeColor;
/** The color of the grid. Changes when the Look-and-Feel changes. */
protected Color gridColor;
//
// Datatransfer
//
/**
* True if Drag-and-Drop should be used for move operations. Default is
* false due to a JDK bug.
*/
protected boolean dragEnabled = false;
/**
* True if the graph accepts transfers from other components (graphs). This
* also affects the clipboard. Default is true.
*/
protected boolean dropEnabled = true;
//
// Unbound Properties
//
/** Number of clicks for editing to start. Default is 2 clicks. */
protected int editClickCount = 2;
/** True if the graph allows interactions. Default is true. */
protected boolean enabled = true;
/** True if the snap method should be active (snap to grid). */
protected boolean gridEnabled = false;
/** Size of a handle. Default is 3 pixels. */
protected int handleSize = 3;
/** Maximum distance between a cell and the mousepointer. Default is 4. */
protected int tolerance = 4;
/** Minimum amount of pixels to start a move transaction. Default is 5. */
protected int minimumMove = 5;
/**
* True if getPortViewAt should return the default port if no other port is
* found. Default is false.
*/
protected boolean isJumpToDefaultPort = false;
/**
* Specifies if cells should be added to a group when moved over the group's
* area. Default is false.
*/
protected boolean isMoveIntoGroups = false;
/**
* Specifies if cells should be removed from groups when removed from the
* group area. Default is false.
*/
protected boolean isMoveOutOfGroups = false;
/**
* True if selected edges are disconnected from unselected vertices on move.
* Default is false.
*/
protected boolean disconnectOnMove = false;
/** True if the graph allows move operations. Default is true. */
protected boolean moveable = true;
/** True if the graph allows "ctrl-drag" operations. Default is false. */
protected boolean cloneable = false;
/** True if the graph allows cells to be resized. Default is true. */
protected boolean sizeable = true;
/**
* True if the graph allows points to be modified/added/removed. Default is
* true.
*/
protected boolean bendable = true;
/**
* True if the graph allows new connections to be established. Default is
* true.
*/
protected boolean connectable = true;
/**
* True if the graph allows existing connections to be removed. Default is
* true.
*/
protected boolean disconnectable = true;
/**
* If true, when editing is to be stopped by way of selection changing, data
* in graph changing or other means <code>stopCellEditing</code> is
* invoked, and changes are saved. If false, <code>cancelCellEditing</code>
* is invoked, and changes are discarded.
*/
protected boolean invokesStopCellEditing;
/**
* This is set to true for the life of the setUI call.
*/
private boolean settingUI;
//
// Bound propery names
//
/**
* Bound property name for <code>graphModel</code>.
*/
public final static String GRAPH_MODEL_PROPERTY = "model";
/**
* Bound property name for <code>graphModel</code>.
*/
public final static String GRAPH_LAYOUT_CACHE_PROPERTY = "view";
/**
* Bound property name for <code>graphModel</code>.
*/
public final static String MARQUEE_HANDLER_PROPERTY = "marquee";
/**
* Bound property name for <code>editable</code>.
*/
public final static String EDITABLE_PROPERTY = "editable";
/**
* Bound property name for <code>selectionEnabled</code>.
*/
public final static String SELECTIONENABLED_PROPERTY = "selectionEnabled";
/**
* Bound property name for <code>scale</code>.
*/
public final static String SCALE_PROPERTY = "scale";
/**
* Bound property name for <code>antiAliased</code>.
*/
public final static String ANTIALIASED_PROPERTY = "antiAliased";
/**
* Bound property name for <code>gridSize</code>.
*/
public final static String GRID_SIZE_PROPERTY = "gridSize";
/**
* Bound property name for <code>gridVisible</code>.
*/
public final static String GRID_VISIBLE_PROPERTY = "gridVisible";
/**
* Bound property name for <code>gridColor</code>.
*/
public final static String GRID_COLOR_PROPERTY = "gridColor";
/**
* Bound property name for <code>gridColor</code>.
*/
public final static String HANDLE_COLOR_PROPERTY = "handleColor";
/**
* Bound property name for <code>gridColor</code>.
*/
public final static String HANDLE_SIZE_PROPERTY = "handleSize";
/**
* Bound property name for <code>gridColor</code>.
*/
public final static String LOCKED_HANDLE_COLOR_PROPERTY = "lockedHandleColor";
/**
* Bound property name for <code>gridVisible</code>.
*/
public final static String PORTS_VISIBLE_PROPERTY = "portsVisible";
/**
* Bound property name for <code>portsScaled</code>.
*/
public final static String PORTS_SCALED_PROPERTY = "portsScaled";
/**
* Bound property name for <code>selectionModel</code>.
*/
public final static String SELECTION_MODEL_PROPERTY = "selectionModel";
/**
* Bound property name for <code>messagesStopCellEditing</code>.
*/
public final static String INVOKES_STOP_CELL_EDITING_PROPERTY = "invokesStopCellEditing";
/**
* Creates and returns a sample <code>GraphModel</code>. Used primarily
* for beanbuilders to show something interesting.
*/
public static void addSampleData(GraphModel model) {
ConnectionSet cs = new ConnectionSet();
Map attributes = new Hashtable();
// Styles For Implement/Extend/Aggregation
AttributeMap implementStyle = new AttributeMap();
GraphConstants.setLineBegin(implementStyle,
GraphConstants.ARROW_TECHNICAL);
GraphConstants.setBeginSize(implementStyle, 10);
GraphConstants.setDashPattern(implementStyle, new float[] { 3, 3 });
GraphConstants.setFont(implementStyle, GraphConstants.DEFAULTFONT
.deriveFont(10));
AttributeMap extendStyle = new AttributeMap();
GraphConstants
.setLineBegin(extendStyle, GraphConstants.ARROW_TECHNICAL);
GraphConstants.setBeginFill(extendStyle, true);
GraphConstants.setBeginSize(extendStyle, 10);
GraphConstants.setFont(extendStyle, GraphConstants.DEFAULTFONT
.deriveFont(10));
AttributeMap aggregateStyle = new AttributeMap();
GraphConstants.setLineBegin(aggregateStyle,
GraphConstants.ARROW_DIAMOND);
GraphConstants.setBeginFill(aggregateStyle, true);
GraphConstants.setBeginSize(aggregateStyle, 6);
GraphConstants.setLineEnd(aggregateStyle, GraphConstants.ARROW_SIMPLE);
GraphConstants.setEndSize(aggregateStyle, 8);
GraphConstants.setLabelPosition(aggregateStyle, new Point2D.Double(500,
0));
GraphConstants.setFont(aggregateStyle, GraphConstants.DEFAULTFONT
.deriveFont(10));
//
// The Swing MVC Pattern
//
// Model Column
DefaultGraphCell gm = new DefaultGraphCell("GraphModel");
attributes.put(gm,
createBounds(new AttributeMap(), 20, 100, Color.blue));
gm.add(new DefaultPort("GraphModel/Center"));
DefaultGraphCell dgm = new DefaultGraphCell("DefaultGraphModel");
attributes.put(dgm, createBounds(new AttributeMap(), 20, 180,
Color.blue));
dgm.add(new DefaultPort("DefaultGraphModel/Center"));
DefaultEdge dgmImplementsGm = new DefaultEdge("implements");
cs.connect(dgmImplementsGm, gm.getChildAt(0), dgm.getChildAt(0));
attributes.put(dgmImplementsGm, implementStyle);
DefaultGraphCell modelGroup = new DefaultGraphCell("ModelGroup");
modelGroup.add(gm);
modelGroup.add(dgm);
modelGroup.add(dgmImplementsGm);
// JComponent Column
DefaultGraphCell jc = new DefaultGraphCell("JComponent");
attributes.put(jc, createBounds(new AttributeMap(), 180, 20,
Color.green));
jc.add(new DefaultPort("JComponent/Center"));
DefaultGraphCell jg = new DefaultGraphCell("JGraph");
attributes.put(jg, createBounds(new AttributeMap(), 180, 100,
Color.green));
jg.add(new DefaultPort("JGraph/Center"));
DefaultEdge jgExtendsJc = new DefaultEdge("extends");
cs.connect(jgExtendsJc, jc.getChildAt(0), jg.getChildAt(0));
attributes.put(jgExtendsJc, extendStyle);
// UI Column
DefaultGraphCell cu = new DefaultGraphCell("ComponentUI");
attributes
.put(cu, createBounds(new AttributeMap(), 340, 20, Color.red));
cu.add(new DefaultPort("ComponentUI/Center"));
DefaultGraphCell gu = new DefaultGraphCell("GraphUI");
attributes.put(gu,
createBounds(new AttributeMap(), 340, 100, Color.red));
gu.add(new DefaultPort("GraphUI/Center"));
DefaultGraphCell dgu = new DefaultGraphCell("BasicGraphUI");
attributes.put(dgu, createBounds(new AttributeMap(), 340, 180,
Color.red));
dgu.add(new DefaultPort("BasicGraphUI/Center"));
DefaultEdge guExtendsCu = new DefaultEdge("extends");
cs.connect(guExtendsCu, cu.getChildAt(0), gu.getChildAt(0));
attributes.put(guExtendsCu, extendStyle);
DefaultEdge dguImplementsDu = new DefaultEdge("implements");
cs.connect(dguImplementsDu, gu.getChildAt(0), dgu.getChildAt(0));
attributes.put(dguImplementsDu, implementStyle);
DefaultGraphCell uiGroup = new DefaultGraphCell("UIGroup");
uiGroup.add(cu);
uiGroup.add(gu);
uiGroup.add(dgu);
uiGroup.add(dguImplementsDu);
uiGroup.add(guExtendsCu);
// Aggregations
DefaultEdge jgAggregatesGm = new DefaultEdge("model");
cs.connect(jgAggregatesGm, jg.getChildAt(0), gm.getChildAt(0));
attributes.put(jgAggregatesGm, aggregateStyle);
DefaultEdge jcAggregatesCu = new DefaultEdge("ui");
cs.connect(jcAggregatesCu, jc.getChildAt(0), cu.getChildAt(0));
attributes.put(jcAggregatesCu, aggregateStyle);
// Insert Cells into model
Object[] cells = new Object[] { jgAggregatesGm, jcAggregatesCu,
modelGroup, jc, jg, jgExtendsJc, uiGroup };
model.insert(cells, attributes, cs, null, null);
}
/**
* Returns an attributeMap for the specified position and color.
*/
public static Map createBounds(AttributeMap map, int x, int y, Color c) {
GraphConstants.setBounds(map, map.createRect(x, y, 90, 30));
GraphConstants.setBorder(map, BorderFactory.createRaisedBevelBorder());
GraphConstants.setBackground(map, c.darker().darker());
GraphConstants
.setGradientColor(map, c.brighter().brighter().brighter());
GraphConstants.setForeground(map, Color.white);
GraphConstants.setFont(map, GraphConstants.DEFAULTFONT.deriveFont(
Font.BOLD, 12));
GraphConstants.setOpaque(map, true);
return map;
}
/**
* Returns a <code>JGraph</code> with a sample model.
*/
public JGraph() {
this((GraphModel) null);
}
/**
* Returns an instance of <code>JGraph</code> which displays the the
* specified data model.
*
* @param model
* the <code>GraphModel</code> to use as the data model
*/
public JGraph(GraphModel model) {
this(model, (GraphLayoutCache) null);
}
/**
* Returns an instance of <code>JGraph</code> which displays the data
* model using the specified view.
*
* @param cache
* the <code>GraphLayoutCache</code> to use as the view
*/
public JGraph(GraphLayoutCache cache) {
this((cache != null) ? cache.getModel() : null, cache);
}
/**
* Returns an instance of <code>JGraph</code> which displays the specified
* data model using the specified view.
*
* @param model
* the <code>GraphModel</code> to use as the data model
* @param cache
* the <code>GraphLayoutCache</code> to use as the cache
*/
public JGraph(GraphModel model, GraphLayoutCache cache) {
this(model, cache, new BasicMarqueeHandler());
}
/**
* Returns an instance of <code>JGraph</code> which displays the specified
* data model and assigns the specified marquee handler
*
* @param model
* the <code>GraphModel</code> to use as the data model
* @param mh
* the <code>BasicMarqueeHandler</code> to use as the marquee
* handler
*/
public JGraph(GraphModel model, BasicMarqueeHandler mh) {
this(model, null, mh);
}
/**
* Returns an instance of <code>JGraph</code> which displays the specified
* data model using the specified view and assigns the specified marquee
* handler
*
* @param model
* the <code>GraphModel</code> to use as the data model
* @param layoutCache
* the <code>GraphLayoutCache</code> to use as the cache
* @param mh
* the <code>BasicMarqueeHandler</code> to use as the marquee
* handler
*/
public JGraph(GraphModel model, GraphLayoutCache layoutCache,
BasicMarqueeHandler mh) {
setDoubleBuffered(true);
selectionModel = new DefaultGraphSelectionModel(this);
setLayout(null);
marquee = mh;
if (model == null) {
model = new DefaultGraphModel();
setModel(model);
addSampleData(model);
} else
setModel(model);
if (layoutCache == null)
layoutCache = new GraphLayoutCache(model,
new DefaultCellViewFactory());
setGraphLayoutCache(layoutCache);
updateUI();
}
//
// UI-delegate (GraphUI)
//
/**
* Returns the L&F object that renders this component.
*
* @return the GraphUI object that renders this component
*/
public GraphUI getUI() {
return (GraphUI) ui;
}
/**
* Sets the L&F object that renders this component.
*
* @param ui
* the GraphUI L&F object
* @see javax.swing.UIDefaults#getUI(JComponent)
*
*/
public void setUI(GraphUI ui) {
if ((GraphUI) this.ui != ui) {
settingUI = true;
try {
super.setUI(ui);
} finally {
settingUI = false;
}
}
}
/**
* Notification from the <code>UIManager</code> that the L&F has changed.
* Replaces the current UI object with the latest version from the
* <code>UIManager</code>. Subclassers can override this to support
* different GraphUIs.
*
* @see JComponent#updateUI
*
*/
public void updateUI() {
setUI(new org.jgraph.plaf.basic.BasicGraphUI());
invalidate();
}
/**
* Returns the name of the L&F class that renders this component.
*
* @return the string "GraphUI"
* @see JComponent#getUIClassID
*
*/
public String getUIClassID() {
return uiClassID;
}
//
// Content
//
/**
* Returns all cells that the model contains.
*/
public Object[] getRoots() {
return DefaultGraphModel.getRoots(graphModel);
}
/**
* Returns all cells that intersect the given rectangle.
*/
public Object[] getRoots(Rectangle clip) {
CellView[] views = graphLayoutCache.getRoots(clip);
Object[] cells = new Object[views.length];
for (int i = 0; i < views.length; i++)
cells[i] = views[i].getCell();
return cells;
}
/**
* Returns all <code>cells</code> including all descendants in the passed
* in order of cells.
*/
public Object[] getDescendants(Object[] cells) {
return DefaultGraphModel.getDescendants(getModel(), cells).toArray();
}
/**
* Returns all <code>cells</code> including all descendants ordered using
* the current layering data stored by the model.
*/
public Object[] order(Object[] cells) {
return DefaultGraphModel.order(getModel(), cells);
}
/**
* Returns a map of (cell, clone)-pairs for all <code>cells</code> and
* their children. Special care is taken to replace the anchor references
* between ports. (Iterative implementation.)
*/
public Map cloneCells(Object[] cells) {
return graphModel.cloneCells(cells);
}
/**
* Returns the topmost cell view at the specified location using the view's
* bounds on non-leafs to check for containment. If reverse is true this
* will return the innermost view.
*/
public CellView getTopmostViewAt(double x, double y, boolean reverse,
boolean leafsOnly) {
Rectangle2D r = new Rectangle2D.Double(x, y, 1, 1);
Object[] cells = getDescendants(getRoots());
for (int i = (reverse) ? cells.length - 1 : 0; i >= 0
&& i < cells.length; i += (reverse) ? -1 : +1) {
CellView view = getGraphLayoutCache().getMapping(cells[i], false);
if (view != null
&& (!leafsOnly || view.isLeaf())
&& ((view.isLeaf() && view.intersects(this, r)) || (!view
.isLeaf() && view.getBounds().contains(x, y))))
return view;
}
return null;
}
/**
* Returns the topmost cell at the specified location.
*
* @param x
* an integer giving the number of pixels horizontally from the
* left edge of the display area, minus any left margin
* @param y
* an integer giving the number of pixels vertically from the top
* of the display area, minus any top margin
* @return the topmost cell at the specified location
*/
public Object getFirstCellForLocation(double x, double y) {
return getNextCellForLocation(null, x, y);
}
/**
* Returns the cell at the specified location that is "behind" the
* <code>current</code> cell. Returns the topmost cell if there are no
* more cells behind <code>current</code>. Note: This does only return
* visible cells.
*/
public Object getNextCellForLocation(Object current, double x, double y) {
CellView cur = graphLayoutCache.getMapping(current, false);
CellView cell = getNextViewAt(cur, x, y);
if (cell != null)
return cell.getCell();
return null;
}
/**
* Returns the bounding rectangle of the specified cell.
*/
public Rectangle2D getCellBounds(Object cell) {
CellView view = graphLayoutCache.getMapping(cell, false);
if (view != null)
return view.getBounds();
return null;
}
/**
* Returns the bounding rectangle of the specified cells.
*/
public Rectangle2D getCellBounds(Object[] cells) {
if (cells != null && cells.length > 0) {
Rectangle2D r = getCellBounds(cells[0]);
Rectangle2D ret = (r != null) ? (Rectangle2D) r.clone() : null;
for (int i = 1; i < cells.length; i++) {
r = getCellBounds(cells[i]);
if (r != null) {
if (ret == null)
ret = (r != null) ? (Rectangle2D) r.clone() : null;
else
Rectangle2D.union(ret, r, ret);
}
}
return ret;
}
return null;
}
/**
* Returns the next view at the specified location wrt. <code>current</code>.
* This is used to iterate overlapping cells, and cells that are grouped.
* The current selection affects this method. <br>
* Note: This returns the next <i>selectable </i> view. <br>
* Note: Arguments are not expected to be scaled (they are scaled in here).
*/
public CellView getNextViewAt(CellView current, double x, double y) {
return getNextViewAt(current, x, y, false);
}
/**
* Returns the next view at the specified location wrt. <code>current</code>.
* This is used to iterate overlapping cells, and cells that are grouped.
* The current selection affects this method. <br>
* Note: This returns the next <i>selectable </i> view. <br>
* Note: Arguments are not expected to be scaled (they are scaled in here).
*/
public CellView getNextViewAt(CellView current, double x, double y,
boolean leafsOnly) {
CellView[] cells = AbstractCellView
.getDescendantViews(getGraphLayoutCache().getRoots());
return getNextViewAt(cells, current, x, y, leafsOnly);
}
/**
* Note: Arguments are not expected to be scaled (they are scaled in here).
*/
public CellView getNextSelectableViewAt(CellView current, double x, double y) {
CellView[] selectables = getGraphLayoutCache().getMapping(
getSelectionModel().getSelectables(), false);
return getNextViewAt(selectables, current, x, y);
}
/**
* Returns the next view at the specified location wrt. <code>c</code> in
* the specified array of views. The views must be in order, as returned,
* for example, by GraphLayoutCache.order(Object[]).
*/
public CellView getNextViewAt(CellView[] cells, CellView c, double x,
double y) {
return getNextViewAt(cells, c, x, y, false);
}
/**
* Returns the next view at the specified location wrt. <code>c</code> in
* the specified array of views. The views must be in order, as returned,
* for example, by GraphLayoutCache.order(Object[]).
*/
public CellView getNextViewAt(CellView[] cells, CellView c, double x,
double y, boolean leafsOnly) {
if (cells != null) {
Rectangle2D r = fromScreen(new Rectangle2D.Double(x - tolerance, y
- tolerance, 2 * tolerance, 2 * tolerance));
// Iterate through cells and switch to active
// if current is traversed. Cache first cell.
CellView first = null;
boolean active = (c == null);
for (int i = 0; i < cells.length; i++) {
if (cells[i] != null && (!leafsOnly || cells[i].isLeaf())
&& cells[i].intersects(this, r)) {
// TODO: This behaviour is specific to selection and
// should be parametrized (it only returns a group with
// selected children if no other portview is available)
if (active
&& !selectionModel.isChildrenSelected(cells[i]
.getCell())) {
return cells[i];
} else if (first == null)
first = cells[i];
active = active | (cells[i] == c);
}
}
if (first != null)
return first;
}
return null;
}
/**
* Returns the next view at the specified location wrt. <code>c</code> in
* the specified array of views. The views must be in order, as returned,
* for example, by GraphLayoutCache.order(Object[]).
*/
public CellView getLeafViewAt(double x, double y) {
return getNextViewAt(null, x, y, true);
}
/**
* Convenience method to return the port at the specified location.
*/
public Object getPortForLocation(double x, double y) {
PortView view = getPortViewAt(x, y);
if (view != null)
return view.getCell();
return null;
}
/**
* Returns the portview at the specified location. <br>
* Note: Arguments are not expected to be scaled (they are scaled in here).
*/
public PortView getPortViewAt(double x, double y) {
double sx = x / scale;
double sy = y / scale;
Rectangle2D r = new Rectangle2D.Double(sx - tolerance, sy - tolerance,
2 * tolerance, 2 * tolerance);
PortView[] ports = graphLayoutCache.getPorts();
if (ports != null) {
for (int i = ports.length - 1; i >= 0; i--)
if (ports[i] != null && ports[i].intersects(this, r))
return ports[i];
if (isJumpToDefaultPort()) {
CellView cellView = getNextViewAt(null, x, y, true);
// Finds a non-edge cell under the mousepointer
if (cellView != null && graphModel.isEdge(cellView.getCell())) {
CellView nextView = getNextViewAt(cellView, x, y, true);
while (nextView != cellView
&& graphModel.isEdge(nextView.getCell())) {
nextView = getNextViewAt(nextView, x, y, true);
}
cellView = nextView;
}
if (cellView != null) {
PortView defaultPort = getDefaultPortForCell(cellView
.getCell());
return defaultPort;
}
}
}
return null;
}
/**
* Returns the default portview for the specified cell. The default
* implementation returns the first floating port (ie. the first port that
* does not define an offset) or <b>the </b> port, if there is only one
* port.
*
* @param cell
* the cell whose port is to be obtained
* @return the port view of the specified cell
*/
public PortView getDefaultPortForCell(Object cell) {
if (cell != null && !getModel().isEdge(cell)) {
int childCount = getModel().getChildCount(cell);
for (int i = 0; i < childCount; i++) {
Object childCell = getModel().getChild(cell, i);
CellView child = getGraphLayoutCache().getMapping(childCell,
false);
if (child instanceof PortView) {
Point2D offset = GraphConstants.getOffset(child
.getAllAttributes());
if (offset == null || childCount == 1)
return (PortView) child;
}
}
}
return null;
}
/**
* Converts the specified value to string. If the value is an instance of
* CellView then the corresponding value or cell is used.
*/
public String convertValueToString(Object value) {
if (value instanceof CellView)
value = ((CellView) value).getCell();
return String.valueOf(value);
}
//
// Grid and Scale
//
/**
* Returns the given point applied to the grid.
*
* @param p
* a point in screen coordinates.
* @return the same point applied to the grid.
*/
public Point2D snap(Point2D p) {
if (gridEnabled && p != null) {
double sgs = gridSize * getScale();
p.setLocation(Math.round(Math.round(p.getX() / sgs) * sgs), Math
.round(Math.round(p.getY() / sgs) * sgs));
}
return p;
}
/**
* Returns the given rectangle applied to the grid.
*
* @param r
* a rectangle in screen coordinates.
* @return the same rectangle applied to the grid.
*/
public Rectangle2D snap(Rectangle2D r) {
if (gridEnabled && r != null) {
double sgs = gridSize * getScale();
r.setFrame(Math.round(Math.round(r.getX() / sgs) * sgs), Math
.round(Math.round(r.getY() / sgs) * sgs), 1 + Math
.round(Math.round(r.getWidth() / sgs) * sgs), 1 + Math
.round(Math.round(r.getHeight() / sgs) * sgs));
}
return r;
}
/**
* Returns the given dimension applied to the grid.
*
* @param d
* a dimension in screen coordinates to snap to.
* @return the same dimension applied to the grid.
*/
public Dimension2D snap(Dimension2D d) {
if (gridEnabled && d != null) {
double sgs = gridSize * getScale();
d.setSize(1 + Math.round(Math.round(d.getWidth() / sgs) * sgs),
1 + Math.round(Math.round(d.getHeight() / sgs) * sgs));
}
return d;
}
/**
* Upscale the given point in place, using the given instance.
*
* @param p
* the point to be upscaled
* @return the upscaled point instance
*/
public Point2D toScreen(Point2D p) {
if (p == null)
return null;
p.setLocation(Math.round(p.getX() * scale), Math
.round(p.getY() * scale));
return p;
}
/**
* Downscale the given point in place, using the given instance.
*
* @param p
* the point to be downscaled
* @return the downscaled point instance
*/
public Point2D fromScreen(Point2D p) {
if (p == null)
return null;
p.setLocation(Math.round(p.getX() / scale), Math
.round(p.getY() / scale));
return p;
}
/**
* Upscale the given rectangle in place, using the given instance.
*
* @param rect
* the rectangle to be upscaled
* @return the upscaled rectangle instance
*/
public Rectangle2D toScreen(Rectangle2D rect) {
if (rect == null)
return null;
rect.setFrame(rect.getX() * scale, rect.getY() * scale, rect.getWidth()
* scale, rect.getHeight() * scale);
return rect;
}
/**
* Downscale the given rectangle in place, using the given instance.
*
* @param rect
* the rectangle to be downscaled
* @return the down-scaled rectangle instance
*/
public Rectangle2D fromScreen(Rectangle2D rect) {
if (rect == null)
return null;
rect.setFrame(rect.getX() / scale, rect.getY() / scale, rect.getWidth()
/ scale, rect.getHeight() / scale);
return rect;
}
/**
* Computes and updates the size for <code>view</code>.
*/
public void updateAutoSize(CellView view) {
if (view != null && !isEditing()) {
Rectangle2D bounds = (view.getAttributes() != null) ? GraphConstants
.getBounds(view.getAttributes())
: null;
AttributeMap attrs = getModel().getAttributes(view.getCell());
if (bounds == null)
bounds = GraphConstants.getBounds(attrs);
if (bounds != null) {
boolean autosize = GraphConstants.isAutoSize(view
.getAllAttributes());
boolean resize = GraphConstants.isResize(view
.getAllAttributes());
if (autosize || resize) {
Dimension2D d = getUI().getPreferredSize(this, view);
bounds.setFrame(bounds.getX(), bounds.getY(), d.getWidth(),
d.getHeight());
// Remove resize attribute
snap(bounds);
if (resize) {
if (view.getAttributes() != null)
view.getAttributes().remove(GraphConstants.RESIZE);
attrs.remove(GraphConstants.RESIZE);
}
view.refresh(getModel(), getGraphLayoutCache(), false);
}
}
}
}
/**
* Returns the attributes for the specified cell. If the layout cache
* returns a view for the cell then this method returns allAttributes,
* otherwise the method returns model.getAttributes(cell).
*/
public AttributeMap getAttributes(Object cell) {
AttributeMap attrs;
CellView cellView = getGraphLayoutCache().getMapping(cell, false);
if (cellView != null) {
attrs = cellView.getAllAttributes();
} else {
attrs = getModel().getAttributes(cell);
}
return attrs;
}
//
// Unbound Properties
//
/**
* Returns the number of clicks for editing to start.
*/
public int getEditClickCount() {
return editClickCount;
}
/**
* Sets the number of clicks for editing to start.
*/
public void setEditClickCount(int count) {
editClickCount = count;
}
/**
* Returns true if the graph accepts drops/pastes from external sources.
*/
public boolean isDropEnabled() {
return dropEnabled;
}
/**
* Sets if the graph accepts drops/pastes from external sources.
*/
public void setDropEnabled(boolean flag) {
dropEnabled = flag;
}
/**
* Returns true if the graph uses Drag-and-Drop to move cells.
*/
public boolean isDragEnabled() {
return dragEnabled;
}
/**
* Sets if the graph uses Drag-and-Drop to move cells.
*/
public void setDragEnabled(boolean flag) {
dragEnabled = flag;
}
/*
* Returns true if the graph allows movement of cells.
*/
public boolean isMoveable() {
return moveable;
}
/**
* Sets if the graph allows movement of cells.
*/
public void setMoveable(boolean flag) {
moveable = flag;
}
/**
* Returns true if the graph allows adding/removing/modifying points.
*/
public boolean isBendable() {
return bendable;
}
/**
* Sets if the graph allows adding/removing/modifying points.
*/
public void setBendable(boolean flag) {
bendable = flag;
}
/**
* Returns true if the graph allows new connections to be established.
*/
public boolean isConnectable() {
return connectable;
}
/**
* Setse if the graph allows new connections to be established.
*/
public void setConnectable(boolean flag) {
connectable = flag;
}
/**
* Returns true if the graph allows existing connections to be removed.
*/
public boolean isDisconnectable() {
return disconnectable;
}
/**
* Sets if the graph allows existing connections to be removed.
*/
public void setDisconnectable(boolean flag) {
disconnectable = flag;
}
/**
* Returns true if cells are cloned on CTRL-Drag operations.
*/
public boolean isCloneable() {
return cloneable;
}
/**
* Sets if cells are cloned on CTRL-Drag operations.
*/
public void setCloneable(boolean flag) {
cloneable = flag;
}
/**
* Returns true if the graph allows cells to be resized.
*/
public boolean isSizeable() {
return sizeable;
}
/**
* Sets if the graph allows cells to be resized.
*/
public void setSizeable(boolean flag) {
sizeable = flag;
}
/**
* Sets if selected edges should be disconnected from unselected vertices
* when they are moved.
*/
public void setDisconnectOnMove(boolean flag) {
disconnectOnMove = flag;
}
/**
* Returns true if selected edges should be disconnected from unselected
* vertices when they are moved.
*/
public boolean isDisconnectOnMove() {
return disconnectOnMove && disconnectable;
}
/**
* Sets if getPortViewAt should return the default port if no other port is
* found.
*/
public void setJumpToDefaultPort(boolean flag) {
isJumpToDefaultPort = flag;
}
/**
* Returns true if getPortViewAt should return the default port if no other
* port is found.
*/
public boolean isJumpToDefaultPort() {
return isJumpToDefaultPort;
}
/**
* Specifies if cells should be added to groups when moved over the group's
* area.
*/
public void setMoveIntoGroups(boolean flag) {
isMoveIntoGroups = flag;
}
/**
* Returns true if cells should be added to groups when moved over the
* group's area.
*/
public boolean isMoveIntoGroups() {
return isMoveIntoGroups;
}
/**
* Specifies if cells should be removed from groups when removed from the
* group's area.
*/
public void setMoveOutOfGroups(boolean flag) {
isMoveOutOfGroups = flag;
}
/**
* Returns true if cells should be removed from groups when removed from the
* group's area.
*/
public boolean isMoveOutOfGroups() {
return isMoveOutOfGroups;
}
/**
* Returns true if the grid is active.
*
* @see #snap(Point2D)
*
*/
public boolean isGridEnabled() {
return gridEnabled;
}
/**
* If set to true, the grid will be active.
*
* @see #snap(Point2D)
*
*/
public void setGridEnabled(boolean flag) {
gridEnabled = flag;
}
/**
* Returns true if the graph allows to move cells below zero.
*/
public boolean isMoveBelowZero() {
return moveBelowZero;
}
/**
* Sets if the graph allows to move cells below zero.
*/
public void setMoveBelowZero(boolean moveBelowZero) {
this.moveBelowZero = moveBelowZero;
}
/**
* Returns the maximum distance between the mousepointer and a cell to be
* selected.
*/
public int getTolerance() {
return tolerance;
}
/**
* Sets the maximum distance between the mousepointer and a cell to be
* selected.
*/
public void setTolerance(int size) {
tolerance = size;
}
/**
* Returns the size of the handles.
*/
public int getHandleSize() {
return handleSize;
}
/**
* Sets the size of the handles.
*/
public void setHandleSize(int size) {
int oldValue = handleSize;
handleSize = size;
firePropertyChange(HANDLE_SIZE_PROPERTY, oldValue, size);
}
/**
* Returns the miminum amount of pixels for a move operation.
*/
public int getMinimumMove() {
return minimumMove;
}
/**
* Sets the miminum amount of pixels for a move operation.
*/
public void setMinimumMove(int pixels) {
minimumMove = pixels;
}
//
// Laf-Specific color scheme. These colors are changed
// by BasicGraphUI when the laf changes.
//
/**
* Returns the current grid color.
*/
public Color getGridColor() {
return gridColor;
}
/**
* Sets the current grid color.
*/
public void setGridColor(Color newColor) {
Color oldValue = gridColor;
gridColor = newColor;
firePropertyChange(GRID_COLOR_PROPERTY, oldValue, newColor);
}
/**
* Returns the current handle color.
*/
public Color getHandleColor() {
return handleColor;
}
/**
* Sets the current handle color.
*/
public void setHandleColor(Color newColor) {
Color oldValue = handleColor;
handleColor = newColor;
firePropertyChange(HANDLE_COLOR_PROPERTY, oldValue, newColor);
}
/**
* Returns the current second handle color.
*/
public Color getLockedHandleColor() {
return lockedHandleColor;
}
/**
* Sets the current second handle color.
*/
public void setLockedHandleColor(Color newColor) {
Color oldValue = lockedHandleColor;
lockedHandleColor = newColor;
firePropertyChange(LOCKED_HANDLE_COLOR_PROPERTY, oldValue, newColor);
}
/**
* Returns the current marquee color.
*/
public Color getMarqueeColor() {
return marqueeColor;
}
/**
* Sets the current marquee color.
*/
public void setMarqueeColor(Color newColor) {
marqueeColor = newColor;
}
/**
* Returns the current highlight color.
*/
public Color getHighlightColor() {
return highlightColor;
}
/**
* Sets the current selection highlight color.
*/
public void setHighlightColor(Color newColor) {
highlightColor = newColor;
}
//
// Bound properties
//
/**
* Returns the current scale.
*
* @return the current scale as a double
*/
public double getScale() {
return scale;
}
/**
* Sets the current scale.
* <p>
* Fires a property change for the SCALE_PROPERTY.
*
* @param newValue
* the new scale
*/
public void setScale(double newValue) {
if (newValue > 0) {
double oldValue = this.scale;
scale = newValue;
firePropertyChange(SCALE_PROPERTY, oldValue, newValue);
}
}
/**
* Returns the size of the grid in pixels.
*
* @return the size of the grid as an int
*/
public double getGridSize() {
return gridSize;
}
/**
* Returns the current grid view mode.
*/
public int getGridMode() {
return gridMode;
}
/**
* Sets the size of the grid.
* <p>
* Fires a property change for the GRID_SIZE_PROPERTY.
*
* @param newSize
* the new size of the grid in pixels
*/
public void setGridSize(double newSize) {
double oldValue = this.gridSize;
this.gridSize = newSize;
firePropertyChange(GRID_SIZE_PROPERTY, oldValue, newSize);
}
/**
* Sets the current grid view mode.
*
* @param mode
* The current grid view mode. Valid values are <CODE>
* DOT_GRID_MODE</CODE>,<CODE>CROSS_GRID_MODE</CODE>, and
* <CODE>LINE_GRID_MODE</CODE>.
*/
public void setGridMode(int mode) {
if (mode == DOT_GRID_MODE || mode == CROSS_GRID_MODE
|| mode == LINE_GRID_MODE) {
gridMode = mode;
repaint();
}
}
/**
* Returns true if the grid will be visible.
*
* @return true if the grid is visible
*/
public boolean isGridVisible() {
return gridVisible;
}
/**
* If set to true, the grid will be visible.
* <p>
* Fires a property change for the GRID_VISIBLE_PROPERTY.
*/
public void setGridVisible(boolean flag) {
boolean oldValue = gridVisible;
gridVisible = flag;
firePropertyChange(GRID_VISIBLE_PROPERTY, oldValue, flag);
}
/**
* Returns true if the ports will be visible.
*
* @return true if the ports are visible
*/
public boolean isPortsVisible() {
return portsVisible;
}
/**
* If set to true, the ports will be visible.
* <p>
* Fires a property change for the PORTS_VISIBLE_PROPERTY.
*/
public void setPortsVisible(boolean flag) {
boolean oldValue = portsVisible;
portsVisible = flag;
firePropertyChange(PORTS_VISIBLE_PROPERTY, oldValue, flag);
}
/**
* Returns true if the ports will be scaled.
*
* @return true if the ports are visible
*/
public boolean isPortsScaled() {
return portsScaled;
}
/**
* If set to true, the ports will be scaled.
* <p>
* Fires a property change for the PORTS_SCALED_PROPERTY.
*/
public void setPortsScaled(boolean flag) {
boolean oldValue = portsScaled;
portsScaled = flag;
firePropertyChange(PORTS_SCALED_PROPERTY, oldValue, flag);
}
/**
* Returns true if the graph will be anti aliased.
*
* @return true if the graph is anti aliased
*/
public boolean isAntiAliased() {
return antiAliased;
}
/**
* Sets antialiasing on or off based on the boolean value.
* <p>
* Fires a property change for the ANTIALIASED_PROPERTY.
*
* @param newValue
* whether to turn antialiasing on or off
*/
public void setAntiAliased(boolean newValue) {
boolean oldValue = this.antiAliased;
this.antiAliased = newValue;
firePropertyChange(ANTIALIASED_PROPERTY, oldValue, newValue);
}
/**
* Returns true if the graph is editable (if it allows cells to be edited).
*
* @return true if the graph is editable
*/
public boolean isEditable() {
return editable;
}
/**
* Determines whether the graph is editable. Fires a property change event
* if the new setting is different from the existing setting.
* <p>
* Note: Editable determines whether the graph allows editing. This is not
* to be confused with enabled, which allows the graph to handle mouse
* events (including editing).
*
* @param flag
* a boolean value, true if the graph is editable
*/
public void setEditable(boolean flag) {
boolean oldValue = this.editable;
this.editable = flag;
firePropertyChange(EDITABLE_PROPERTY, oldValue, flag);
}
/**
* Returns true if the cell selection is enabled
*
* @return true if the cell selection is enabled
*/
public boolean isSelectionEnabled() {
return selectionEnabled;
}
/**
* Determines whether cell selection is enabled. Fires a property change
* event if the new setting is different from the existing setting.
*
* @param flag
* a boolean value, true if cell selection is enabled
*/
public void setSelectionEnabled(boolean flag) {
boolean oldValue = this.editable;
this.selectionEnabled = flag;
firePropertyChange(SELECTIONENABLED_PROPERTY, oldValue, flag);
}
/**
* Returns true if graph allows invalid null ports during previews
*
* @return true if the graph allows invalid null ports during previews
*/
public boolean isPreviewInvalidNullPorts() {
return previewInvalidNullPorts;
}
/**
* Determines whether the graph allows invalid null ports during previews
*
* @param flag
* a boolean value, true if the graph allows invalid null ports
* during previews
*/
public void setPreviewInvalidNullPorts(boolean flag) {
this.previewInvalidNullPorts = flag;
}
/**
* Returns the <code>GraphModel</code> that is providing the data.
*
* @return the model that is providing the data
*/
public GraphModel getModel() {
return graphModel;
}
/**
* Sets the <code>GraphModel</code> that will provide the data. Note:
* Updates the current GraphLayoutCache's model using setModel if the
* GraphLayoutCache points to a different model.
* <p>
* Fires a property change for the GRAPH_MODEL_PROPERTY.
*
* @param newModel
* the <code>GraphModel</code> that is to provide the data
*/
public void setModel(GraphModel newModel) {
GraphModel oldModel = graphModel;
graphModel = newModel;
firePropertyChange(GRAPH_MODEL_PROPERTY, oldModel, graphModel);
// FIX: Use Listener
if (graphLayoutCache != null
&& graphLayoutCache.getModel() != graphModel)
graphLayoutCache.setModel(graphModel);
clearSelection();
invalidate();
}
/**
* Returns the <code>GraphLayoutCache</code> that is providing the
* view-data.
*
* @return the view that is providing the view-data
*/
public GraphLayoutCache getGraphLayoutCache() {
return graphLayoutCache;
}
/**
* Sets the <code>GraphLayoutCache</code> that will provide the view-data.
* <p>
* Note: Updates the graphs's model using using the model from the layout
* cache.
* <p>
* Fires a property change for the GRAPH_LAYOUT_CACHE_PROPERTY.
*
* @param newLayoutCache
* the <code>GraphLayoutCache</code> that is to provide the
* view-data
*/
public void setGraphLayoutCache(GraphLayoutCache newLayoutCache) {
GraphLayoutCache oldLayoutCache = graphLayoutCache;
graphLayoutCache = newLayoutCache;
firePropertyChange(GRAPH_LAYOUT_CACHE_PROPERTY, oldLayoutCache,
graphLayoutCache);
if (graphLayoutCache != null
&& graphLayoutCache.getModel() != getModel())
setModel(graphLayoutCache.getModel());
invalidate();
}
/**
* Returns the <code>MarqueeHandler</code> that will handle marquee
* selection.
*/
public BasicMarqueeHandler getMarqueeHandler() {
return marquee;
}
/**
* Sets the <code>MarqueeHandler</code> that will handle marquee
* selection.
*
* @param newMarquee
* the <code>BasicMarqueeHandler</code> that is to provide
* marquee handling
*/
public void setMarqueeHandler(BasicMarqueeHandler newMarquee) {
BasicMarqueeHandler oldMarquee = marquee;
marquee = newMarquee;
firePropertyChange(MARQUEE_HANDLER_PROPERTY, oldMarquee, newMarquee);
invalidate();
}
/**
* Determines what happens when editing is interrupted by selecting another
* cell in the graph, a change in the graph's data, or by some other means.
* Setting this property to <code>true</code> causes the changes to be
* automatically saved when editing is interrupted.
* <p>
* Fires a property change for the INVOKES_STOP_CELL_EDITING_PROPERTY.
*
* @param newValue
* true means that <code>stopCellEditing</code> is invoked when
* editing is interruped, and data is saved; false means that
* <code>cancelCellEditing</code> is invoked, and changes are
* lost
*/
public void setInvokesStopCellEditing(boolean newValue) {
boolean oldValue = invokesStopCellEditing;
invokesStopCellEditing = newValue;
firePropertyChange(INVOKES_STOP_CELL_EDITING_PROPERTY, oldValue,
newValue);
}
/**
* Returns the indicator that tells what happens when editing is
* interrupted.
*
* @return the indicator that tells what happens when editing is interrupted
* @see #setInvokesStopCellEditing
*
*/
public boolean getInvokesStopCellEditing() {
return invokesStopCellEditing;
}
/**
* Returns <code>true</code> if the graph and the cell are editable. This
* is invoked from the UI before editing begins to ensure that the given
* cell can be edited.
*
* @return true if the specified cell is editable
* @see #isEditable
*
*/
public boolean isCellEditable(Object cell) {
if (cell != null) {
CellView view = graphLayoutCache.getMapping(cell, false);
if (view != null) {
return isEditable()
&& GraphConstants.isEditable(view.getAllAttributes());
}
}
return false;
}
/**
* Overrides <code>JComponent</code>'s<code>getToolTipText</code>
* method in order to allow the graph to create a tooltip for the topmost
* cell under the mousepointer. This differs from JTree where the renderers
* tooltip is used.
* <p>
* NOTE: For <code>JGraph</code> to properly display tooltips of its
* renderers, <code>JGraph</code> must be a registered component with the
* <code>ToolTipManager</code>. This can be done by invoking
* <code>ToolTipManager.sharedInstance().registerComponent(graph)</code>.
* This is not done automatically!
*
* @param e
* the <code>MouseEvent</code> that initiated the
* <code>ToolTip</code> display
* @return a string containing the tooltip or <code>null</code> if
* <code>event</code> is null
*/
public String getToolTipText(MouseEvent e) {
if (e != null) {
Object cell = getFirstCellForLocation(e.getX(), e.getY());
CellView view = getGraphLayoutCache().getMapping(cell, false);
if (view != null) {
Component c = view.getRendererComponent(this, false, false,
false);
if (c instanceof JComponent) {
Rectangle2D rect = getCellBounds(cell);
Point2D where = fromScreen(e.getPoint());
// Pass the event to the renderer in graph coordinates;
// the renderer is ignorant of screen scaling
e = new MouseEvent(c, e.getID(), e.getWhen(), e
.getModifiers(),
(int) (where.getX() - rect.getX()), (int) (where
.getY() - rect.getY()), e.getClickCount(),
e.isPopupTrigger());
return ((JComponent) c).getToolTipText(e);
}
}
}
return super.getToolTipText(e);
}
//
// The following are convenience methods that get forwarded to the
// current GraphSelectionModel.
//
/**
* Sets the graph's selection model. When a <code>null</code> value is
* specified an emtpy <code>selectionModel</code> is used, which does not
* allow selections.
*
* @param selectionModel
* the <code>GraphSelectionModel</code> to use, or
* <code>null</code> to disable selections
* @see GraphSelectionModel
*
*/
public void setSelectionModel(GraphSelectionModel selectionModel) {
if (selectionModel == null)
selectionModel = EmptySelectionModel.sharedInstance();
GraphSelectionModel oldValue = this.selectionModel;
// Remove Redirector From Old Selection Model
if (this.selectionModel != null && selectionRedirector != null)
this.selectionModel
.removeGraphSelectionListener(selectionRedirector);
this.selectionModel = selectionModel;
// Add Redirector To New Selection Model
if (selectionRedirector != null)
this.selectionModel.addGraphSelectionListener(selectionRedirector);
firePropertyChange(SELECTION_MODEL_PROPERTY, oldValue,
this.selectionModel);
}
/**
* Returns the model for selections. This should always return a non-
* <code>null</code> value. If you don't want to allow anything to be
* selected set the selection model to <code>null</code>, which forces an
* empty selection model to be used.
*
* @return the current selection model
* @see #setSelectionModel
*
*/
public GraphSelectionModel getSelectionModel() {
return selectionModel;
}
/**
* Clears the selection.
*/
public void clearSelection() {
getSelectionModel().clearSelection();
}
/**
* Returns true if the selection is currently empty.
*
* @return true if the selection is currently empty
*/
public boolean isSelectionEmpty() {
return getSelectionModel().isSelectionEmpty();
}
/**
* Adds a listener for <code>GraphSelection</code> events.
*
* @param tsl
* the <code>GraphSelectionListener</code> that will be
* notified when a cell is selected or deselected (a "negative
* selection")
*/
public void addGraphSelectionListener(GraphSelectionListener tsl) {
listenerList.add(GraphSelectionListener.class, tsl);
if (listenerList.getListenerCount(GraphSelectionListener.class) != 0
&& selectionRedirector == null) {
selectionRedirector = new GraphSelectionRedirector();
selectionModel.addGraphSelectionListener(selectionRedirector);
}
}
/**
* Removes a <code>GraphSelection</code> listener.
*
* @param tsl
* the <code>GraphSelectionListener</code> to remove
*/
public void removeGraphSelectionListener(GraphSelectionListener tsl) {
listenerList.remove(GraphSelectionListener.class, tsl);
if (listenerList.getListenerCount(GraphSelectionListener.class) == 0
&& selectionRedirector != null) {
selectionModel.removeGraphSelectionListener(selectionRedirector);
selectionRedirector = null;
}
}
/**
* Notifies all listeners that have registered interest for notification on
* this event type. The event instance is lazily created using the
* parameters passed into the fire method.
*
* @param e
* the <code>GraphSelectionEvent</code> generated by the
* <code>GraphSelectionModel</code> when a cell is selected or
* deselected
* @see javax.swing.event.EventListenerList
*
*/
protected void fireValueChanged(GraphSelectionEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == GraphSelectionListener.class) {
((GraphSelectionListener) listeners[i + 1]).valueChanged(e);
}
}
}
/**
* Selects the specified cell.
*
* @param cell
* the <code>Object</code> specifying the cell to select
*/
public void setSelectionCell(Object cell) {
getSelectionModel().setSelectionCell(cell);
}
/**
* Selects the specified cells.
*
* @param cells
* an array of objects that specifies the cells to select
*/
public void setSelectionCells(Object[] cells) {
getSelectionModel().setSelectionCells(cells);
}
/**
* Adds the cell identified by the specified <code>Object</code> to the
* current selection.
*
* @param cell
* the cell to be added to the selection
*/
public void addSelectionCell(Object cell) {
getSelectionModel().addSelectionCell(cell);
}
/**
* Adds each cell in the array of cells to the current selection.
*
* @param cells
* an array of objects that specifies the cells to add
*/
public void addSelectionCells(Object[] cells) {
getSelectionModel().addSelectionCells(cells);
}
/**
* Removes the cell identified by the specified Object from the current
* selection.
*
* @param cell
* the cell to be removed from the selection
*/
public void removeSelectionCell(Object cell) {
getSelectionModel().removeSelectionCell(cell);
}
/**
* Returns the first selected cell.
*
* @return the <code>Object</code> for the first selected cell, or
* <code>null</code> if nothing is currently selected
*/
public Object getSelectionCell() {
return getSelectionModel().getSelectionCell();
}
/**
* Returns all selected cells.
*
* @return an array of objects representing the selected cells, or
* <code>null</code> if nothing is currently selected
*/
public Object[] getSelectionCells() {
return getSelectionModel().getSelectionCells();
}
/**
* Returns all selected cells in <code>cells</code>.
*/
public Object[] getSelectionCells(Object[] cells) {
if (cells != null) {
List selected = new ArrayList(cells.length);
for (int i = 0; i < cells.length; i++) {
if (isCellSelected(cells[i]))
selected.add(cells[i]);
}
return selected.toArray();
}
return null;
}
/**
* Returns the selection cell at the specified location.
*
* @return Returns the selection cell for <code>pt</code>.
*/
public Object getSelectionCellAt(Point2D pt) {
pt = fromScreen((Point2D) pt.clone());
Object[] cells = getSelectionCells();
if (cells != null) {
for (int i = 0; i < cells.length; i++)
if (getCellBounds(cells[i]).contains(pt.getX(), pt.getY()))
return cells[i];
}
return null;
}
/**
* Returns the number of cells selected.
*
* @return the number of cells selected
*/
public int getSelectionCount() {
return getSelectionModel().getSelectionCount();
}
/**
* Returns true if the cell is currently selected.
*
* @param cell
* an object identifying a cell
* @return true if the cell is selected
*/
public boolean isCellSelected(Object cell) {
return getSelectionModel().isCellSelected(cell);
}
/**
* Scrolls to the specified cell. Only works when this <code>JGraph</code>
* is contained in a <code>JScrollPane</code>.
*
* @param cell
* the object identifying the cell to bring into view
*/
public void scrollCellToVisible(Object cell) {
Rectangle2D bounds = getCellBounds(cell);
if (bounds != null) {
Rectangle2D b2 = toScreen((Rectangle2D) bounds.clone());
scrollRectToVisible(new Rectangle((int) b2.getX(), (int) b2.getY(),
(int) b2.getWidth(), (int) b2.getHeight()));
}
}
/**
* Makes sure the specified point is visible.
*
* @param p
* the point that should be visible
*/
public void scrollPointToVisible(Point2D p) {
if (p != null)
scrollRectToVisible(new Rectangle((int) p.getX(), (int) p.getY(),
1, 1));
}
/**
* Returns true if the graph is being edited. The item that is being edited
* can be obtained using <code>getEditingCell</code>.
*
* @return true if the user is currently editing a cell
* @see #getSelectionCell
*
*/
public boolean isEditing() {
GraphUI graph = getUI();
if (graph != null)
return graph.isEditing(this);
return false;
}
/**
* Ends the current editing session. (The
* <code>DefaultGraphCellEditor</code> object saves any edits that are
* currently in progress on a cell. Other implementations may operate
* differently.) Has no effect if the tree isn't being edited. <blockquote>
* <b>Note: </b> <br>
* To make edit-saves automatic whenever the user changes their position in
* the graph, use {@link #setInvokesStopCellEditing}. </blockquote>
*
* @return true if editing was in progress and is now stopped, false if
* editing was not in progress
*/
public boolean stopEditing() {
GraphUI graph = getUI();
if (graph != null)
return graph.stopEditing(this);
return false;
}
/**
* Cancels the current editing session. Has no effect if the graph isn't
* being edited.
*/
public void cancelEditing() {
GraphUI graph = getUI();
if (graph != null)
graph.cancelEditing(this);
}
/**
* Selects the specified cell and initiates editing. The edit-attempt fails
* if the <code>CellEditor</code> does not allow editing for the specified
* item.
*/
public void startEditingAtCell(Object cell) {
GraphUI graph = getUI();
if (graph != null)
graph.startEditingAtCell(this, cell);
}
/**
* Returns the cell that is currently being edited.
*
* @return the cell being edited
*/
public Object getEditingCell() {
GraphUI graph = getUI();
if (graph != null)
return graph.getEditingCell(this);
return null;
}
/**
* Messaged when the graph has changed enough that we need to resize the
* bounds, but not enough that we need to remove the cells (e.g cells were
* inserted into the graph). You should never have to invoke this, the UI
* will invoke this as it needs to. (Note: This is invoked by GraphUI, eg.
* after moving.)
*/
public void graphDidChange() {
revalidate();
repaint();
}
/**
* Returns a {@link BufferedImage} for the graph using inset as an empty
* border around the cells of the graph. If bg is null then a transparent
* background is applied to the image, else the background is filled with
* the bg color. Therefore, one should only use a null background if the
* fileformat support transparency, eg. GIF and PNG. For JPG, you can use
* <code>Color.WHITE</code> for example.
*
* @return Returns an image of the graph.
*/
public BufferedImage getImage(Color bg, int inset) {
Object[] cells = getRoots();
Rectangle2D bounds = getCellBounds(cells);
if (bounds != null) {
toScreen(bounds);
BufferedImage img = new BufferedImage((int) bounds.getWidth() + 2
* inset, (int) bounds.getHeight() + 2 * inset,
(bg != null) ? BufferedImage.TYPE_INT_RGB
: BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics();
if (bg != null) {
graphics.setColor(bg);
graphics.fillRect(0, 0, img.getWidth(), img.getHeight());
} else {
graphics.setComposite(AlphaComposite.getInstance(
AlphaComposite.CLEAR, 0.0f));
graphics.fillRect(0, 0, img.getWidth(), img.getHeight());
graphics.setComposite(AlphaComposite.SrcOver);
}
graphics.translate((int) (-bounds.getX() + inset), (int) (-bounds
.getY() + inset));
boolean tmp = isDoubleBuffered();
RepaintManager currentManager = RepaintManager.currentManager(this);
currentManager.setDoubleBufferingEnabled(false);
paint(graphics);
currentManager.setDoubleBufferingEnabled(tmp);
return img;
}
return null;
}
/**
* Serialization support.
*/
private void writeObject(ObjectOutputStream s) throws IOException {
Vector values = new Vector();
s.defaultWriteObject();
// Save the cellEditor, if its Serializable.
if (graphModel instanceof Serializable) {
values.addElement("graphModel");
values.addElement(graphModel);
}
// Save the graphModel, if its Serializable.
if (graphLayoutCache instanceof Serializable) {
values.addElement("graphLayoutCache");
values.addElement(graphLayoutCache);
}
// Save the selectionModel, if its Serializable.
if (selectionModel instanceof Serializable) {
values.addElement("selectionModel");
values.addElement(selectionModel);
}
s.writeObject(values);
if (getUIClassID().equals(uiClassID)) {
/*
* byte count = JComponent.getWriteObjCounter(this);
* JComponent.setWriteObjCounter(this, --count);
*/
if (/* count == 0 && */
ui != null) {
ui.installUI(this);
}
}
}
/**
* Serialization support.
*/
private void readObject(ObjectInputStream s) throws IOException,
ClassNotFoundException {
s.defaultReadObject();
Vector values = (Vector) s.readObject();
int indexCounter = 0;
int maxCounter = values.size();
if (indexCounter < maxCounter
&& values.elementAt(indexCounter).equals("graphModel")) {
graphModel = (GraphModel) values.elementAt(++indexCounter);
indexCounter++;
}
if (indexCounter < maxCounter
&& values.elementAt(indexCounter).equals("graphLayoutCache")) {
graphLayoutCache = (GraphLayoutCache) values
.elementAt(++indexCounter);
indexCounter++;
}
if (indexCounter < maxCounter
&& values.elementAt(indexCounter).equals("selectionModel")) {
selectionModel = (GraphSelectionModel) values
.elementAt(++indexCounter);
indexCounter++;
}
// Reinstall the redirector.
if (listenerList.getListenerCount(GraphSelectionListener.class) != 0) {
selectionRedirector = new GraphSelectionRedirector();
selectionModel.addGraphSelectionListener(selectionRedirector);
}
}
/**
* <code>EmptySelectionModel</code> is a <code>GraphSelectionModel</code>
* that does not allow anything to be selected.
* <p>
* <strong>Warning: </strong> Serialized objects of this class will not be
* compatible with future Swing releases. The current serialization support
* is appropriate for short term storage or RMI between applications running
* the same version of Swing. A future release of Swing will provide support
* for long term persistence.
*/
public static class EmptySelectionModel extends DefaultGraphSelectionModel {
/** Unique shared instance. */
protected static final EmptySelectionModel sharedInstance = new EmptySelectionModel();
/**
* A <code>null</code> implementation that constructs an
* EmptySelectionModel.
*/
public EmptySelectionModel() {
super(null);
}
/** Returns a shared instance of an empty selection model. */
static public EmptySelectionModel sharedInstance() {
return sharedInstance;
}
/** A <code>null</code> implementation that selects nothing. */
public void setSelectionCells(Object[] cells) {
}
/** A <code>null</code> implementation that adds nothing. */
public void addSelectionCells(Object[] cells) {
}
/** A <code>null</code> implementation that removes nothing. */
public void removeSelectionCells(Object[] cells) {
}
}
/**
* Handles creating a new <code>GraphSelectionEvent</code> with the
* <code>JGraph</code> as the source and passing it off to all the
* listeners.
* <p>
* <strong>Warning: </strong> Serialized objects of this class will not be
* compatible with future Swing releases. The current serialization support
* is appropriate for short term storage or RMI between applications running
* the same version of Swing. A future release of Swing will provide support
* for long term persistence.
*/
protected class GraphSelectionRedirector implements Serializable,
GraphSelectionListener {
/**
* Invoked by the <code>GraphSelectionModel</code> when the selection
* changes.
*
* @param e
* the <code>GraphSelectionEvent</code> generated by the
* <code>GraphSelectionModel</code>
*/
public void valueChanged(GraphSelectionEvent e) {
GraphSelectionEvent newE;
newE = (GraphSelectionEvent) e.cloneWithSource(JGraph.this);
fireValueChanged(newE);
}
} // End of class JGraph.GraphSelectionRedirector
//
// Scrollable interface
//
/**
* Returns the preferred display size of a <code>JGraph</code>. The
* height is determined from <code>getPreferredWidth</code>.
*
* @return the graph's preferred size
*/
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
/**
* Returns the amount to increment when scrolling. The amount is 4.
*
* @param visibleRect
* the view area visible within the viewport
* @param orientation
* either <code>SwingConstants.VERTICAL</code> or
* <code>SwingConstants.HORIZONTAL</code>
* @param direction
* less than zero to scroll up/left, greater than zero for
* down/right
* @return the "unit" increment for scrolling in the specified direction
* @see javax.swing.JScrollBar#setUnitIncrement(int)
*
*/
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation, int direction) {
if (orientation == SwingConstants.VERTICAL) {
return 2;
}
return 4;
}
/**
* Returns the amount for a block increment, which is the height or width of
* <code>visibleRect</code>, based on <code>orientation</code>.
*
* @param visibleRect
* the view area visible within the viewport
* @param orientation
* either <code>SwingConstants.VERTICAL</code> or
* <code>SwingConstants.HORIZONTAL</code>
* @param direction
* less than zero to scroll up/left, greater than zero for
* down/right.
* @return the "block" increment for scrolling in the specified direction
* @see javax.swing.JScrollBar#setBlockIncrement(int)
*
*/
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction) {
return (orientation == SwingConstants.VERTICAL) ? visibleRect.height
: visibleRect.width;
}
/**
* Returns false to indicate that the width of the viewport does not
* determine the width of the graph, unless the preferred width of the graph
* is smaller than the viewports width. In other words: ensure that the
* graph is never smaller than its viewport.
*
* @return false
* @see Scrollable#getScrollableTracksViewportWidth
*
*/
public boolean getScrollableTracksViewportWidth() {
if (getParent() instanceof JViewport) {
return (((JViewport) getParent()).getWidth() > getPreferredSize().width);
}
return false;
}
/**
* Returns false to indicate that the height of the viewport does not
* determine the height of the graph, unless the preferred height of the
* graph is smaller than the viewports height. In other words: ensure that
* the graph is never smaller than its viewport.
*
* @return false
* @see Scrollable#getScrollableTracksViewportHeight
*
*/
public boolean getScrollableTracksViewportHeight() {
if (getParent() instanceof JViewport) {
return (((JViewport) getParent()).getHeight() > getPreferredSize().height);
}
return false;
}
/**
* Returns a string representation of this <code>JGraph</code>. This
* method is intended to be used only for debugging purposes, and the
* content and format of the returned string may vary between
* implementations. The returned string may be empty but may not be
* <code>null</code>.
*
* @return a string representation of this <code>JGraph</code>.
*/
protected String paramString() {
String editableString = (editable ? "true" : "false");
String invokesStopCellEditingString = (invokesStopCellEditing ? "true"
: "false");
return super.paramString() + ",editable=" + editableString
+ ",invokesStopCellEditing=" + invokesStopCellEditingString;
}
public static void main(String[] args) {
System.out.println(VERSION);
}
}