/*
* $Id$
*
* Copyright (c) 2001-2008 Gaudenz Alder
*
*/
package org.jgraph.graph;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.WeakHashMap;
import javax.swing.event.EventListenerList;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoableEdit;
import org.jgraph.event.GraphLayoutCacheEvent;
import org.jgraph.event.GraphLayoutCacheListener;
import org.jgraph.event.GraphModelEvent;
import org.jgraph.util.RectUtils;
/**
* An object that defines the view of a graphmodel. This object maps between
* model cells and views and provides a set of methods to change these views.
* The view may also contain its own set of attributes and is therefore an
* extension of an Observable, which may be observed by the GraphUI. It uses the
* model to send its changes to the command history.
*
* @version 1.0 1/1/02
* @author Gaudenz Alder
*/
@SuppressWarnings ( "all" )
public class GraphLayoutCache implements CellMapper, Serializable {
/**
* True if the cells should be auto-sized when their values change. Default
* is false.
*/
protected boolean autoSizeOnValueChange = false;
/**
* Boolean indicating whether existing connections should me made visible if
* their sources or targets are made visible, given the opposite end of the
* edge is already visible or made visible, too. Default is true.
*/
protected boolean showsExistingConnections = true;
/**
* Boolean indicating whether connections should be made visible when
* reconnected and their source and target port is visible. Default is true.
*/
protected boolean showsChangedConnections = true;
/**
* Boolean indicating whether edited cells should be made visible if they
* are changed via
* {@link #edit(Map, ConnectionSet, ParentMap, UndoableEdit[])}. Default is
* true.
*/
protected boolean showsInvisibleEditedCells = true;
/**
* Boolean indicating whether inserted should be made visible if they are
* inserted via
* {@link #insert(Object[], Map, ConnectionSet, ParentMap, UndoableEdit[])}.
* Default is true.
*/
protected boolean showsInsertedCells = true;
/**
* Boolean indicating whether inserted edges should me made visible if their
* sources or targets are already visible. Default is true.
*/
protected boolean showsInsertedConnections = true;
/**
* Boolean indicating whether existing connections should be hidden if their
* source or target and no parent of the ports is visible, either by hiding
* the cell or by changing the source or target of the edge to a hidden
* cell. Default is true.
*/
protected boolean hidesExistingConnections = true;
/**
* Boolean indicating whether existing connections should be hidden if their
* source or target port is removed from the model. Default is false.
*/
protected boolean hidesDanglingConnections = false;
/**
* Boolean indicating whether cellviews should be remembered once visible in
* this GraphLayoutCache. Default is true.
*/
protected boolean remembersCellViews = true;
/**
* Boolean indicating whether inserted cells should automatically be
* selected. Default is true. This is ignored if the cache is partial. Note:
* Despite the name of this field the implementation is located in the
* BasicGraphUI.GraphModelHandler.graphChanged method.
*/
protected boolean selectsAllInsertedCells = false;
/**
* Boolean indicating whether cells that are inserted using the local insert
* method should automatically be selected. Default is true. This is ignored
* if the cache is not partial and selectsAllInsertedCells is true, in which
* case the cells will be selected through another mechanism. Note: Despite
* the name of this field the implementation is located in the
* BasicGraphUI.GraphLayoutCacheObserver.changed method.
*/
protected boolean selectsLocalInsertedCells = false;
/**
* Boolean indicating whether children should be moved to the parent group's
* origin on expand. Default is true.
*/
protected boolean movesChildrenOnExpand = true;
/**
* Boolean indicating whether parents should be moved to the child area
* origin on collapse. Default is true.
*/
protected boolean movesParentsOnCollapse = true;
/**
* Boolean indicating whether parents should always be resized to the child
* area on collapse. If false the size is only initially updated if it has
* not yet been assigned. Default is false.
*/
protected boolean resizesParentsOnCollapse = false;
/**
* Specified the initial x- and y-scaling factor for initial collapsed group
* bounds. Default is 1.0, ie. no scaling.
*/
protected double collapseXScale = 1.0, collapseYScale = 1.0;
/**
* Boolean indicating whether edges should be reconneted to visible parents
* on collapse/expand. Default is false.
*
* @deprecated edges are moved to parent view and back automatically
*/
protected boolean reconnectsEdgesToVisibleParent = false;
/**
* The list of listeners that listen to the GraphLayoutCache.
*/
protected EventListenerList listenerList = new EventListenerList();
/**
* Reference to the graphModel
*/
protected GraphModel graphModel;
/**
* Maps cells to views.
*/
protected Map mapping = new Hashtable();
/**
* Maps cells to views. The hidden mapping is used to remembed cell views
* that are hidden, based on the remembersCellViews setting. hiddenMapping
* must use weak keys for the cells since when cells are removed
* hiddenMapping is not updated.
*/
protected transient Map hiddenMapping = new WeakHashMap();
/**
* Factory to create the views.
*/
protected CellViewFactory factory = null;
/**
* The set of visible cells.
*/
protected Set visibleSet = new HashSet();
/**
* Ordered list of roots for the view.
*/
protected List roots = new ArrayList();
/**
* Cached array of all ports for the view.
*/
protected PortView[] ports;
/**
* Only portions of the model are visible.
*/
protected boolean partial = false;
/**
* Controls if all attributes are local. If this is false then the
* createLocalEdit will check the localAttributes set to see if a specific
* attribute is local, otherwise it will assume that all attributes are
* local. This allows to make all attributes local without actually knowing
* them. Default is false.
*/
protected boolean allAttributesLocal = false;
/**
* A set containing all attribute keys that are stored in the cell views, in
* other words, the view-local attributes.
*/
protected Set localAttributes = new HashSet();
/**
* Constructs a graph layout cache.
*/
public GraphLayoutCache() {
this(new DefaultGraphModel(), new DefaultCellViewFactory());
}
/**
* Constructs a view for the specified model that uses <code>factory</code>
* to create its views.
*
* @param model
* the model that constitues the data source
*/
public GraphLayoutCache(GraphModel model, CellViewFactory factory) {
this(model, factory, false);
}
/**
* Constructs a view for the specified model that uses <code>factory</code>
* to create its views.
*
* @param model
* the model that constitues the data source
*/
public GraphLayoutCache(GraphModel model, CellViewFactory factory,
boolean partial) {
this(model, factory, null, null, partial);
}
/**
* Constructs a view for the specified model that uses <code>factory</code>
* to create its views.
*
* @param model
* the model that constitues the data source
*/
public GraphLayoutCache(GraphModel model, CellViewFactory factory,
CellView[] cellViews, CellView[] hiddenCellViews, boolean partial) {
this.factory = factory;
this.partial = partial;
if (cellViews != null) {
graphModel = model;
for (int i = 0; i < cellViews.length; i++) {
if (cellViews[i] != null) {
putMapping(cellViews[i].getCell(), cellViews[i]);
if (partial)
visibleSet.add(cellViews[i].getCell());
}
}
insertViews(cellViews);
// Notify observers for autosizing?
} else {
setModel(model);
}
if (hiddenCellViews != null) {
for (int i = 0; i < hiddenCellViews.length; i++)
hiddenMapping.put(hiddenCellViews[i].getCell(),
hiddenCellViews[i]);
}
}
//
// GraphLayoutCacheListeners
//
/**
* Adds a listener for the GraphLayoutCacheEvent posted after the graph
* layout cache changes.
*
* @see #removeGraphLayoutCacheListener
* @param l
* the listener to add
*/
public void addGraphLayoutCacheListener(GraphLayoutCacheListener l) {
listenerList.add(GraphLayoutCacheListener.class, l);
}
/**
* Removes a listener previously added with <B>addGraphLayoutCacheListener()
* </B>.
*
* @see #addGraphLayoutCacheListener
* @param l
* the listener to remove
*/
public void removeGraphLayoutCacheListener(GraphLayoutCacheListener l) {
listenerList.remove(GraphLayoutCacheListener.class, l);
}
/**
* Invoke this method after you've changed how the cells are to be
* represented in the graph.
*/
public void cellViewsChanged(final CellView[] cellViews) {
if (cellViews != null) {
fireGraphLayoutCacheChanged(this,
new GraphLayoutCacheEvent.GraphLayoutCacheChange() {
public Object[] getInserted() {
return null;
}
public Object[] getRemoved() {
return null;
}
public Map getPreviousAttributes() {
return null;
}
public Object getSource() {
return this;
}
public Object[] getChanged() {
return cellViews;
}
public Map getAttributes() {
return null;
}
public Object[] getContext() {
return null;
}
public Rectangle2D getDirtyRegion() {
return null;
}
public void setDirtyRegion(Rectangle2D dirty) {}
});
}
}
/*
* Notify 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.
*
* @see EventListenerList
*/
protected void fireGraphLayoutCacheChanged(Object source,
GraphLayoutCacheEvent.GraphLayoutCacheChange edit) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
GraphLayoutCacheEvent e = null;
// 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] == GraphLayoutCacheListener.class) {
// Lazily create the event:
if (e == null)
e = new GraphLayoutCacheEvent(source, edit);
((GraphLayoutCacheListener) listeners[i + 1])
.graphLayoutCacheChanged(e);
}
}
}
/**
* Return an array of all GraphLayoutCacheListener that were added to this
* model.
*/
public GraphLayoutCacheListener[] getGraphLayoutCacheListeners() {
return (GraphLayoutCacheListener[]) listenerList
.getListeners(GraphLayoutCacheListener.class);
}
//
// Accessors
//
/**
* Sets the factory that creates the cell views.
*/
public void setFactory(CellViewFactory factory) {
this.factory = factory;
}
/**
* Returns the factory that was passed to the constructor.
*/
public CellViewFactory getFactory() {
return factory;
}
/**
* Sets the current model.
*/
public void setModel(GraphModel model) {
roots.clear();
mapping.clear();
hiddenMapping.clear();
visibleSet.clear();
graphModel = model;
if (!isPartial()) {
Object[] cells = DefaultGraphModel.getRoots(getModel());
CellView[] cellViews = getMapping(cells, true);
insertViews(cellViews);
}
// Update PortView Cache and Notify Observers
updatePorts();
cellViewsChanged(getRoots());
}
/**
* @return Returns an unordered array of all visible cellviews.
*/
public CellView[] getCellViews() {
Collection coll = mapping.values();
CellView[] result = new CellView[coll.size()];
coll.toArray(result);
return result;
}
/**
* Returns the bounding box for the specified cell views.
*/
public static Rectangle2D getBounds(CellView[] views) {
if (views != null && views.length > 0) {
Rectangle2D r = (views[0] != null) ? views[0].getBounds() : null;
Rectangle2D ret = (r != null) ? (Rectangle2D) r.clone() : null;
for (int i = 1; i < views.length; i++) {
r = (views[i] != null) ? views[i].getBounds() : null;
if (r != null) {
if (ret == null)
ret = (r != null) ? (Rectangle2D) r.clone() : null;
else
Rectangle2D.union(ret, r, ret);
}
}
return ret;
}
return null;
}
/**
* A helper method to return various arrays of cells that are visible in
* this cache. For example, to get all selected vertices in a graph, do
* <code>graph.getSelectionCells(graph.getGraphLayoutCache().getCells(false, true,
false, false));</code>
*/
public Object[] getCells(boolean groups, boolean vertices, boolean ports,
boolean edges) {
CellView[] views = getCellViews();
List result = new ArrayList(views.length);
GraphModel model = getModel();
for (int i = 0; i < views.length; i++) {
Object cell = views[i].getCell();
boolean isEdge = model.isEdge(cell);
if ((ports || !model.isPort(cell))) {
if (((((ports || vertices) && !isEdge) || (edges && isEdge)) && views[i]
.isLeaf())
|| (groups && !views[i].isLeaf()))
result.add(views[i].getCell());
}
}
return result.toArray();
}
/**
* Returns a nested map of (cell, map) pairs that represent all attributes
* of all cell views in this view.
*
* @see #getCellViews
*/
public Map createNestedMap() {
CellView[] cellViews = getCellViews();
Map nested = new Hashtable();
for (int i = 0; i < cellViews.length; i++) {
nested.put(cellViews[i].getCell(), new Hashtable((Map) cellViews[i]
.getAllAttributes().clone()));
}
return nested;
}
/**
* @return Returns an unordered array of all hidden cellviews.
*/
public CellView[] getHiddenCellViews() {
Collection coll = hiddenMapping.values();
CellView[] result = new CellView[coll.size()];
coll.toArray(result);
return result;
}
/**
* Remaps all existing views using the CellViewFactory
* and replaces the respective root views.
*/
public synchronized void reload() {
List newRoots = new ArrayList();
Map oldMapping = new Hashtable(mapping);
mapping.clear();
Iterator it = oldMapping.keySet().iterator();
Set rootsSet = new HashSet(roots);
while (it.hasNext()) {
Object cell = it.next();
CellView oldView = (CellView) oldMapping.get(cell);
CellView newView = getMapping(cell, true);
newView.changeAttributes(this, oldView.getAttributes());
// newView.refresh(getModel(), this, false);
if (rootsSet.contains(oldView))
newRoots.add(newView);
}
// replace hidden
hiddenMapping.clear();
roots = newRoots;
}
/**
* Returns the current model.
*/
public GraphModel getModel() {
return graphModel;
}
/**
* Returns the roots of the view.
*/
public CellView[] getRoots() {
CellView[] views = new CellView[roots.size()];
roots.toArray(views);
return views;
}
/**
* Return all root cells that intersect the given rectangle.
*/
public CellView[] getRoots(Rectangle2D clip) {
java.util.List result = new ArrayList();
CellView[] views = getRoots();
for (int i = 0; i < views.length; i++)
if (views[i].getBounds().intersects(clip))
result.add(views[i]);
views = new CellView[result.size()];
result.toArray(views);
return views;
}
/**
* Returns a an array with the visible cells in <code>cells</code>.
*/
public Object[] getVisibleCells(Object[] cells) {
if (cells != null) {
List result = new ArrayList(cells.length);
for (int i = 0; i < cells.length; i++)
if (isVisible(cells[i]))
result.add(cells[i]);
return result.toArray();
}
return null;
}
/**
* Returns the ports of the view.
*/
public PortView[] getPorts() {
return ports;
}
/**
* Updates the cached array of ports.
*/
protected void updatePorts() {
Object[] roots = DefaultGraphModel.getRoots(graphModel);
List list = DefaultGraphModel.getDescendants(graphModel, roots);
if (list != null) {
ArrayList result = new ArrayList();
Iterator it = list.iterator();
while (it.hasNext()) {
Object cell = it.next();
if (graphModel.isPort(cell)) {
CellView portView = getMapping(cell, false);
if (portView != null) {
result.add(portView);
portView.refresh(this, this, false);
}
}
}
ports = new PortView[result.size()];
result.toArray(ports);
}
}
public void refresh(CellView[] views, boolean create) {
if (views != null)
for (int i = 0; i < views.length; i++)
refresh(views[i], create);
}
public void refresh(CellView view, boolean create) {
if (view != null) {
view.refresh(this, this, create);
CellView[] children = view.getChildViews();
for (int i = 0; i < children.length; i++)
refresh(children[i], create);
}
}
public void update(CellView[] views) {
if (views != null)
for (int i = 0; i < views.length; i++)
update(views[i]);
}
public void update(CellView view) {
if (view != null) {
view.update(this);
CellView[] children = view.getChildViews();
for (int i = 0; i < children.length; i++)
update(children[i]);
}
}
//
// Update View based on Model Change
//
/**
* Called from BasicGraphUI.ModelHandler to update the view based on the
* specified GraphModelEvent.
*/
public void graphChanged(GraphModelEvent.GraphModelChange change) {
// Get Old Attributes From GraphModelChange (Undo) -- used to remap
// removed cells
CellView[] views = change.getViews(this);
if (views != null) {
// Only ex-visible views are piggybacked
for (int i = 0; i < views.length; i++)
if (views[i] != null) {
// Do not use putMapping because cells are invisible
mapping.put(views[i].getCell(), views[i]);
}
// Ensure visible state
setVisibleImpl(getCells(views), true);
}
// Fetch View Order Of Changed Cells (Before any changes)
Object[] changed = change.getChanged();
// Fetch Views to Insert before Removal (Special case: two step process,
// see setModel)
getMapping(change.getInserted(), true);
// Remove and Hide Roots
views = removeCells(change.getRemoved());
// Store Removed Attributes In GraphModelChange (Undo)
change.putViews(this, views);
// Insert New Roots
// insertViews(insertViews);
// Hide edges with invisible source or target
if (isPartial()) {
// Then show
showCellsForChange(change);
// First hide
hideCellsForChange(change);
}
// Refresh Changed Cells
if (changed != null && changed.length > 0) {
// Restore All Cells in Model Order (Replace Roots)
for (int i = 0; i < changed.length; i++) {
CellView view = getMapping(changed[i], false);
if (view != null) {
view.refresh(this, this, true);
// Update child edges in groups (routing)
update(view);
}
}
}
reloadRoots();
// Refresh Context of Changed Cells (=Connected Edges)
refresh(getMapping(getContext(change), false), false);
updatePorts();
}
/**
* Completely reloads all roots from the model in the order returned by
* DefaultGraphModel.getAll. This uses the current visibleSet and mapping to
* fetch the cell views for the cells.
*/
protected void reloadRoots() {
// Reorder roots
Object[] orderedCells = DefaultGraphModel.getAll(graphModel);
List newRoots = new ArrayList();
for (int i = 0; i < orderedCells.length; i++) {
CellView view = getMapping(orderedCells[i], false);
if (view != null) {
view.refresh(this, this, true);
if (view.getParentView() == null) {
newRoots.add(view);
}
}
}
roots = newRoots;
}
/**
* Hook for subclassers to augment the context for a graphChange. This means
* you can add additional cells that should be refreshed on a special change
* event. eg. parallel edges when one is removed or added.
*/
protected Object[] getContext(GraphModelEvent.GraphModelChange change) {
return change.getContext();
}
protected void hideCellsForChange(GraphModelEvent.GraphModelChange change) {
// Hide visible edges between invisible vertices
// 1. Remove attached edges of removed cells
// 2. Remove edges who's source or target has changed to
// invisible.
Object[] tmp = change.getRemoved();
Set removed = new HashSet();
if (tmp != null)
for (int i = 0; i < tmp.length; i++)
removed.add(tmp[i]);
if (hidesDanglingConnections || hidesExistingConnections) {
Object[] changed = change.getChanged();
for (int i = 0; i < changed.length; i++) {
CellView view = getMapping(changed[i], false);
if (view instanceof EdgeView) {
EdgeView edge = (EdgeView) view;
Object oldSource = (edge.getSource() == null) ? null : edge
.getSource().getCell();
Object oldTarget = (edge.getTarget() == null) ? null : edge
.getTarget().getCell();
Object newSource = graphModel.getSource(changed[i]);
Object newTarget = graphModel.getTarget(changed[i]);
boolean hideExisting = (hidesExistingConnections && ((newSource != null && !hasVisibleParent(
newSource, null)) || (newTarget != null && !hasVisibleParent(
newTarget, null))));
if ((hidesDanglingConnections && (removed
.contains(oldSource) || removed.contains(oldTarget)))
|| hideExisting) {
setVisibleImpl(new Object[] { changed[i] }, false);
}
}
}
}
}
/**
* Checks if the port or one of its parents is visible.
*/
protected boolean hasVisibleParent(Object cell, Set invisible) {
boolean isVisible = false;
do {
isVisible = (invisible == null || !invisible.contains(cell)) ? isVisible(cell)
: false;
cell = getModel().getParent(cell);
} while (cell != null && !isVisible);
return isVisible;
}
protected void showCellsForChange(GraphModelEvent.GraphModelChange change) {
Object[] inserted = change.getInserted();
if (inserted != null && showsInsertedConnections) {
for (int i = 0; i < inserted.length; i++) {
if (!isVisible(inserted[i])) {
Object source = graphModel.getSource(inserted[i]);
Object target = graphModel.getTarget(inserted[i]);
if ((source != null || target != null)
&& (isVisible(source) && isVisible(target)))
setVisibleImpl(new Object[] { inserted[i] }, true);
}
}
}
if (change.getConnectionSet() != null) {
Set changedSet = change.getConnectionSet().getChangedEdges();
if (changedSet != null && showsChangedConnections) {
Object[] changed = changedSet.toArray();
for (int i = 0; i < changed.length; i++) {
if (!isVisible(changed[i])) {
Object source = graphModel.getSource(changed[i]);
Object target = graphModel.getTarget(changed[i]);
if ((source != null || target != null)
&& (isVisible(source) && isVisible(target))
&& !isVisible(changed[i]))
setVisibleImpl(new Object[] { changed[i] }, true);
}
}
}
}
}
/**
* Adds the specified model root cells to the view. Do not add a view that
* is already in roots.
*/
public void insertViews(CellView[] views) {
if (views != null) {
refresh(views, true);
for (int i = 0; i < views.length; i++) {
if (views[i] != null
&& getMapping(views[i].getCell(), false) != null) {
CellView parentView = views[i].getParentView();
Object parent = (parentView != null) ? parentView.getCell()
: null;
if (!graphModel.isPort(views[i].getCell())
&& parent == null) {
roots.add(views[i]);
}
}
}
}
}
/**
* Removes the specified model root cells from the view by removing the
* mapping between the cell and its view and makes the cells invisible.
*/
public CellView[] removeCells(Object[] cells) {
if (cells != null && cells.length > 0) {
CellView[] views = new CellView[cells.length];
// Store views to be removed from roots in an intermediate
// set for performance reasons
Set removedRoots = null;
for (int i = 0; i < cells.length; i++) {
views[i] = removeMapping(cells[i]);
if (views[i] != null) {
views[i].removeFromParent();
if (removedRoots == null) {
removedRoots = new HashSet();
}
removedRoots.add(views[i]);
visibleSet.remove(views[i].getCell());
}
}
if (removedRoots !=null && removedRoots.size() > 0) {
// If any roots have been removed, reform the roots
// lists appropriately, keeping the order the same
int newRootsSize = roots.size() - removedRoots.size();
if (newRootsSize < 8) {
newRootsSize = 8;
}
List newRoots = new ArrayList(newRootsSize);
Iterator iter = roots.iterator();
while (iter.hasNext()) {
Object cell = iter.next();
if (!removedRoots.contains(cell)) {
newRoots.add(cell);
}
}
roots = newRoots;
}
return views;
}
return null;
}
//
// Cell Mapping
//
/**
* Takes an array of views and returns the array of the corresponding cells
* by using <code>getCell</code> for each view.
*/
public Object[] getCells(CellView[] views) {
if (views != null) {
Object[] cells = new Object[views.length];
for (int i = 0; i < views.length; i++)
if (views[i] != null)
cells[i] = views[i].getCell();
return cells;
}
return null;
}
/**
* Returns the view for the specified cell. If create is true and no view is
* found then a view is created using createView(Object).
*/
public CellView getMapping(Object cell, boolean create) {
if (cell == null)
return null;
CellView view = (CellView) mapping.get(cell);
if (view == null && create && isVisible(cell)) {
view = (CellView) hiddenMapping.get(cell);
if (view != null) {
putMapping(cell, view);
hiddenMapping.remove(cell);
} else {
view = factory.createView(graphModel, cell);
putMapping(cell, view);
view.refresh(this, this, true); // Create Dependent
// Views
view.update(this);
}
}
return view;
}
/**
* Returns the views for the specified array of cells without creating these
* views on the fly.
*/
public CellView[] getMapping(Object[] cells) {
return getMapping(cells, false);
}
/**
* Returns the views for the specified array of cells. Returned array may
* contain null pointers if the respective cell is not mapped in this view
* and <code>create</code> is <code>false</code>.
*/
public CellView[] getMapping(Object[] cells, boolean create) {
if (cells != null) {
CellView[] result = new CellView[cells.length];
for (int i = 0; i < cells.length; i++)
result[i] = getMapping(cells[i], create);
return result;
}
return null;
}
/**
* Associates the specified model cell with the specified view.
*/
public void putMapping(Object cell, CellView view) {
if (cell != null && view != null)
mapping.put(cell, view);
}
/**
* Removes the associaten for the specified model cell and returns the view
* that was previously associated with the cell. Updates the portlist if
* necessary.
*/
public CellView removeMapping(Object cell) {
if (cell != null) {
CellView view = (CellView) mapping.remove(cell);
return view;
}
return null;
}
//
// Partial View
//
// Null is always visible!
public boolean isVisible(Object cell) {
return !isPartial() || visibleSet.contains(cell) || cell == null;
}
public Set getVisibleSet() {
return new HashSet(visibleSet);
}
public void setVisibleSet(Set visible) {
visibleSet = visible;
}
/**
* Makes the specified cell visible or invisible depending on the flag
* passed in. Note the cell really is a cell, not a cell view.
*
* @param cell
* the cell whose visibility is to be changed
* @param visible
* <code>true</code> if cell is to be made visible
*/
public void setVisible(Object cell, boolean visible) {
setVisible(new Object[] { cell }, visible);
}
/**
* Makes the specified cells visible or invisible depending on the flag
* passed in. Note the cells really are cells, not cell views.
*
* @param cells
* the cells whose visibility is to be changed
* @param visible
* <code>true</code> if the cells are to be made visible
*/
public void setVisible(Object[] cells, boolean visible) {
if (visible)
setVisible(cells, null);
else
setVisible(null, cells);
}
/**
* Changes the visibility state of the cells passed in. Note that the arrays
* must contain cells, not cell views.
*
* @param visible
* cells to be made visible
* @param invisible
* cells to be made invisible
*/
public void setVisible(Object[] visible, Object[] invisible) {
setVisible(visible, invisible, null);
}
/**
* Changes the visibility state of the cells passed in. Note that the arrays
* must contain cells, not cell views.
*
* @param visible
* cells to be made visible
* @param invisible
* cells to be made invisible
* @param cs
* a <code>ConnectionSet</code> describing the new state of
* edge connections in the graph
*/
public void setVisible(Object[] visible, Object[] invisible,
ConnectionSet cs) {
GraphLayoutCacheEdit edit = new GraphLayoutCacheEdit(null, null,
visible, invisible);
edit.end();
graphModel.edit(null, cs, null, new UndoableEdit[] { edit });
}
// This is used to augment the array passed to the setVisible method.
protected Object[] addVisibleDependencies(Object[] cells, boolean visible) {
if (cells != null) {
if (visible) {
// Make ports and source and target vertex visible
Set all = new HashSet();
for (int i = 0; i < cells.length; i++) {
all.add(cells[i]);
// Add ports
all.addAll(getPorts(cells[i]));
// Add source vertex and ports
Collection coll = getParentPorts(graphModel
.getSource(cells[i]));
if (coll != null)
all.addAll(coll);
// Add target vertex and ports
coll = getParentPorts(graphModel.getTarget(cells[i]));
if (coll != null)
all.addAll(coll);
}
if (showsExistingConnections) {
Set tmp = DefaultGraphModel.getEdges(getModel(), cells);
Iterator it = tmp.iterator();
while (it.hasNext()) {
Object obj = it.next();
Object source = graphModel.getSource(obj);
Object target = graphModel.getTarget(obj);
if ((isVisible(source) || all.contains(source))
&& (isVisible(target) || all.contains(target)))
all.add(obj);
}
}
all.removeAll(visibleSet);
all.remove(null);
return all.toArray();
} else {
if (hidesExistingConnections) {
Set all = new HashSet();
for (int i = 0; i < cells.length; i++) {
all.addAll(getPorts(cells[i]));
all.add(cells[i]);
}
Iterator it = DefaultGraphModel.getEdges(graphModel, cells)
.iterator();
while (it.hasNext()) {
Object edge = it.next();
Object newSource = graphModel.getSource(edge);
Object newTarget = graphModel.getTarget(edge);
// Note: At this time the cells are not yet hidden
if ((newSource != null && !hasVisibleParent(newSource,
all))
|| (newTarget != null && !hasVisibleParent(
newTarget, all))) {
all.add(edge);
}
}
all.remove(null);
return all.toArray();
}
}
}
return cells;
}
/**
* The actual implementation of changing cells' visibility state. This
* method does not deal with creating the undo or updating the
* GraphLayoutCache correctly. The <code>setVisible</code> methods in this
* class are intended to be the main public way to change visiblilty.
* However, if you do not require the undo to be formed, this method is much
* quicker, just note that you must call <code>updatePorts</code> if this
* method returns true.
*
* @param cells
* @param visible
* @return whether or not the ports needed updating in the calling method
*/
public boolean setVisibleImpl(Object[] cells, boolean visible) {
cells = addVisibleDependencies(cells, visible);
if (cells != null && isPartial()) {
boolean updatePorts = false;
// Update Visible Set
CellView[] views = new CellView[cells.length];
if (!visible) {
views = removeCells(cells);
}
// Set used for model roots contains call for performance
Set modelRoots = null;
for (int i = 0; i < cells.length; i++) {
if (cells[i] != null) {
if (visible) {
visibleSet.add(cells[i]);
views[i] = getMapping(cells[i], true);
} else {
if (views[i] != null) {
if (modelRoots == null) {
modelRoots = new HashSet(DefaultGraphModel
.getRootsAsCollection(getModel()));
}
if (modelRoots.contains(views[i].getCell())
&& remembersCellViews) {
hiddenMapping.put(views[i].getCell(), views[i]);
}
updatePorts = true;
}
}
}
}
// Make Cell Views Visible (if not already in place)
if (visible) {
Set parentSet = new HashSet();
Set removedRoots = null;
for (int i = 0; i < views.length; i++) {
if (views[i] != null) {
CellView view = views[i];
// Remove all children from roots
CellView[] children = AbstractCellView
.getDescendantViews(new CellView[] { view });
for (int j = 0; j < children.length; j++) {
if (removedRoots == null) {
removedRoots = new HashSet();
}
removedRoots.add(children[j]);
}
view.refresh(this, this, false);
// Link cellView into graphLayoutCache
CellView parentView = view.getParentView();
if (parentView != null)
parentSet.add(parentView);
updatePorts = true;
}
}
if (removedRoots !=null && removedRoots.size() > 0) {
// If any roots have been removed, reform the roots
// lists appropriately, keeping the order the same
List newRoots = new ArrayList();
Iterator iter = roots.iterator();
while (iter.hasNext()) {
Object cell = iter.next();
if (!removedRoots.contains(cell)) {
newRoots.add(cell);
}
}
roots = newRoots;
}
CellView[] parentViews = new CellView[parentSet.size()];
parentSet.toArray(parentViews);
refresh(parentViews, true);
}
return updatePorts;
}
return false;
}
protected Collection getParentPorts(Object cell) {
// does nothing if a parent is already visible
Object parent = graphModel.getParent(cell);
while (parent != null) {
if (isVisible(parent))
return null;
parent = graphModel.getParent(parent);
}
// Else returns the parent and all ports
parent = graphModel.getParent(cell);
Collection collection = getPorts(parent);
collection.add(parent);
return collection;
}
protected Collection getPorts(Object cell) {
LinkedList list = new LinkedList();
for (int i = 0; i < graphModel.getChildCount(cell); i++) {
Object child = graphModel.getChild(cell, i);
if (graphModel.isPort(child))
list.add(child);
}
return list;
}
//
// Change Support
//
public boolean isPartial() {
return partial;
}
/**
* Required for XML persistence
*
* @return whether or not the cache is partial
*/
public boolean getPartial() {
return isPartial();
}
/**
* Messaged when the user has altered the value for the item identified by
* cell to newValue. If newValue signifies a truly new value the model
* should post a graphCellsChanged event. This calls
* augmentNestedMapForValueChange.
*/
public void valueForCellChanged(Object cell, Object newValue) {
Map nested = null;
if (isAutoSizeOnValueChange()) {
CellView view = getMapping(cell, false);
if (view != null) {
AttributeMap attrs = view.getAllAttributes();
Rectangle2D bounds = GraphConstants.getBounds(attrs);
Rectangle2D dummyBounds = null;
// Force the model to store the old bounds
if (bounds != null) {
dummyBounds = attrs.createRect(bounds.getX(),
bounds.getY(), 0, 0);
} else {
dummyBounds = attrs.createRect(0, 0, 0, 0);
}
nested = GraphConstants.createAttributes(new Object[] { cell },
new Object[] { GraphConstants.RESIZE,
GraphConstants.BOUNDS }, new Object[] {
Boolean.TRUE, dummyBounds });
}
} else {
nested = new Hashtable();
nested.put(cell, new Hashtable());
}
augmentNestedMapForValueChange(nested, cell, newValue);
edit(nested, null, null, null);
}
/**
* Hook for subclassers to add more stuff for value changes. Currently this
* adds the new value to the change.
*/
protected void augmentNestedMapForValueChange(Map nested, Object cell,
Object newValue) {
Map attrs = (Map) nested.get(cell);
if (attrs != null)
GraphConstants.setValue(attrs, newValue);
}
/**
* Inserts the <code>cells</code> and connections into the model, and
* absorbs the local attributes. This implementation sets the inserted cells
* visible and selects the new roots depending on graph.selectNewCells.
*/
public void insert(Object[] roots, Map attributes, ConnectionSet cs,
ParentMap pm, UndoableEdit[] e) {
Object[] visible = null;
if (isPartial() && showsInsertedCells) {
List tmp = DefaultGraphModel.getDescendants(graphModel, roots);
tmp.removeAll(visibleSet);
if (!tmp.isEmpty())
visible = tmp.toArray();
}
// Absorb local attributes
GraphLayoutCacheEdit edit = createLocalEdit(roots, attributes, visible,
null);
if (edit != null)
e = augment(e, edit);
graphModel.insert(roots, attributes, cs, pm, e);
}
/**
* Inserts the cloned cells from the clone map and clones the passed-in
* arguments according to the clone map before insertion and returns the
* clones in order of the cells. This example shows how to clone the current
* selection and get a reference to the clones:
*
* <pre>
* Object[] cells = graph.getDescendants(graph.order(graph.getSelectionCells()));
* 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);
* </pre>
*/
public Object[] insertClones(Object[] cells, Map clones, Map nested,
ConnectionSet cs, ParentMap pm, double dx, double dy) {
if (cells != null) {
if (cs != null)
cs = cs.clone(clones);
if (pm != null)
pm = pm.clone(clones);
if (nested != null) {
nested = GraphConstants.replaceKeys(clones, nested);
AttributeMap.translate(nested.values(), dx, dy);
}
// Replace cells in order
Object[] newCells = new Object[cells.length];
for (int i = 0; i < cells.length; i++)
newCells[i] = clones.get(cells[i]);
// Insert into cache/model
insert(newCells, nested, cs, pm, null);
return newCells;
}
return null;
}
/**
* Inserts the specified vertex into the graph model. This method does in
* fact nothing, it calls insert edge with the vertex and the source and
* target port set to null. This example shows how to add a vertex with a
* port and a black border:
*
* <pre>
* DefaultGraphCell vertex = new DefaultGraphCell("Hello, world!");
* Map attrs = vertex.getAttributes();
* GraphConstants.setOpaque(attrs, false);
* GraphConstants.setBorderColor(attrs, Color.black);
* DefaultPort port = new DefaultPort();
* vertex.add(port);
* port.setParent(vertex);
* graph.getGraphLayoutCache().insert(vertex);
* </pre>
*
* @param cell
* inserts the specified cell in the cache
*/
public void insert(Object cell) {
insert(new Object[] { cell });
}
/**
* Inserts the specified edge into the graph model. This method does in fact
* nothing, it calls insert with a default connection set.
*
* @param edge
* the edge to be inserted
* @param source
* the source port this edge is connected to
* @param target
* the target port this edge is connected to
*/
public void insertEdge(Object edge, Object source, Object target) {
insert(new Object[] { edge }, new Hashtable(), new ConnectionSet(edge,
source, target), new ParentMap());
}
/**
* Inserts the specified cells into the graph model. This method is a
* general implementation of cell insertion. If the source and target port
* are null, then no connection set is created. The method uses the
* attributes from the specified edge and the egdge's children to construct
* the insert call. This example shows how to insert an edge with a special
* arrow between two known vertices:
*
* <pre>
* Object source = graph.getDefaultPortForCell(sourceVertex).getCell();
* Object target = graph.getDefaultPortForCell(targetVertex).getCell();
* DefaultEdge edge = new DefaultEdge("Hello, world!");
* edge.setSource(source);
* edge.setTarget(target);
* Map attrs = edge.getAttributes();
* GraphConstants.setLineEnd(attrs, GraphConstants.ARROW_TECHNICAL);
* graph.getGraphLayoutCache().insert(edge);
* </pre>
*/
public void insert(Object[] cells) {
insert(cells, new Hashtable(), new ConnectionSet(), new ParentMap());
}
/**
* Variant of the insert method that allows to pass a default connection set
* and parent map and nested map.
*/
public void insert(Object[] cells, Map nested, ConnectionSet cs,
ParentMap pm) {
if (cells != null) {
if (nested == null)
nested = new Hashtable();
if (cs == null)
cs = new ConnectionSet();
if (pm == null)
pm = new ParentMap();
for (int i = 0; i < cells.length; i++) {
// Using the children of the vertex we construct the parent map.
int childCount = getModel().getChildCount(cells[i]);
for (int j = 0; j < childCount; j++) {
Object child = getModel().getChild(cells[i], j);
pm.addEntry(child, cells[i]);
// And add their attributes to the nested map
AttributeMap attrs = getModel().getAttributes(child);
if (attrs != null)
nested.put(child, attrs);
}
// A nested map with the vertex as key
// and its attributes as the value
// is required for the model.
Map attrsTmp = (Map) nested.get(cells[i]);
Map attrs = getModel().getAttributes(cells[i]);
if (attrsTmp != null)
attrs.putAll(attrsTmp);
nested.put(cells[i], attrs);
// Check if we have parameters for a connection set.
Object sourcePort = getModel().getSource(cells[i]);
if (sourcePort != null)
cs.connect(cells[i], sourcePort, true);
Object targetPort = getModel().getTarget(cells[i]);
if (targetPort != null)
cs.connect(cells[i], targetPort, false);
}
// Create an array with the parent and its children.
cells = DefaultGraphModel.getDescendants(getModel(), cells)
.toArray();
// Finally call the insert method on the parent class.
insert(cells, nested, cs, pm, null);
}
}
/**
* Inserts the specified cell as a parent of children. Note: All cells that
* are not yet in the model will be inserted. This example shows how to
* group the current selection and pass the group default bounds in case it
* is later collapsed:
*
* <pre>
* DefaultGraphCell group = new DefaultGraphCell("Hello, world!");
* Object[] cells = DefaultGraphModel.order(graph.getModel(), graph
* .getSelectionCells());
* Rectangle2D bounds = graph.getCellBounds(cells);
* if (bounds != null) {
* bounds = new Rectangle2D.Double(bounds.getX() + bounds.getWidth() / 4,
* bounds.getY() + bounds.getHeight() / 4, bounds.getWidth() / 2,
* bounds.getHeight() / 2);
* GraphConstants.setBounds(group.getAttributes(), bounds);
* }
* graph.getGraphLayoutCache().insertGroup(group, cells);
* </pre>
*/
public void insertGroup(Object group, Object[] children) {
if (group != null && children != null && children.length > 0) {
Map nested = new Hashtable();
// List to store all children that are not in the model
List newCells = new ArrayList(children.length + 1);
// Plus the group cell at pos 0
if (!getModel().contains(group)) {
newCells.add(group);
}
// Create a parent map for the group and the children, and
// store the children's attributes in the nested map.
// Note: This implementation assumes that the children have
// not yet been added to the group object. Therefore,
// the insert method will only collect the group
// attributes, but will ignore the child attributes.
ParentMap pm = new ParentMap();
for (int i = 0; i < children.length; i++) {
pm.addEntry(children[i], group);
if (!getModel().contains(children[i])) {
newCells.add(children[i]);
AttributeMap attrs = getModel().getAttributes(children[i]);
if (attrs != null)
nested.put(children[i], attrs);
}
}
if (newCells.isEmpty())
edit(nested, null, pm, null);
else
insert(newCells.toArray(), nested, null, pm);
}
}
/**
* Removes <code>cells</code> from the model.
*/
public void remove(Object[] cells) {
graphModel.remove(cells);
}
/**
* Removes cells from the model, including all children and connected edges
* if <code>children</code> or <code>edges</code> is true, respectively.
*
* @param cells
* The cells to remove.
* @param descendants
* Whether to remove all descendants as well.
* @param edges
* Whether to remove all connected edges as well.
*/
public void remove(Object[] cells, boolean descendants, boolean edges) {
if (cells != null && cells.length > 0) {
if (edges) {
Object[] tmp = DefaultGraphModel.getEdges(getModel(), cells)
.toArray();
Object[] newCells = new Object[cells.length + tmp.length];
System.arraycopy(cells, 0, newCells, 0, cells.length);
System.arraycopy(tmp, 0, newCells, cells.length, tmp.length);
cells = newCells;
}
if (descendants)
cells = DefaultGraphModel.getDescendants(getModel(), cells)
.toArray();
remove(cells);
}
}
/**
* Hides the specified cells with all children if <code>descandants</code>
* is true.
*/
public void hideCells(Object[] cells, boolean descandants) {
if (cells != null && cells.length > 0) {
if (descandants)
cells = DefaultGraphModel.getDescendants(getModel(), cells)
.toArray();
setVisible(cells, false);
}
}
/**
* Shows the specified cells with all children if <code>descandants</code>
* is true.
*/
public void showCells(Object[] cells, boolean descandants) {
if (cells != null && cells.length > 0) {
if (descandants)
cells = DefaultGraphModel.getDescendants(getModel(), cells)
.toArray();
setVisible(cells, true);
}
}
/**
* Ungroups all groups in cells and returns the children that are not ports.
* Note: This replaces the parents with their group cells in the group
* structure.
*/
public Object[] ungroup(Object[] cells) {
if (cells != null && cells.length > 0) {
ArrayList toRemove = new ArrayList();
ArrayList children = new ArrayList();
boolean groupExists = false;
for (int i = 0; i < cells.length; i++) {
boolean childExists = false;
ArrayList tempPortList = new ArrayList();
for (int j = 0; j < getModel().getChildCount(cells[i]); j++) {
Object child = getModel().getChild(cells[i], j);
if (!getModel().isPort(child)) {
children.add(child);
childExists = true;
} else {
tempPortList.add(child);
}
}
if (childExists) {
toRemove.addAll(tempPortList);
toRemove.add(cells[i]);
groupExists = true;
}
}
if (groupExists)
remove(toRemove.toArray());
return children.toArray();
}
return null;
}
/**
* Toggles the collapsed state of the specified cells.
*
* @param cells
* The cells to toggle the collapsed state for.
* @param collapseOnly
* Whether cells should only be collapsed.
* @param expandOnly
* Whether cells should only be expanded.
*
*/
public void toggleCollapsedState(Object[] cells, boolean collapseOnly,
boolean expandOnly) {
List toExpand = new ArrayList();
List toCollapse = new ArrayList();
for (int i = 0; i < cells.length; i++) {
Object cell = cells[i];
CellView view = getMapping(cell, false);
if (view != null) {
// Adds to list of expansion cells if it is a leaf in the layout
// cache and we do not only want to collapse.
if (view.isLeaf() && !collapseOnly)
toExpand.add(view.getCell());
// Else adds to list of to-be-collapsed cells if it is not a
// leaf in the layout cache we do not only want to expand.
else if (!view.isLeaf() && !expandOnly)
toCollapse.add(view.getCell());
}
}
if (!toCollapse.isEmpty() || !toExpand.isEmpty())
setCollapsedState(toCollapse.toArray(), toExpand.toArray());
}
/**
* Collapses all groups by hiding all their descendants.
*
* @param groups
*/
public void collapse(Object[] groups) {
setCollapsedState(groups, null);
}
/**
* Expands all groups by showing all children. (Note: This does not show all
* descandants, but only the first generation of children.)
*/
public void expand(Object[] cells) {
setCollapsedState(null, cells);
}
/**
* Collapses and/or expands the specified cell(s)
*
* @param collapse
* the cells to be collapsed
* @param expand
* the cells to be expanded
*/
public void setCollapsedState(Object[] collapse, Object[] expand) {
// Get all descandants for the groups
ConnectionSet cs = new ConnectionSet();
// Collapse cells
List toHide = DefaultGraphModel.getDescendants(getModel(), collapse);
if (collapse != null) {
// Remove the groups themselfes
for (int i = 0; i < collapse.length; i++) {
toHide.remove(collapse[i]);
cellWillCollapse(collapse[i]);
}
// Remove the ports (will be hidden automatically)
for (int i = 0; i < collapse.length; i++) {
int childCount = getModel().getChildCount(collapse[i]);
if (childCount > 0) {
for (int j = 0; j < childCount; j++) {
Object child = getModel().getChild(collapse[i], j);
if (getModel().isPort(child)) {
toHide.remove(child);
}
}
}
}
}
// Expand cells
Set toShow = new HashSet();
if (expand != null) {
for (int i = 0; i < expand.length; i++) {
int childCount = getModel().getChildCount(expand[i]);
for (int j = 0; j < childCount; j++) {
toShow.add(getModel().getChild(expand[i], j));
}
}
}
setVisible(toShow.toArray(),
(toHide != null) ? toHide.toArray() : null, cs);
}
/**
* Hook for subclassers to return the first or last visible port to replace
* the current source or target port of the edge. This is called when groups
* are collapsed for the edges that cross the group, ie. go from a child
* cell to a cell which is outside the group. This implementation returns
* the first port of the parent group if source is true, otherwise it
* returns the last port of the parent group.
*/
protected Object getParentPort(Object edge, boolean source) {
// Contains the parent of the parent vertex, eg. the group
Object parent = getModel().getParent(
(source) ? DefaultGraphModel.getSourceVertex(getModel(), edge)
: DefaultGraphModel.getTargetVertex(getModel(), edge));
// Finds a port in the group
int c = getModel().getChildCount(parent);
for (int i = (source) ? c - 1 : 0; i < getModel().getChildCount(parent)
&& i >= 0; i += (source) ? -1 : +1) {
Object child = getModel().getChild(parent, i);
if (getModel().isPort(child)) {
return child;
}
}
return null;
}
/**
* Hook for subclassers to return the port to be used for edges that have
* been connected to the group. This is called from expand. This returns the
* first port of the first or last vertex depending on <code>source</code>.
*/
protected Object getChildPort(Object edge, boolean source) {
GraphModel model = getModel();
// Contains the parent of the port, eg. the group
Object parent = (source) ? DefaultGraphModel.getSourceVertex(model,
edge) : DefaultGraphModel.getTargetVertex(model, edge);
// Finds a vertex in the group
int c = model.getChildCount(parent);
for (int i = (source) ? c - 1 : 0; i < c && i >= 0; i += (source) ? -1
: +1) {
Object child = model.getChild(parent, i);
if (!model.isEdge(child) && !model.isPort(child)) {
// Finds a port in the vertex
for (int j = 0; j < model.getChildCount(child); j++) {
Object port = model.getChild(child, j);
if (model.isPort(port)) {
return port;
}
}
}
}
return null;
}
/**
* Applies the <code>propertyMap</code> and the connection changes to the
* model. The initial <code>edits</code> that triggered the call are
* considered to be part of this transaction. Notifies the model- and undo
* listeners of the change. Note: The passed in attributes may contain
* PortViews.
*/
public void edit(Map attributes, ConnectionSet cs, ParentMap pm,
UndoableEdit[] e) {
if (attributes != null || cs != null || pm != null || e != null) {
Object[] visible = null;
if (isPartial() && showsInvisibleEditedCells) {
Set tmp = new HashSet();
if (attributes != null)
tmp.addAll(attributes.keySet());
if (cs != null)
tmp.addAll(cs.getChangedEdges());
if (pm != null)
tmp.addAll(pm.getChangedNodes());
tmp.removeAll(visibleSet);
if (!tmp.isEmpty())
visible = tmp.toArray();
}
GraphLayoutCacheEdit edit = createLocalEdit(null, attributes,
visible, null);
if (edit != null)
e = augment(e, edit);
// Pass to model
graphModel.edit(attributes, cs, pm, e);
}
}
/**
* A shortcut method that takes a nested map and passes it to the edit
* method.
*/
public void edit(Map attributes) {
edit(attributes, null, null, null);
}
/**
* Applies the <code>attributes</code> to all <code>cells</code> by
* creating a map that contains the attributes for each cell and passing it
* to edit on this layout cache. Example:
*
* <pre>
* Map attrs = new java.util.Hashtable();
* GraphConstants.setBackground(attrs, Color.RED);
* graph.getGraphLayoutCache().edit(graph.getSelectionCells(), attrs);
* </pre>
*/
public void edit(Object[] cells, Map attributes) {
if (attributes != null && cells != null && cells.length > 0) {
Map nested = new Hashtable();
for (int i = 0; i < cells.length; i++)
nested.put(cells[i], attributes);
edit(nested, null, null, null);
}
}
/**
* Applies the <code>attributes</code> to a single <code>cell</code> by
* creating a map that contains the attributes for this cell and passing it
* to edit on this layout cache. Example:
*
* <pre>
* Map attrs = new java.util.Hashtable();
* GraphConstants.setBackground(attrs, Color.RED);
* graph.getGraphLayoutCache().editCell(graph.getSelectionCell(), attrs);
* </pre>
*/
public void editCell(Object cell, Map attributes) {
if (attributes != null && cell != null) {
edit(new Object[] { cell }, attributes);
}
}
protected UndoableEdit[] augment(UndoableEdit[] e, UndoableEdit edit) {
if (edit != null) {
int size = (e != null) ? e.length + 1 : 1;
UndoableEdit[] result = new UndoableEdit[size];
if (e != null)
System.arraycopy(e, 0, result, 0, size - 2);
result[size - 1] = edit;
return result;
}
return e;
}
/**
* Sends <code>cells</code> to back. Note: This expects an array of cells!
*/
public void toBack(Object[] cells) {
if (cells != null && cells.length > 0) {
graphModel.toBack(cells);
}
}
/**
* Brings <code>cells</code> to front. Note: This expects an array of
* cells!
*/
public void toFront(Object[] cells) {
if (cells != null && cells.length > 0) {
graphModel.toFront(cells);
}
}
/**
* Creates a local edit for the specified change. A local operation contains
* all visibility changes, as well as all changes to attributes that are
* local, and all control attributes. <br>
* Note: You must use cells as keys for the nested map, not cell views.
*/
protected GraphLayoutCacheEdit createLocalEdit(Object[] inserted,
Map nested, Object[] visible, Object[] invisible) {
// Create an edit if there are any view-local attributes set
if ((nested != null && !nested.isEmpty())
&& (!localAttributes.isEmpty() || isAllAttributesLocal())) {
// Move or Copy Local Attributes to Local View
Map globalMap = new Hashtable();
Map localMap = new Hashtable();
Map localAttr;
Iterator it = nested.entrySet().iterator();
while (it.hasNext()) {
localAttr = new Hashtable();
Map.Entry entry = (Map.Entry) it.next();
// (cell, Hashtable)
Object cell = entry.getKey();
Map attr = (Map) entry.getValue();
// Create Difference of Existing and New Attributes
CellView tmpView = getMapping(cell, false);
if (tmpView != null)
attr = tmpView.getAllAttributes().diff(attr);
// End of Diff
Iterator it2 = attr.entrySet().iterator();
while (it2.hasNext()) {
Map.Entry entry2 = (Map.Entry) it2.next();
// (key, value)
Object key = entry2.getKey();
Object value = entry2.getValue();
boolean isControlAttribute = isControlAttribute(cell, key,
value);
if (isAllAttributesLocal() || isControlAttribute
|| isLocalAttribute(cell, key, value)) {
localAttr.put(key, value);
if (!isControlAttribute)
it2.remove();
}
}
if (!localAttr.isEmpty())
localMap.put(cell, localAttr);
if (!attr.isEmpty())
globalMap.put(cell, attr);
}
nested.clear();
nested.putAll(globalMap);
if (visible != null || invisible != null || !localMap.isEmpty()) {
GraphLayoutCacheEdit edit = new GraphLayoutCacheEdit(inserted,
new Hashtable(localMap), visible, invisible);
edit.end();
return edit;
}
} else if (visible != null || invisible != null) {
GraphLayoutCacheEdit edit = new GraphLayoutCacheEdit(inserted,
null, visible, invisible);
edit.end();
return edit;
}
return null;
}
/**
* Returns true if the set of local attributes contains <code>key</code>
*/
protected boolean isLocalAttribute(Object cell, Object key, Object value) {
return localAttributes.contains(key);
}
/**
* Returns true if <code>key</code> is a control attribute
*/
protected boolean isControlAttribute(Object cell, Object key, Object value) {
return GraphConstants.REMOVEALL.equals(key)
|| GraphConstants.REMOVEATTRIBUTES.equals(key);
}
/**
* Handles the removal of view local attributes. Since these attributes are
* only being stored in the view, the option is provided to copy the values
* for that key into the model. Without this, those values are lost.
*
* @param key
* the key of the view local attribute
* @param addToModel
* whether or not to move the attribute values to the graph model
* @param override
* whether or not to override the key's value in the model cell's
* attribute map if it exists
* @return whether or not the operation completed sucessfully
*/
public boolean removeViewLocalAttribute(Object key, boolean addToModel,
boolean override) {
if (localAttributes.contains(key)) {
if (addToModel) {
// Iterate through all views copying this attribute to the
// cell.
copyRemovedViewValue(key, addToModel, override, mapping
.values());
copyRemovedViewValue(key, addToModel, override, hiddenMapping
.values());
}
localAttributes.remove(key);
return true;
}
return false;
}
/**
* Helper method to copy removed view local attributes to model cell's
*
* @param key
* the key of the view local attribute
* @param addToModel
* whether or not to move the attribute values to the graph model
* @param override
* whether or not to override the key's value in the model cell's
* attribute map if it exists
* @param coll
* the current collection being analysed
*/
private void copyRemovedViewValue(Object key, boolean addToModel,
boolean override, Collection coll) {
Iterator iter = coll.iterator();
while (iter.hasNext()) {
CellView cellView = (CellView) iter.next();
Map attributes = cellView.getAttributes();
if (attributes.containsKey(key)) {
if (addToModel) {
Object cell = cellView.getCell();
Map cellAttributes = graphModel.getAttributes(cell);
if (cellAttributes != null) {
boolean cellContainsKey = cellAttributes
.containsKey(key);
// Write the model cell's attribute map key
// if overriding is enabled or if key doesn't
// exist
if (!override || !cellContainsKey) {
Object value = attributes.get(key);
cellAttributes.put(key, value);
}
}
}
attributes.remove(key);
}
}
}
/**
* An implementation of GraphLayoutCacheChange.
*/
public class GraphLayoutCacheEdit extends CompoundEdit implements
GraphLayoutCacheEvent.GraphLayoutCacheChange {
protected Object[] cells, previousCells = null;
protected CellView[] context, hidden;
protected Map attributes, previousAttributes;
protected Object[] visible, invisible;
/**
* The dirty region associated with this event prior to the change
*/
protected Rectangle2D dirtyRegion = null;
// Remember which cells have changed for finding their context
protected Set changedCells = new HashSet();
/**
* Constructs a GraphViewEdit. This modifies the attributes of the
* specified views and may be used to notify UndoListeners.
*
* @param nested
* the map that defines the new attributes
*/
public GraphLayoutCacheEdit(Map nested) {
this(null, nested, null, null);
attributes = nested;
}
/**
* Constructs a GraphViewEdit. This modifies the attributes of the
* specified views and may be used to notify UndoListeners. This should
* also take an array of removed cell views, but it is not possible to
* add further UndoableEdits to an already executed CompoundEdit, such
* as a GraphModel change. Thus, to handle implicit changes -- rather
* than piggybacking on the model's event -- the CompoundEdit's addEdit
* method should be extended to accept and instantly execute sub-
* sequent edits (implicit changes to the view, such as removing a
* mapping, hiding a view or the like).
*
* @param inserted
* an array of inserted cells
* @param attributes
* the map that defines the new attributes
* @param visible
* an array defining which cells are visible
* @param invisible
* an array defining which cells are invisible
*/
public GraphLayoutCacheEdit(Object[] inserted, Map attributes,
Object[] visible, Object[] invisible) {
super();
this.attributes = attributes;
this.previousAttributes = attributes;
this.cells = inserted;
this.visible = visible;
this.invisible = invisible;
}
public Object getSource() {
return GraphLayoutCache.this;
}
public boolean isSignificant() {
return true;
}
/**
* Returns the cell views that have changed.
*/
public Object[] getChanged() {
return changedCells.toArray();
}
/**
* Returns the cells that habe been made visible.
*/
public Object[] getInserted() {
return invisible;
}
/**
* Returns the cells that have changed.
*/
public Object[] getRemoved() {
return visible;
}
/**
* Returns the views that have not changed explicitly, but implicitly
* because one of their dependent cells has changed.
*/
public Object[] getContext() {
return context;
}
/**
* Returns a map of (cell view, attribute) pairs.
*/
public Map getAttributes() {
return attributes;
}
/**
* Returns a map of (cell view, attribute) pairs.
*/
public Map getPreviousAttributes() {
return previousAttributes;
}
public Rectangle2D getDirtyRegion() {
return dirtyRegion;
}
public void setDirtyRegion(Rectangle2D dirty) {
this.dirtyRegion = dirty;
}
/**
* Redoes a change.
*
* @exception CannotRedoException
* if the change cannot be redone
*/
public void redo() throws CannotRedoException {
super.redo();
execute();
}
/**
* Undoes a change.
*
* @exception CannotUndoException
* if the change cannot be undone
*/
public void undo() throws CannotUndoException {
super.undo();
execute();
}
/**
* Execute this edit such that the next invocation to this method will
* invert the last execution.
*/
public void execute() {
GraphModel model = getModel();
changedCells.clear();
// Remember or restore hidden cells
if (hidden != null)
for (int i = 0; i < hidden.length; i++)
if (hidden[i] != null)
mapping.put(hidden[i].getCell(), hidden[i]);
if (invisible != null && invisible.length >0) {
CellView[] invisibleViews = new CellView[invisible.length];
invisibleViews = getMapping(invisible, true);
Rectangle2D changedBounds = getBounds(invisibleViews);
dirtyRegion = RectUtils.union(dirtyRegion, changedBounds);
}
if (!remembersCellViews) // already remembered
hidden = getMapping(invisible);
// Handle visibility
boolean updatePorts = setVisibleImpl(visible, true)
| setVisibleImpl(invisible, false);
if (visible != null) {
for (int i = 0; i < visible.length; i++) {
changedCells.add(visible[i]);
// Only calls if not inserted
if (cells == null)
cellExpanded(visible[i]);
}
}
if (invisible != null)
for (int i = 0; i < invisible.length; i++)
changedCells.add(invisible[i]);
// Swap arrays
Object[] tmp = visible;
visible = invisible;
invisible = tmp;
// Handle attributes
if (attributes != null) {
previousAttributes = attributes;
changedCells.addAll(attributes.keySet());
}
if (updatePorts)
updatePorts();
// Add ancestor cells to changed cells
Set parentSet = new HashSet();
Iterator it = changedCells.iterator();
while (it.hasNext()) {
Object parent = model.getParent(it.next());
while (parent != null) {
parentSet.add(parent);
parent = model.getParent(parent);
}
}
changedCells.addAll(parentSet);
Set ctx = DefaultGraphModel.getEdges(getModel(), changedCells
.toArray());
context = getMapping(ctx.toArray());
Set allChangedCells = new HashSet(changedCells);
allChangedCells.addAll(ctx);
CellView[] allChangedCellViews = getMapping(allChangedCells.toArray());
Rectangle2D changedBounds = getBounds(allChangedCellViews);
dirtyRegion = RectUtils.union(dirtyRegion, changedBounds);
if (attributes != null) {
attributes = handleAttributes(attributes);
}
// Refresh all changed cells
refresh(getMapping(changedCells.toArray(), false), false);
// Updates the connected edges. Make sure that changedCells
// contains no edges, as these will be removed from the result.
refresh(context, false);
tmp = cells;
cells = previousCells;
previousCells = tmp;
reloadRoots();
fireGraphLayoutCacheChanged(GraphLayoutCache.this, this);
}
}
/**
* Called when a child has been made visible by expanding its parent. This
* implementation translates the child so that it reflects the offset of the
* parent group since the child was last visible (see
* {@link #movesChildrenOnExpand}).
*/
protected void cellExpanded(Object cell) {
GraphModel model = getModel();
// Moves the child to the group origin if it is not a port
if (movesChildrenOnExpand && !model.isPort(cell)) {
CellView view = getMapping(cell, false);
if (view != null) {
CellView parent = getMapping(model.getParent(cell), false);
if (parent != null) {
if (DefaultGraphModel.isVertex(model, parent))
{
// Computes the offset of the parent group
Rectangle2D src = GraphConstants.getBounds(parent
.getAllAttributes());
Rectangle2D rect = parent.getBounds();
double dx = src.getX() - rect.getX();
double dy = src.getY() - rect.getY();
// Gets the attributes from the cell view or
// cell and translates the bounds or points
AttributeMap attrs = view.getAttributes();
if (!attrs.contains(GraphConstants.BOUNDS))
attrs = model.getAttributes(view.getCell());
attrs.translate(dx, dy);
}
}
}
}
}
protected void cellWillCollapse(Object cell) {
GraphModel model = getModel();
if (movesParentsOnCollapse) {
CellView view = getMapping(cell, false);
if (view != null && !view.isLeaf()) {
// Uses view-local attribute if available
AttributeMap attrs = view.getAttributes();
if (!attrs.contains(GraphConstants.BOUNDS)
&& !localAttributes.contains(GraphConstants.BOUNDS))
attrs = model.getAttributes(cell);
// Moves the group to the origin of the children
Rectangle2D src = GraphConstants.getBounds(attrs);
Rectangle2D b = view.getBounds();
// FIXME: What if the group is exactly at "defaultBounds"?
if (resizesParentsOnCollapse || src == null
|| src.equals(VertexView.defaultBounds)) {
src = attrs.createRect(b.getX(), b.getY(), b.getWidth()
* collapseXScale, b.getHeight() * collapseYScale);
attrs.applyValue(GraphConstants.BOUNDS, src);
} else {
src.setFrame(b.getX(), b.getY(), src.getWidth(), src
.getHeight());
}
}
}
}
/**
* Attention: Undo will not work for routing-change if ROUTING and POINTS
* are stored in different locations. This happens if the model holds the
* routing attribute and the routing changes from unrouted to routed. In
* this case the points in the view are already routed according to the new
* scheme when written to the command history (-> no undo).
*/
protected Map handleAttributes(Map attributes) {
Map undo = new Hashtable();
CellView[] views = new CellView[attributes.size()];
Iterator it = attributes.entrySet().iterator();
int i = 0;
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
CellView cv = getMapping(entry.getKey(), false);
views[i] = cv;
i += 1;
if (cv != null && cv.getAttributes() != null) {
Map deltaNew = (Map) entry.getValue();
// System.out.println("state=" + cv.getAttributes());
// System.out.println("change=" + deltaNew);
Map deltaOld = cv.getAttributes().applyMap(deltaNew);
cv.refresh(this, this, false);
// System.out.println("state'=" + cv.getAttributes());
// System.out.println("change'=" + deltaOld);
undo.put(cv.getCell(), deltaOld);
}
}
// Re-route all child edges
update(views);
return undo;
}
//
// Static Methods
//
/**
* Translates the specified views by the given amount.
*
* @param views
* an array of cell view to each be translated
* @param dx
* the amount to translate the views in the x-axis
* @param dy
* the amount to translate the views in the x-axis
*/
public static void translateViews(CellView[] views, double dx, double dy) {
for (int i = 0; i < views.length; i++) {
if (views[i] instanceof AbstractCellView) {
((AbstractCellView) views[i]).translate(dx, dy);
}
}
}
/**
* Returns a collection of cells that are connected to the specified cell by
* edges. Any cells specified in the exclude set will be ignored.
*
* @param cell
* The cell from which the neighbours will be determined
* @param exclude
* The set of cells to ignore when searching
* @param directed
* whether or not direction of edges should be taken into account
* @param visibleCells
* whether or not to only consider visible cells
* @return Returns the list of neighbours for <code>cell</code>
*/
public List getNeighbours(Object cell, Set exclude, boolean directed,
boolean visibleCells) {
// Traverse Graph
GraphModel model = getModel();
Object[] fanout = (directed) ? DefaultGraphModel.getOutgoingEdges(
model, cell) : DefaultGraphModel.getEdges(model,
new Object[] { cell }).toArray();
List neighbours = new ArrayList(fanout.length);
Set localExclude = new HashSet(fanout.length + 8, (float) 0.75);
for (int i = 0; i < fanout.length; i++) {
// if only visible cells are being processed, check that this
// edge is visible before looking for neighbours with it
if (!visibleCells || isVisible(fanout[i])) {
Object neighbour = DefaultGraphModel.getOpposite(model,
fanout[i], cell);
if (neighbour != null
&& (exclude == null || !exclude.contains(neighbour))
&& !localExclude.contains(neighbour)
&& (!visibleCells || isVisible(neighbour))) {
localExclude.add(neighbour);
neighbours.add(neighbour);
}
}
}
return neighbours;
}
/**
* Returns the outgoing edges for cell. Cell should be a port or a vertex.
*
* @param cell
* The cell from which the outgoing edges will be determined
* @param exclude
* The set of edges to ignore when searching
* @param visibleCells
* whether or not only visible cells should be processed
* @param selfLoops
* whether or not to include self loops in the returned list
* @return Returns the list of outgoing edges for <code>cell</code>
*/
public List getOutgoingEdges(Object cell, Set exclude,
boolean visibleCells, boolean selfLoops) {
return getEdges(cell, exclude, visibleCells, selfLoops, false);
}
/**
* Returns the incoming edges for cell. Cell should be a port or a vertex.
*
* @param cell
* The cell from which the incoming edges will be determined
* @param exclude
* The set of edges to ignore when searching
* @param visibleCells
* whether or not only visible cells should be processed
* @param selfLoops
* whether or not to include self loops in the returned list
* @return Returns the list of incoming edges for <code>cell</code>
*/
public List getIncomingEdges(Object cell, Set exclude,
boolean visibleCells, boolean selfLoops) {
return getEdges(cell, exclude, visibleCells, selfLoops, true);
}
/**
* Returns the incoming or outgoing edges for cell. Cell should be a port or
* a vertex.
*
* @param cell
* The cell from which the edges will be determined
* @param exclude
* The set of edges to ignore when searching
* @param visibleCells
* whether or not only visible cells should be processed
* @param selfLoops
* whether or not to include self loops in the returned list
* @param incoming
* <code>true</code> if incoming edges are to be obtained,
* <code>false</code> if outgoing edges are to be obtained
* @return Returns the list of incoming or outgoing edges for
* <code>cell</code>
*/
protected List getEdges(Object cell, Set exclude, boolean visibleCells,
boolean selfLoops, boolean incoming) {
GraphModel model = getModel();
Object[] edges = DefaultGraphModel.getEdges(model, cell, incoming);
List edgeList = new ArrayList(edges.length);
Set localExclude = new HashSet(edges.length);
for (int i = 0; i < edges.length; i++) {
// Check that the edge is neiter in the passed in exclude set or
// the local exclude set. Also, if visibleCells is true check
// the edge is visible in the cache.
if ((exclude == null || !exclude.contains(edges[i]))
&& !localExclude.contains(edges[i])
&& (!visibleCells || isVisible(edges[i]))) {
// Add the edge to the list if all edges, including self loops
// are allowed. If self loops are not allowed, ensure the
// source and target of the edge are different
if (selfLoops == true
|| model.getSource(edges[i]) != model
.getTarget(edges[i])) {
edgeList.add(edges[i]);
}
localExclude.add(edges[i]);
}
}
return edgeList;
}
/**
* Returns all views, shortcut to getAllDescendants(getRoots())
*/
public CellView[] getAllViews() {
return getAllDescendants(getRoots());
}
/**
* Returns all views, including descendants that have a parent in
* <code>views</code>, especially the PortViews. Note: Iterative
* Implementation using model.getChild and getMapping on this cell mapper.
*/
public CellView[] getAllDescendants(CellView[] views) {
Stack stack = new Stack();
for (int i = 0; i < views.length; i++)
if (views[i] != null)
stack.add(views[i]);
ArrayList result = new ArrayList();
while (!stack.isEmpty()) {
CellView tmp = (CellView) stack.pop();
Object[] children = tmp.getChildViews();
for (int i = 0; i < children.length; i++)
stack.add(children[i]);
result.add(tmp);
// Add Port Views
for (int i = 0; i < graphModel.getChildCount(tmp.getCell()); i++) {
Object child = graphModel.getChild(tmp.getCell(), i);
if (graphModel.isPort(child)) {
CellView view = getMapping(child, false);
if (view != null)
stack.add(view);
}
}
}
CellView[] ret = new CellView[result.size()];
result.toArray(ret);
return ret;
}
/**
* Returns the hiddenMapping.
*
* @return Map
*/
public Map getHiddenMapping() {
return hiddenMapping;
}
/**
* Sets the showsExistingConnections
*
* @param showsExistingConnections
*/
public void setShowsExistingConnections(boolean showsExistingConnections) {
this.showsExistingConnections = showsExistingConnections;
}
/**
* Returns the showsExistingConnections.
*
* @return boolean
*/
public boolean isShowsExistingConnections() {
return showsExistingConnections;
}
/**
* Sets the showsInsertedConnections
*
* @param showsInsertedConnections
*/
public void setShowsInsertedConnections(boolean showsInsertedConnections) {
this.showsInsertedConnections = showsInsertedConnections;
}
/**
* Returns the showsInsertedConnections.
*
* @return boolean
*/
public boolean isShowsInsertedConnections() {
return showsInsertedConnections;
}
/**
* Sets the hidesExistingConnections
*
* @param hidesExistingConnections
*/
public void setHidesExistingConnections(boolean hidesExistingConnections) {
this.hidesExistingConnections = hidesExistingConnections;
}
/**
* Returns the hidesExistingConnections.
*
* @return boolean
*/
public boolean isHidesExistingConnections() {
return hidesExistingConnections;
}
/**
* Sets the hidesDanglingConnections
*
* @param hidesDanglingConnections
*/
public void setHidesDanglingConnections(boolean hidesDanglingConnections) {
this.hidesDanglingConnections = hidesDanglingConnections;
}
/**
* Returns the hidesDanglingConnections.
*
* @return boolean
*/
public boolean isHidesDanglingConnections() {
return hidesDanglingConnections;
}
/**
* Sets the rememberCellViews.
*
* @param rememberCellViews
* The rememberCellViews to set
*/
public void setRemembersCellViews(boolean rememberCellViews) {
this.remembersCellViews = rememberCellViews;
}
/**
* Returns the remembersCellViews.
*
* @return boolean
*/
public boolean isRemembersCellViews() {
return remembersCellViews;
}
/**
* Sets the hiddenSet.
*
* @param hiddenSet
* The hiddenSet to set
*/
public void setHiddenSet(Map hiddenSet) {
this.hiddenMapping = hiddenSet;
}
/**
* @return Returns the localAttributes.
*/
public Set getLocalAttributes() {
return localAttributes;
}
/**
* @param localAttributes
* The localAttributes to set.
*/
public void setLocalAttributes(Set localAttributes) {
this.localAttributes = localAttributes;
}
/**
* @return Returns the askLocalAttribute.
*/
public boolean isAllAttributesLocal() {
return allAttributesLocal;
}
/**
* @param allAttributesLocal
* The allAttributesLocal to set.
*/
public void setAllAttributesLocal(boolean allAttributesLocal) {
this.allAttributesLocal = allAttributesLocal;
}
/**
* Returns true if cells should be auto-sized when their values change
*
* @return true if cells should be auto-sized when their values change
*/
public boolean isAutoSizeOnValueChange() {
return autoSizeOnValueChange;
}
/**
* Determines whether cells should be auto-sized when their values change.
* Fires a property change event if the new setting is different from the
* existing setting.
*
* @param flag
* a boolean value, true if cells should be auto-sized when their
* values change
*/
public void setAutoSizeOnValueChange(boolean flag) {
this.autoSizeOnValueChange = flag;
}
/**
* @return Returns the selectsAllInsertedCells.
*/
public boolean isSelectsAllInsertedCells() {
return selectsAllInsertedCells;
}
/**
* @param selectsAllInsertedCells
* The selectsAllInsertedCells to set.
*/
public void setSelectsAllInsertedCells(boolean selectsAllInsertedCells) {
this.selectsAllInsertedCells = selectsAllInsertedCells;
}
/**
* @return Returns the selectsLocalInsertedCells.
*/
public boolean isSelectsLocalInsertedCells() {
return selectsLocalInsertedCells;
}
/**
* @param selectsLocalInsertedCells
* The selectsLocalInsertedCells to set.
*/
public void setSelectsLocalInsertedCells(boolean selectsLocalInsertedCells) {
this.selectsLocalInsertedCells = selectsLocalInsertedCells;
}
/**
* @return Returns the reconnectsEdgesToVisibleParent.
* @deprecated edges are moved to parent view and back automatically
*/
public boolean isReconnectsEdgesToVisibleParent() {
return reconnectsEdgesToVisibleParent;
}
/**
* @param reconnectsEdgesToVisibleParent
* The reconnectsEdgesToVisibleParent to set.
* @deprecated edges are moved to parent view and back automatically
*/
public void setReconnectsEdgesToVisibleParent(
boolean reconnectsEdgesToVisibleParent) {
this.reconnectsEdgesToVisibleParent = reconnectsEdgesToVisibleParent;
}
/**
* @return Returns the showsChangedConnections.
*/
public boolean isShowsChangedConnections() {
return showsChangedConnections;
}
/**
* @param showsChangedConnections
* The showsChangedConnections to set.
*/
public void setShowsChangedConnections(boolean showsChangedConnections) {
this.showsChangedConnections = showsChangedConnections;
}
/**
* @return Returns the moveChildrenOnExpand.
*/
public boolean isMovesChildrenOnExpand() {
return movesChildrenOnExpand;
}
/**
* @param moveChildrenOnExpand
* The moveChildrenOnExpand to set.
*/
public void setMovesChildrenOnExpand(boolean moveChildrenOnExpand) {
this.movesChildrenOnExpand = moveChildrenOnExpand;
}
/**
* @return Returns the collapseXScale.
*/
public double getCollapseXScale() {
return collapseXScale;
}
/**
* @param collapseXScale
* The collapseXScale to set.
*/
public void setCollapseXScale(double collapseXScale) {
this.collapseXScale = collapseXScale;
}
/**
* @return Returns the collapseYScale.
*/
public double getCollapseYScale() {
return collapseYScale;
}
/**
* @param collapseYScale
* The collapseYScale to set.
*/
public void setCollapseYScale(double collapseYScale) {
this.collapseYScale = collapseYScale;
}
/**
* @return Returns the movesParentsOnCollapse.
*/
public boolean isMovesParentsOnCollapse() {
return movesParentsOnCollapse;
}
/**
* @param movesParentsOnCollapse
* The movesParentsOnCollapse to set.
*/
public void setMovesParentsOnCollapse(boolean movesParentsOnCollapse) {
this.movesParentsOnCollapse = movesParentsOnCollapse;
}
/**
* @return Returns the resizesParentsOnCollapse.
*/
public boolean isResizesParentsOnCollapse() {
return resizesParentsOnCollapse;
}
/**
* @param resizesParentsOnCollapse
* The resizesParentsOnCollapse to set.
*/
public void setResizesParentsOnCollapse(boolean resizesParentsOnCollapse) {
this.resizesParentsOnCollapse = resizesParentsOnCollapse;
}
/**
* Serialization support.
*/
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
// Write out the hidden mapping
Map map = new Hashtable(hiddenMapping);
s.writeObject(map);
}
/**
* Serialization support.
*/
private void readObject(ObjectInputStream s) throws IOException,
ClassNotFoundException {
s.defaultReadObject();
// Read the hidden mapping
Map map = (Map) s.readObject();
hiddenMapping = new WeakHashMap(map);
}
}