0001: /*
0002: * @(#)GraphLayoutCache.java 1.0 03-JUL-04
0003: *
0004: * Copyright (c) 2001-2004 Gaudenz Alder
0005: *
0006: */
0007: package org.jgraph.graph;
0008:
0009: import java.awt.geom.Rectangle2D;
0010: import java.io.IOException;
0011: import java.io.ObjectInputStream;
0012: import java.io.ObjectOutputStream;
0013: import java.io.Serializable;
0014: import java.util.ArrayList;
0015: import java.util.Collection;
0016: import java.util.HashSet;
0017: import java.util.Hashtable;
0018: import java.util.Iterator;
0019: import java.util.LinkedList;
0020: import java.util.List;
0021: import java.util.Map;
0022: import java.util.Set;
0023: import java.util.Stack;
0024: import java.util.WeakHashMap;
0025:
0026: import javax.swing.event.EventListenerList;
0027: import javax.swing.undo.CannotRedoException;
0028: import javax.swing.undo.CannotUndoException;
0029: import javax.swing.undo.CompoundEdit;
0030: import javax.swing.undo.UndoableEdit;
0031:
0032: import org.jgraph.event.GraphLayoutCacheEvent;
0033: import org.jgraph.event.GraphLayoutCacheListener;
0034: import org.jgraph.event.GraphModelEvent;
0035:
0036: /**
0037: * An object that defines the view of a graphmodel. This object maps between
0038: * model cells and views and provides a set of methods to change these views.
0039: * The view may also contain its own set of attributes and is therefore an
0040: * extension of an Observable, which may be observed by the GraphUI. It uses the
0041: * model to send its changes to the command history.
0042: *
0043: * @version 1.0 1/1/02
0044: * @author Gaudenz Alder
0045: */
0046: public class GraphLayoutCache implements CellMapper, Serializable {
0047:
0048: /**
0049: * True if the cells should be auto-sized when their values change. Default
0050: * is false.
0051: */
0052: protected boolean autoSizeOnValueChange = false;
0053:
0054: /**
0055: * Boolean indicating whether existing connections should me made visible if
0056: * their sources or targets are made visible, given the opposite end of the
0057: * edge is already visible or made visible, too. Default is true.
0058: */
0059: protected boolean showsExistingConnections = true;
0060:
0061: /**
0062: * Boolean indicating whether connections should be made visible when
0063: * reconnected and their source and target port is visible. Default is true.
0064: */
0065: protected boolean showsChangedConnections = true;
0066:
0067: /**
0068: * Boolean indicating whether edited cells should be made visible if they
0069: * are changed via
0070: * {@link #edit(Map, ConnectionSet, ParentMap, UndoableEdit[])}. Default is
0071: * true.
0072: */
0073: protected boolean showsInvisibleEditedCells = true;
0074:
0075: /**
0076: * Boolean indicating whether inserted should be made visible if they are
0077: * inserted via
0078: * {@link #insert(Object[], Map, ConnectionSet, ParentMap, UndoableEdit[])}.
0079: * Default is true.
0080: */
0081: protected boolean showsInsertedCells = true;
0082:
0083: /**
0084: * Boolean indicating whether inserted edges should me made visible if their
0085: * sources or targets are already visible. Default is true.
0086: */
0087: protected boolean showsInsertedConnections = true;
0088:
0089: /**
0090: * Boolean indicating whether existing connections should be hidden if their
0091: * source or target and no parent of the ports is visible, either by hiding
0092: * the cell or by changing the source or target of the edge to a hidden
0093: * cell. Default is true.
0094: */
0095: protected boolean hidesExistingConnections = true;
0096:
0097: /**
0098: * Boolean indicating whether existing connections should be hidden if their
0099: * source or target port is removed from the model. Default is false.
0100: */
0101: protected boolean hidesDanglingConnections = false;
0102:
0103: /**
0104: * Boolean indicating whether cellviews should be remembered once visible in
0105: * this GraphLayoutCache. Default is true.
0106: */
0107: protected boolean remembersCellViews = true;
0108:
0109: /**
0110: * Boolean indicating whether inserted cells should automatically be
0111: * selected. Default is true. This is ignored if the cache is partial. Note:
0112: * Despite the name of this field the implementation is located in the
0113: * BasicGraphUI.GraphModelHandler.graphChanged method.
0114: */
0115: protected boolean selectsAllInsertedCells = false;
0116:
0117: /**
0118: * Boolean indicating whether cells that are inserted using the local insert
0119: * method should automatically be selected. Default is true. This is ignored
0120: * if the cache is not partial and selectsAllInsertedCells is true, in which
0121: * case the cells will be selected through another mechanism. Note: Despite
0122: * the name of this field the implementation is located in the
0123: * BasicGraphUI.GraphLayoutCacheObserver.changed method.
0124: */
0125: protected boolean selectsLocalInsertedCells = false;
0126:
0127: /**
0128: * Boolean indicating whether children should be moved to the parent group's
0129: * origin on expand. Default is true.
0130: */
0131: protected boolean movesChildrenOnExpand = true;
0132:
0133: /**
0134: * Boolean indicating whether parents should be moved to the child area
0135: * origin on collapse. Default is true.
0136: */
0137: protected boolean movesParentsOnCollapse = true;
0138:
0139: /**
0140: * Boolean indicating whether parents should always be resized to the child
0141: * area on collapse. If false the size is only initially updated if it has
0142: * not yet been assigned. Default is false.
0143: */
0144: protected boolean resizesParentsOnCollapse = false;
0145:
0146: /**
0147: * Specified the initial x- and y-scaling factor for initial collapsed group
0148: * bounds. Default is 1.0, ie. no scaling.
0149: */
0150: protected double collapseXScale = 1.0, collapseYScale = 1.0;
0151:
0152: /**
0153: * Boolean indicating whether edges should be reconneted to visible parents
0154: * on collapse/expand. Default is false.
0155: *
0156: * @deprecated edges are moved to parent view and back automatically
0157: */
0158: protected boolean reconnectsEdgesToVisibleParent = false;
0159:
0160: /**
0161: * The list of listeners that listen to the model.
0162: */
0163: protected EventListenerList listenerList = new EventListenerList();
0164:
0165: /**
0166: * Reference to the graphModel
0167: */
0168: protected GraphModel graphModel;
0169:
0170: /**
0171: * Maps cells to views.
0172: */
0173: protected Map mapping = new Hashtable();
0174:
0175: /**
0176: * Maps cells to views. The hidden mapping is used to remembed cell views
0177: * that are hidden, based on the remembersCellViews setting. hiddenMapping
0178: * must use weak keys for the cells since when cells are removed
0179: * hiddenMapping is not updated.
0180: */
0181: protected transient Map hiddenMapping = new WeakHashMap();
0182:
0183: /**
0184: * Factory to create the views.
0185: */
0186: protected CellViewFactory factory = null;
0187:
0188: /**
0189: * The set of visible cells.
0190: */
0191: protected Set visibleSet = new HashSet();
0192:
0193: /**
0194: * Ordered list of roots for the view.
0195: */
0196: protected List roots = new ArrayList();
0197:
0198: /**
0199: * Cached array of all ports for the view.
0200: */
0201: protected PortView[] ports;
0202:
0203: /**
0204: * Only portions of the model are visible.
0205: */
0206: protected boolean partial = false;
0207:
0208: /**
0209: * Controls if all attributes are local. If this is false then the
0210: * createLocalEdit will check the localAttributes set to see if a specific
0211: * attribute is local, otherwise it will assume that all attributes are
0212: * local. This allows to make all attributes local without actually knowing
0213: * them. Default is false.
0214: */
0215: protected boolean allAttributesLocal = false;
0216:
0217: /**
0218: * A set containing all attribute keys that are stored in the cell views, in
0219: * other words, the view-local attributes.
0220: */
0221: protected Set localAttributes = new HashSet();
0222:
0223: /**
0224: * Constructs a graph layout cache.
0225: */
0226: public GraphLayoutCache() {
0227: this (new DefaultGraphModel(), new DefaultCellViewFactory());
0228: }
0229:
0230: /**
0231: * Constructs a view for the specified model that uses <code>factory</code>
0232: * to create its views.
0233: *
0234: * @param model
0235: * the model that constitues the data source
0236: */
0237: public GraphLayoutCache(GraphModel model, CellViewFactory factory) {
0238: this (model, factory, false);
0239: }
0240:
0241: /**
0242: * Constructs a view for the specified model that uses <code>factory</code>
0243: * to create its views.
0244: *
0245: * @param model
0246: * the model that constitues the data source
0247: */
0248: public GraphLayoutCache(GraphModel model, CellViewFactory factory,
0249: boolean partial) {
0250: this (model, factory, null, null, partial);
0251: }
0252:
0253: /**
0254: * Constructs a view for the specified model that uses <code>factory</code>
0255: * to create its views.
0256: *
0257: * @param model
0258: * the model that constitues the data source
0259: */
0260: public GraphLayoutCache(GraphModel model, CellViewFactory factory,
0261: CellView[] cellViews, CellView[] hiddenCellViews,
0262: boolean partial) {
0263: this .factory = factory;
0264: this .partial = partial;
0265: if (cellViews != null) {
0266: graphModel = model;
0267: for (int i = 0; i < cellViews.length; i++) {
0268: if (cellViews[i] != null) {
0269: putMapping(cellViews[i].getCell(), cellViews[i]);
0270: if (partial)
0271: visibleSet.add(cellViews[i].getCell());
0272: }
0273: }
0274: insertViews(cellViews);
0275: // Notify observers for autosizing?
0276: } else {
0277: setModel(model);
0278: }
0279: if (hiddenCellViews != null) {
0280: for (int i = 0; i < hiddenCellViews.length; i++)
0281: hiddenMapping.put(hiddenCellViews[i].getCell(),
0282: hiddenCellViews[i]);
0283: }
0284: }
0285:
0286: //
0287: // GraphLayoutCacheListeners
0288: //
0289:
0290: /**
0291: * Adds a listener for the GraphLayoutCacheEvent posted after the graph
0292: * layout cache changes.
0293: *
0294: * @see #removeGraphLayoutCacheListener
0295: * @param l
0296: * the listener to add
0297: */
0298: public void addGraphLayoutCacheListener(GraphLayoutCacheListener l) {
0299: listenerList.add(GraphLayoutCacheListener.class, l);
0300: }
0301:
0302: /**
0303: * Removes a listener previously added with <B>addGraphLayoutCacheListener()
0304: * </B>.
0305: *
0306: * @see #addGraphLayoutCacheListener
0307: * @param l
0308: * the listener to remove
0309: */
0310: public void removeGraphLayoutCacheListener(
0311: GraphLayoutCacheListener l) {
0312: listenerList.remove(GraphLayoutCacheListener.class, l);
0313: }
0314:
0315: /**
0316: * Invoke this method after you've changed how the cells are to be
0317: * represented in the graph.
0318: */
0319: public void cellViewsChanged(final CellView[] cellViews) {
0320: if (cellViews != null) {
0321: fireGraphLayoutCacheChanged(this ,
0322: new GraphLayoutCacheEvent.GraphLayoutCacheChange() {
0323:
0324: public Object[] getInserted() {
0325: return null;
0326: }
0327:
0328: public Object[] getRemoved() {
0329: return null;
0330: }
0331:
0332: public Map getPreviousAttributes() {
0333: return null;
0334: }
0335:
0336: public Object getSource() {
0337: return this ;
0338: }
0339:
0340: public Object[] getChanged() {
0341: return cellViews;
0342: }
0343:
0344: public Map getAttributes() {
0345: return null;
0346: }
0347:
0348: public Object[] getContext() {
0349: return null;
0350: }
0351:
0352: });
0353: }
0354: }
0355:
0356: /*
0357: * Notify all listeners that have registered interest for notification on
0358: * this event type. The event instance is lazily created using the
0359: * parameters passed into the fire method.
0360: *
0361: * @see EventListenerList
0362: */
0363: protected void fireGraphLayoutCacheChanged(Object source,
0364: GraphLayoutCacheEvent.GraphLayoutCacheChange edit) {
0365: // Guaranteed to return a non-null array
0366: Object[] listeners = listenerList.getListenerList();
0367: GraphLayoutCacheEvent e = null;
0368: // Process the listeners last to first, notifying
0369: // those that are interested in this event
0370: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0371: if (listeners[i] == GraphLayoutCacheListener.class) {
0372: // Lazily create the event:
0373: if (e == null)
0374: e = new GraphLayoutCacheEvent(source, edit);
0375: ((GraphLayoutCacheListener) listeners[i + 1])
0376: .graphLayoutCacheChanged(e);
0377: }
0378: }
0379: }
0380:
0381: /**
0382: * Return an array of all GraphLayoutCacheListener that were added to this
0383: * model.
0384: */
0385: public GraphLayoutCacheListener[] getGraphLayoutCacheListeners() {
0386: return (GraphLayoutCacheListener[]) listenerList
0387: .getListeners(GraphLayoutCacheListener.class);
0388: }
0389:
0390: //
0391: // Accessors
0392: //
0393:
0394: /**
0395: * Sets the factory that creates the cell views.
0396: */
0397: public void setFactory(CellViewFactory factory) {
0398: this .factory = factory;
0399: }
0400:
0401: /**
0402: * Returns the factory that was passed to the constructor.
0403: */
0404: public CellViewFactory getFactory() {
0405: return factory;
0406: }
0407:
0408: /**
0409: * Sets the current model.
0410: */
0411: public void setModel(GraphModel model) {
0412: roots.clear();
0413: mapping.clear();
0414: hiddenMapping.clear();
0415: visibleSet.clear();
0416: graphModel = model;
0417: if (!isPartial()) {
0418: Object[] cells = DefaultGraphModel.getRoots(getModel());
0419: CellView[] cellViews = getMapping(cells, true);
0420: insertViews(cellViews);
0421: }
0422: // Update PortView Cache and Notify Observers
0423: updatePorts();
0424: cellViewsChanged(getRoots());
0425: }
0426:
0427: /**
0428: * @return Returns an unordered array of all visible cellviews.
0429: */
0430: public CellView[] getCellViews() {
0431: Collection coll = mapping.values();
0432: CellView[] result = new CellView[coll.size()];
0433: coll.toArray(result);
0434: return result;
0435: }
0436:
0437: /**
0438: * Returns the bounding box for the specified cell views.
0439: */
0440: public static Rectangle2D getBounds(CellView[] views) {
0441: if (views != null && views.length > 0) {
0442: Rectangle2D r = views[0].getBounds();
0443: Rectangle2D ret = (r != null) ? (Rectangle2D) r.clone()
0444: : null;
0445: for (int i = 1; i < views.length; i++) {
0446: r = views[i].getBounds();
0447: if (r != null) {
0448: if (ret == null)
0449: ret = (r != null) ? (Rectangle2D) r.clone()
0450: : null;
0451: else
0452: Rectangle2D.union(ret, r, ret);
0453: }
0454: }
0455: return ret;
0456: }
0457: return null;
0458: }
0459:
0460: /**
0461: * A helper method to return various arrays of cells that are visible in
0462: * this cache. For example, to get all selected vertices in a graph, do
0463: * <code>graph.getSelectionCells(graph.getGraphLayoutCache().getCells(false, true,
0464: false, false));</code>
0465: */
0466: public Object[] getCells(boolean groups, boolean vertices,
0467: boolean ports, boolean edges) {
0468: CellView[] views = getCellViews();
0469: List result = new ArrayList(views.length);
0470: GraphModel model = getModel();
0471: for (int i = 0; i < views.length; i++) {
0472: Object cell = views[i].getCell();
0473: boolean isEdge = model.isEdge(cell);
0474: if ((ports || !model.isPort(cell))) {
0475: if (((((ports || vertices) && !isEdge) || (edges && isEdge)) && views[i]
0476: .isLeaf())
0477: || (groups && !views[i].isLeaf()))
0478: result.add(views[i].getCell());
0479:
0480: }
0481: }
0482: return result.toArray();
0483: }
0484:
0485: /**
0486: * Returns a nested map of (cell, map) pairs that represent all attributes
0487: * of all cell views in this view.
0488: *
0489: * @see #getCellViews
0490: */
0491: public Map createNestedMap() {
0492: CellView[] cellViews = getCellViews();
0493: Map nested = new Hashtable();
0494: for (int i = 0; i < cellViews.length; i++) {
0495: nested.put(cellViews[i].getCell(), new Hashtable(
0496: (Map) cellViews[i].getAllAttributes().clone()));
0497: }
0498: return nested;
0499: }
0500:
0501: /**
0502: * @return Returns an unordered array of all hidden cellviews.
0503: */
0504: public CellView[] getHiddenCellViews() {
0505: Collection coll = hiddenMapping.values();
0506: CellView[] result = new CellView[coll.size()];
0507: coll.toArray(result);
0508: return result;
0509: }
0510:
0511: /**
0512: * Remaps all existing views using the CellViewFactory
0513: * and replaces the respective root views.
0514: */
0515: public synchronized void reload() {
0516: List newRoots = new ArrayList();
0517: Map oldMapping = new Hashtable(mapping);
0518: mapping.clear();
0519: Iterator it = oldMapping.keySet().iterator();
0520: Set rootsSet = new HashSet(roots);
0521: while (it.hasNext()) {
0522: Object cell = it.next();
0523: CellView oldView = (CellView) oldMapping.get(cell);
0524: CellView newView = getMapping(cell, true);
0525: newView.changeAttributes(this , oldView.getAttributes());
0526: // newView.refresh(getModel(), this, false);
0527: if (rootsSet.contains(oldView))
0528: newRoots.add(newView);
0529: }
0530: // replace hidden
0531: hiddenMapping.clear();
0532: roots = newRoots;
0533: }
0534:
0535: /**
0536: * Returns the current model.
0537: */
0538: public GraphModel getModel() {
0539: return graphModel;
0540: }
0541:
0542: /**
0543: * Returns the roots of the view.
0544: */
0545: public CellView[] getRoots() {
0546: CellView[] views = new CellView[roots.size()];
0547: roots.toArray(views);
0548: return views;
0549: }
0550:
0551: /**
0552: * Return all root cells that intersect the given rectangle.
0553: */
0554: public CellView[] getRoots(Rectangle2D clip) {
0555: java.util.List result = new ArrayList();
0556: CellView[] views = getRoots();
0557: for (int i = 0; i < views.length; i++)
0558: if (views[i].getBounds().intersects(clip))
0559: result.add(views[i]);
0560: views = new CellView[result.size()];
0561: result.toArray(views);
0562: return views;
0563: }
0564:
0565: /**
0566: * Returns a an array with the visible cells in <code>cells</code>.
0567: */
0568: public Object[] getVisibleCells(Object[] cells) {
0569: if (cells != null) {
0570: List result = new ArrayList(cells.length);
0571: for (int i = 0; i < cells.length; i++)
0572: if (isVisible(cells[i]))
0573: result.add(cells[i]);
0574: return result.toArray();
0575: }
0576: return null;
0577: }
0578:
0579: /**
0580: * Returns the ports of the view.
0581: */
0582: public PortView[] getPorts() {
0583: return ports;
0584: }
0585:
0586: /**
0587: * Updates the cached array of ports.
0588: */
0589: protected void updatePorts() {
0590: Object[] roots = DefaultGraphModel.getRoots(graphModel);
0591: List list = DefaultGraphModel.getDescendants(graphModel, roots);
0592: if (list != null) {
0593: ArrayList result = new ArrayList();
0594: Iterator it = list.iterator();
0595: while (it.hasNext()) {
0596: Object cell = it.next();
0597: if (graphModel.isPort(cell)) {
0598: CellView portView = getMapping(cell, false);
0599: if (portView != null) {
0600: result.add(portView);
0601: portView.refresh(this , this , false);
0602: }
0603: }
0604: }
0605: ports = new PortView[result.size()];
0606: result.toArray(ports);
0607: }
0608: }
0609:
0610: public void refresh(CellView[] views, boolean create) {
0611: if (views != null)
0612: for (int i = 0; i < views.length; i++)
0613: refresh(views[i], create);
0614: }
0615:
0616: public void refresh(CellView view, boolean create) {
0617: if (view != null) {
0618: view.refresh(this , this , create);
0619: CellView[] children = view.getChildViews();
0620: for (int i = 0; i < children.length; i++)
0621: refresh(children[i], create);
0622: }
0623: }
0624:
0625: public void update(CellView[] views) {
0626: if (views != null)
0627: for (int i = 0; i < views.length; i++)
0628: update(views[i]);
0629: }
0630:
0631: public void update(CellView view) {
0632: if (view != null) {
0633: view.update(this );
0634: CellView[] children = view.getChildViews();
0635: for (int i = 0; i < children.length; i++)
0636: update(children[i]);
0637: }
0638: }
0639:
0640: //
0641: // Update View based on Model Change
0642: //
0643: /**
0644: * Called from BasicGraphUI.ModelHandler to update the view based on the
0645: * specified GraphModelEvent.
0646: */
0647: public void graphChanged(GraphModelEvent.GraphModelChange change) {
0648: // Get Old Attributes From GraphModelChange (Undo) -- used to remap
0649: // removed cells
0650: CellView[] views = change.getViews(this );
0651: if (views != null) {
0652: // Only ex-visible views are piggybacked
0653: for (int i = 0; i < views.length; i++)
0654: if (views[i] != null) {
0655: // Do not use putMapping because cells are invisible
0656: mapping.put(views[i].getCell(), views[i]);
0657: }
0658: // Ensure visible state
0659: setVisibleImpl(getCells(views), true);
0660: }
0661: // Fetch View Order Of Changed Cells (Before any changes)
0662: Object[] changed = change.getChanged();
0663: // Fetch Views to Insert before Removal (Special case: two step process,
0664: // see setModel)
0665: getMapping(change.getInserted(), true);
0666: // Remove and Hide Roots
0667: views = removeCells(change.getRemoved());
0668: // Store Removed Attributes In GraphModelChange (Undo)
0669: change.putViews(this , views);
0670: // Insert New Roots
0671: // insertViews(insertViews);
0672: // Hide edges with invisible source or target
0673: if (isPartial()) {
0674: // Then show
0675: showCellsForChange(change);
0676: // First hide
0677: hideCellsForChange(change);
0678: }
0679: // Refresh Changed Cells
0680: if (changed != null && changed.length > 0) {
0681: // Restore All Cells in Model Order (Replace Roots)
0682: for (int i = 0; i < changed.length; i++) {
0683: CellView view = getMapping(changed[i], false);
0684: if (view != null) {
0685: view.refresh(this , this , true);
0686: // Update child edges in groups (routing)
0687: update(view);
0688: }
0689: }
0690: }
0691: reloadRoots();
0692: // Refresh Context of Changed Cells (=Connected Edges)
0693: refresh(getMapping(getContext(change), false), false);
0694: updatePorts();
0695: }
0696:
0697: /**
0698: * Completely reloads all roots from the model in the order returned by
0699: * DefaultGraphModel.getAll. This uses the current visibleSet and mapping to
0700: * fetch the cell views for the cells.
0701: */
0702: protected void reloadRoots() {
0703: // Reorder roots
0704: Object[] orderedCells = DefaultGraphModel.getAll(graphModel);
0705: List newRoots = new ArrayList();
0706: for (int i = 0; i < orderedCells.length; i++) {
0707: CellView view = getMapping(orderedCells[i], false);
0708: if (view != null) {
0709: view.refresh(this , this , true);
0710: if (view.getParentView() == null) {
0711: newRoots.add(view);
0712: }
0713: }
0714: }
0715: roots = newRoots;
0716: }
0717:
0718: /**
0719: * Hook for subclassers to augment the context for a graphChange. This means
0720: * you can add additional cells that should be refreshed on a special change
0721: * event. eg. parallel edges when one is removed or added.
0722: */
0723: protected Object[] getContext(
0724: GraphModelEvent.GraphModelChange change) {
0725: return change.getContext();
0726: }
0727:
0728: protected void hideCellsForChange(
0729: GraphModelEvent.GraphModelChange change) {
0730: // Hide visible edges between invisible vertices
0731: // 1. Remove attached edges of removed cells
0732: // 2. Remove edges who's source or target has changed to
0733: // invisible.
0734: Object[] tmp = change.getRemoved();
0735: Set removed = new HashSet();
0736: if (tmp != null)
0737: for (int i = 0; i < tmp.length; i++)
0738: removed.add(tmp[i]);
0739: if (hidesDanglingConnections || hidesExistingConnections) {
0740: Object[] changed = change.getChanged();
0741: for (int i = 0; i < changed.length; i++) {
0742: CellView view = getMapping(changed[i], false);
0743: if (view instanceof EdgeView) {
0744: EdgeView edge = (EdgeView) view;
0745: Object oldSource = (edge.getSource() == null) ? null
0746: : edge.getSource().getCell();
0747: Object oldTarget = (edge.getTarget() == null) ? null
0748: : edge.getTarget().getCell();
0749: Object newSource = graphModel.getSource(changed[i]);
0750: Object newTarget = graphModel.getTarget(changed[i]);
0751: boolean hideExisting = (hidesExistingConnections && ((newSource != null && !hasVisibleParent(
0752: newSource, null)) || (newTarget != null && !hasVisibleParent(
0753: newTarget, null))));
0754: if ((hidesDanglingConnections && (removed
0755: .contains(oldSource) || removed
0756: .contains(oldTarget)))
0757: || hideExisting) {
0758: setVisibleImpl(new Object[] { changed[i] },
0759: false);
0760: }
0761: }
0762: }
0763: }
0764: }
0765:
0766: /**
0767: * Checks if the port or one of its parents is visible.
0768: */
0769: protected boolean hasVisibleParent(Object cell, Set invisible) {
0770: boolean isVisible = false;
0771: do {
0772: isVisible = (invisible == null || !invisible.contains(cell)) ? isVisible(cell)
0773: : false;
0774: cell = getModel().getParent(cell);
0775: } while (cell != null && !isVisible);
0776: return isVisible;
0777: }
0778:
0779: protected void showCellsForChange(
0780: GraphModelEvent.GraphModelChange change) {
0781: Object[] inserted = change.getInserted();
0782: if (inserted != null && showsInsertedConnections) {
0783: for (int i = 0; i < inserted.length; i++) {
0784: if (!isVisible(inserted[i])) {
0785: Object source = graphModel.getSource(inserted[i]);
0786: Object target = graphModel.getTarget(inserted[i]);
0787: if ((source != null || target != null)
0788: && (isVisible(source) && isVisible(target)))
0789: setVisibleImpl(new Object[] { inserted[i] },
0790: true);
0791: }
0792: }
0793: }
0794: if (change.getConnectionSet() != null) {
0795: Set changedSet = change.getConnectionSet()
0796: .getChangedEdges();
0797: if (changedSet != null && showsChangedConnections) {
0798: Object[] changed = changedSet.toArray();
0799: for (int i = 0; i < changed.length; i++) {
0800: if (!isVisible(changed[i])) {
0801: Object source = graphModel
0802: .getSource(changed[i]);
0803: Object target = graphModel
0804: .getTarget(changed[i]);
0805: if ((source != null || target != null)
0806: && (isVisible(source) && isVisible(target))
0807: && !isVisible(changed[i]))
0808: setVisibleImpl(new Object[] { changed[i] },
0809: true);
0810: }
0811: }
0812: }
0813: }
0814: }
0815:
0816: /**
0817: * Adds the specified model root cells to the view. Do not add a view that
0818: * is already in roots.
0819: */
0820: public void insertViews(CellView[] views) {
0821: if (views != null) {
0822: refresh(views, true);
0823: for (int i = 0; i < views.length; i++) {
0824: if (views[i] != null
0825: && getMapping(views[i].getCell(), false) != null) {
0826: CellView parentView = views[i].getParentView();
0827: Object parent = (parentView != null) ? parentView
0828: .getCell() : null;
0829: if (!graphModel.isPort(views[i].getCell())
0830: && parent == null) {
0831: roots.add(views[i]);
0832: }
0833: }
0834: }
0835: }
0836: }
0837:
0838: /**
0839: * Removes the specified model root cells from the view by removing the
0840: * mapping between the cell and its view and makes the cells invisible.
0841: */
0842: public CellView[] removeCells(Object[] cells) {
0843: if (cells != null && cells.length > 0) {
0844: CellView[] views = new CellView[cells.length];
0845: // Store views to be removed from roots in an intermediate
0846: // set for performance reasons
0847: Set removedRoots = null;
0848: for (int i = 0; i < cells.length; i++) {
0849: views[i] = removeMapping(cells[i]);
0850: if (views[i] != null) {
0851: views[i].removeFromParent();
0852: if (removedRoots == null) {
0853: removedRoots = new HashSet();
0854: }
0855: removedRoots.add(views[i]);
0856: visibleSet.remove(views[i].getCell());
0857: }
0858: }
0859: if (removedRoots != null && removedRoots.size() > 0) {
0860: // If any roots have been removed, reform the roots
0861: // lists appropriately, keeping the order the same
0862: int newRootsSize = roots.size() - removedRoots.size();
0863: if (newRootsSize < 8) {
0864: newRootsSize = 8;
0865: }
0866: List newRoots = new ArrayList(newRootsSize);
0867: Iterator iter = roots.iterator();
0868: while (iter.hasNext()) {
0869: Object cell = iter.next();
0870: if (!removedRoots.contains(cell)) {
0871: newRoots.add(cell);
0872: }
0873: }
0874: roots = newRoots;
0875: }
0876: return views;
0877: }
0878: return null;
0879: }
0880:
0881: //
0882: // Cell Mapping
0883: //
0884: /**
0885: * Takes an array of views and returns the array of the corresponding cells
0886: * by using <code>getCell</code> for each view.
0887: */
0888: public Object[] getCells(CellView[] views) {
0889: if (views != null) {
0890: Object[] cells = new Object[views.length];
0891: for (int i = 0; i < views.length; i++)
0892: if (views[i] != null)
0893: cells[i] = views[i].getCell();
0894: return cells;
0895: }
0896: return null;
0897: }
0898:
0899: /**
0900: * Returns the view for the specified cell. If create is true and no view is
0901: * found then a view is created using createView(Object).
0902: */
0903: public CellView getMapping(Object cell, boolean create) {
0904: if (cell == null)
0905: return null;
0906: CellView view = (CellView) mapping.get(cell);
0907: if (view == null && create && isVisible(cell)) {
0908: view = (CellView) hiddenMapping.get(cell);
0909: if (view != null) {
0910: putMapping(cell, view);
0911: hiddenMapping.remove(cell);
0912: } else {
0913: view = factory.createView(graphModel, cell);
0914: putMapping(cell, view);
0915: view.refresh(this , this , true); // Create Dependent
0916: // Views
0917: view.update(this );
0918: }
0919: }
0920: return view;
0921: }
0922:
0923: /**
0924: * Returns the views for the specified array of cells without creating these
0925: * views on the fly.
0926: */
0927: public CellView[] getMapping(Object[] cells) {
0928: return getMapping(cells, false);
0929: }
0930:
0931: /**
0932: * Returns the views for the specified array of cells. Returned array may
0933: * contain null pointers if the respective cell is not mapped in this view
0934: * and <code>create</code> is <code>false</code>.
0935: */
0936: public CellView[] getMapping(Object[] cells, boolean create) {
0937: if (cells != null) {
0938: CellView[] result = new CellView[cells.length];
0939: for (int i = 0; i < cells.length; i++)
0940: result[i] = getMapping(cells[i], create);
0941: return result;
0942: }
0943: return null;
0944: }
0945:
0946: /**
0947: * Associates the specified model cell with the specified view.
0948: */
0949: public void putMapping(Object cell, CellView view) {
0950: if (cell != null && view != null)
0951: mapping.put(cell, view);
0952: }
0953:
0954: /**
0955: * Removes the associaten for the specified model cell and returns the view
0956: * that was previously associated with the cell. Updates the portlist if
0957: * necessary.
0958: */
0959: public CellView removeMapping(Object cell) {
0960: if (cell != null) {
0961: CellView view = (CellView) mapping.remove(cell);
0962: return view;
0963: }
0964: return null;
0965: }
0966:
0967: //
0968: // Partial View
0969: //
0970: // Null is always visible!
0971: public boolean isVisible(Object cell) {
0972: return !isPartial() || visibleSet.contains(cell)
0973: || cell == null;
0974: }
0975:
0976: public Set getVisibleSet() {
0977: return new HashSet(visibleSet);
0978: }
0979:
0980: public void setVisibleSet(Set visible) {
0981: visibleSet = visible;
0982: }
0983:
0984: /**
0985: * Makes the specified cell visible or invisible depending on the flag
0986: * passed in. Note the cell really is a cell, not a cell view.
0987: *
0988: * @param cell
0989: * the cell whose visibility is to be changed
0990: * @param visible
0991: * <code>true</code> if cell is to be made visible
0992: */
0993: public void setVisible(Object cell, boolean visible) {
0994: setVisible(new Object[] { cell }, visible);
0995: }
0996:
0997: /**
0998: * Makes the specified cells visible or invisible depending on the flag
0999: * passed in. Note the cells really are cells, not cell views.
1000: *
1001: * @param cells
1002: * the cells whose visibility is to be changed
1003: * @param visible
1004: * <code>true</code> if the cells are to be made visible
1005: */
1006: public void setVisible(Object[] cells, boolean visible) {
1007: if (visible)
1008: setVisible(cells, null);
1009: else
1010: setVisible(null, cells);
1011: }
1012:
1013: /**
1014: * Changes the visibility state of the cells passed in. Note that the arrays
1015: * must contain cells, not cell views.
1016: *
1017: * @param visible
1018: * cells to be made visible
1019: * @param invisible
1020: * cells to be made invisible
1021: */
1022: public void setVisible(Object[] visible, Object[] invisible) {
1023: setVisible(visible, invisible, null);
1024: }
1025:
1026: /**
1027: * Changes the visibility state of the cells passed in. Note that the arrays
1028: * must contain cells, not cell views.
1029: *
1030: * @param visible
1031: * cells to be made visible
1032: * @param invisible
1033: * cells to be made invisible
1034: * @param cs
1035: * a <code>ConnectionSet</code> describing the new state of
1036: * edge connections in the graph
1037: */
1038: public void setVisible(Object[] visible, Object[] invisible,
1039: ConnectionSet cs) {
1040: GraphLayoutCacheEdit edit = new GraphLayoutCacheEdit(null,
1041: null, visible, invisible);
1042: edit.end();
1043: graphModel.edit(null, cs, null, new UndoableEdit[] { edit });
1044: }
1045:
1046: // This is used to augment the array passed to the setVisible method.
1047: protected Object[] addVisibleDependencies(Object[] cells,
1048: boolean visible) {
1049: if (cells != null) {
1050: if (visible) {
1051: // Make ports and source and target vertex visible
1052: Set all = new HashSet();
1053: for (int i = 0; i < cells.length; i++) {
1054: all.add(cells[i]);
1055: // Add ports
1056: all.addAll(getPorts(cells[i]));
1057: // Add source vertex and ports
1058: Collection coll = getParentPorts(graphModel
1059: .getSource(cells[i]));
1060: if (coll != null)
1061: all.addAll(coll);
1062: // Add target vertex and ports
1063: coll = getParentPorts(graphModel
1064: .getTarget(cells[i]));
1065: if (coll != null)
1066: all.addAll(coll);
1067: }
1068: if (showsExistingConnections) {
1069: Set tmp = DefaultGraphModel.getEdges(getModel(),
1070: cells);
1071: Iterator it = tmp.iterator();
1072: while (it.hasNext()) {
1073: Object obj = it.next();
1074: Object source = graphModel.getSource(obj);
1075: Object target = graphModel.getTarget(obj);
1076: if ((isVisible(source) || all.contains(source))
1077: && (isVisible(target) || all
1078: .contains(target)))
1079: all.add(obj);
1080: }
1081: }
1082: all.removeAll(visibleSet);
1083: all.remove(null);
1084: return all.toArray();
1085: } else {
1086: if (hidesExistingConnections) {
1087: Set all = new HashSet();
1088: for (int i = 0; i < cells.length; i++) {
1089: all.addAll(getPorts(cells[i]));
1090: all.add(cells[i]);
1091: }
1092: Iterator it = DefaultGraphModel.getEdges(
1093: graphModel, cells).iterator();
1094: while (it.hasNext()) {
1095: Object edge = it.next();
1096: Object newSource = graphModel.getSource(edge);
1097: Object newTarget = graphModel.getTarget(edge);
1098: // Note: At this time the cells are not yet hidden
1099: if ((newSource != null && !hasVisibleParent(
1100: newSource, all))
1101: || (newTarget != null && !hasVisibleParent(
1102: newTarget, all))) {
1103: all.add(edge);
1104: }
1105: }
1106: all.remove(null);
1107: return all.toArray();
1108: }
1109: }
1110: }
1111: return cells;
1112: }
1113:
1114: /**
1115: * The actual implementation of changing cells' visibility state. This
1116: * method does not deal with creating the undo or updating the
1117: * GraphLayoutCache correctly. The <code>setVisible</code> methods in this
1118: * class are intended to be the main public way to change visiblilty.
1119: * However, if you do not require the undo to be formed, this method is much
1120: * quicker, just note that you must call <code>updatePorts</code> if this
1121: * method returns true.
1122: *
1123: * @param cells
1124: * @param visible
1125: * @return whether or not the ports needed updating in the calling method
1126: */
1127: public boolean setVisibleImpl(Object[] cells, boolean visible) {
1128: cells = addVisibleDependencies(cells, visible);
1129: if (cells != null && isPartial()) {
1130: boolean updatePorts = false;
1131: // Update Visible Set
1132: CellView[] views = new CellView[cells.length];
1133: if (!visible) {
1134: views = removeCells(cells);
1135: }
1136: // Set used for model roots contains call for performance
1137: Set modelRoots = null;
1138: for (int i = 0; i < cells.length; i++) {
1139: if (cells[i] != null) {
1140: if (visible) {
1141: visibleSet.add(cells[i]);
1142: views[i] = getMapping(cells[i], true);
1143: } else {
1144: if (views[i] != null) {
1145: if (modelRoots == null) {
1146: modelRoots = new HashSet(
1147: DefaultGraphModel
1148: .getRootsAsCollection(getModel()));
1149: }
1150: if (modelRoots.contains(views[i].getCell())
1151: && remembersCellViews) {
1152: hiddenMapping.put(views[i].getCell(),
1153: views[i]);
1154: }
1155: updatePorts = true;
1156: }
1157: }
1158: }
1159: }
1160: // Make Cell Views Visible (if not already in place)
1161: if (visible) {
1162: Set parentSet = new HashSet();
1163: Set removedRoots = null;
1164: for (int i = 0; i < views.length; i++) {
1165: if (views[i] != null) {
1166: CellView view = views[i];
1167: // Remove all children from roots
1168: CellView[] children = AbstractCellView
1169: .getDescendantViews(new CellView[] { view });
1170: for (int j = 0; j < children.length; j++) {
1171: if (removedRoots == null) {
1172: removedRoots = new HashSet();
1173: }
1174: removedRoots.add(children[j]);
1175: }
1176: view.refresh(this , this , false);
1177: // Link cellView into graphLayoutCache
1178: CellView parentView = view.getParentView();
1179: if (parentView != null)
1180: parentSet.add(parentView);
1181: updatePorts = true;
1182: }
1183: }
1184: if (removedRoots != null && removedRoots.size() > 0) {
1185: // If any roots have been removed, reform the roots
1186: // lists appropriately, keeping the order the same
1187: List newRoots = new ArrayList();
1188: Iterator iter = roots.iterator();
1189: while (iter.hasNext()) {
1190: Object cell = iter.next();
1191: if (!removedRoots.contains(cell)) {
1192: newRoots.add(cell);
1193: }
1194: }
1195: roots = newRoots;
1196: }
1197:
1198: CellView[] parentViews = new CellView[parentSet.size()];
1199: parentSet.toArray(parentViews);
1200: refresh(parentViews, true);
1201: }
1202: return updatePorts;
1203: }
1204: return false;
1205: }
1206:
1207: protected Collection getParentPorts(Object cell) {
1208: // does nothing if a parent is already visible
1209: Object parent = graphModel.getParent(cell);
1210: while (parent != null) {
1211: if (isVisible(parent))
1212: return null;
1213: parent = graphModel.getParent(parent);
1214: }
1215:
1216: // Else returns the parent and all ports
1217: parent = graphModel.getParent(cell);
1218: Collection collection = getPorts(parent);
1219: collection.add(parent);
1220: return collection;
1221: }
1222:
1223: protected Collection getPorts(Object cell) {
1224: LinkedList list = new LinkedList();
1225: for (int i = 0; i < graphModel.getChildCount(cell); i++) {
1226: Object child = graphModel.getChild(cell, i);
1227: if (graphModel.isPort(child))
1228: list.add(child);
1229: }
1230: return list;
1231: }
1232:
1233: //
1234: // Change Support
1235: //
1236: public boolean isPartial() {
1237: return partial;
1238: }
1239:
1240: /**
1241: * Required for XML persistence
1242: *
1243: * @return whether or not the cache is partial
1244: */
1245: public boolean getPartial() {
1246: return isPartial();
1247: }
1248:
1249: /**
1250: * Messaged when the user has altered the value for the item identified by
1251: * cell to newValue. If newValue signifies a truly new value the model
1252: * should post a graphCellsChanged event. This calls
1253: * augmentNestedMapForValueChange.
1254: */
1255: public void valueForCellChanged(Object cell, Object newValue) {
1256: Map nested = null;
1257: if (isAutoSizeOnValueChange()) {
1258: CellView view = getMapping(cell, false);
1259: if (view != null) {
1260: AttributeMap attrs = view.getAllAttributes();
1261: Rectangle2D bounds = GraphConstants.getBounds(attrs);
1262: Rectangle2D dummyBounds = null;
1263: // Force the model to store the old bounds
1264: if (bounds != null) {
1265: dummyBounds = attrs.createRect(bounds.getX(),
1266: bounds.getY(), 0, 0);
1267: } else {
1268: dummyBounds = attrs.createRect(0, 0, 0, 0);
1269: }
1270: nested = GraphConstants.createAttributes(
1271: new Object[] { cell }, new Object[] {
1272: GraphConstants.RESIZE,
1273: GraphConstants.BOUNDS }, new Object[] {
1274: Boolean.TRUE, dummyBounds });
1275: }
1276: } else {
1277: nested = new Hashtable();
1278: nested.put(cell, new Hashtable());
1279: }
1280: augmentNestedMapForValueChange(nested, cell, newValue);
1281: edit(nested, null, null, null);
1282: }
1283:
1284: /**
1285: * Hook for subclassers to add more stuff for value changes. Currently this
1286: * adds the new value to the change.
1287: */
1288: protected void augmentNestedMapForValueChange(Map nested,
1289: Object cell, Object newValue) {
1290: Map attrs = (Map) nested.get(cell);
1291: if (attrs != null)
1292: GraphConstants.setValue(attrs, newValue);
1293: }
1294:
1295: /**
1296: * Inserts the <code>cells</code> and connections into the model, and
1297: * absorbs the local attributes. This implementation sets the inserted cells
1298: * visible and selects the new roots depending on graph.selectNewCells.
1299: */
1300: public void insert(Object[] roots, Map attributes,
1301: ConnectionSet cs, ParentMap pm, UndoableEdit[] e) {
1302: Object[] visible = null;
1303: if (isPartial() && showsInsertedCells) {
1304: List tmp = DefaultGraphModel.getDescendants(graphModel,
1305: roots);
1306: tmp.removeAll(visibleSet);
1307: if (!tmp.isEmpty())
1308: visible = tmp.toArray();
1309: }
1310: // Absorb local attributes
1311: GraphLayoutCacheEdit edit = createLocalEdit(roots, attributes,
1312: visible, null);
1313: if (edit != null)
1314: e = augment(e, edit);
1315: graphModel.insert(roots, attributes, cs, pm, e);
1316: }
1317:
1318: /**
1319: * Inserts the cloned cells from the clone map and clones the passed-in
1320: * arguments according to the clone map before insertion and returns the
1321: * clones in order of the cells. This example shows how to clone the current
1322: * selection and get a reference to the clones:
1323: *
1324: * <pre>
1325: * Object[] cells = graph.getDescendants(graph.order(graph.getSelectionCells()));
1326: * ConnectionSet cs = ConnectionSet.create(graphModel, cells, false);
1327: * ParentMap pm = ParentMap.create(graphModel, cells, false, true);
1328: * cells = graphLayoutCache.insertClones(cells, graph.cloneCells(cells),
1329: * attributes, cs, pm, 0, 0);
1330: * </pre>
1331: */
1332: public Object[] insertClones(Object[] cells, Map clones,
1333: Map nested, ConnectionSet cs, ParentMap pm, double dx,
1334: double dy) {
1335: if (cells != null) {
1336: if (cs != null)
1337: cs = cs.clone(clones);
1338: if (pm != null)
1339: pm = pm.clone(clones);
1340: if (nested != null) {
1341: nested = GraphConstants.replaceKeys(clones, nested);
1342: AttributeMap.translate(nested.values(), dx, dy);
1343: }
1344: // Replace cells in order
1345: Object[] newCells = new Object[cells.length];
1346: for (int i = 0; i < cells.length; i++)
1347: newCells[i] = clones.get(cells[i]);
1348: // Insert into cache/model
1349: insert(newCells, nested, cs, pm, null);
1350: return newCells;
1351: }
1352: return null;
1353: }
1354:
1355: /**
1356: * Inserts the specified vertex into the graph model. This method does in
1357: * fact nothing, it calls insert edge with the vertex and the source and
1358: * target port set to null. This example shows how to add a vertex with a
1359: * port and a black border:
1360: *
1361: * <pre>
1362: * DefaultGraphCell vertex = new DefaultGraphCell("Hello, world!");
1363: * Map attrs = vertex.getAttributes();
1364: * GraphConstants.setOpaque(attrs, false);
1365: * GraphConstants.setBorderColor(attrs, Color.black);
1366: * DefaultPort port = new DefaultPort();
1367: * vertex.add(port);
1368: * port.setParent(vertex);
1369: * graph.getGraphLayoutCache().insert(vertex);
1370: * </pre>
1371: *
1372: * @param cell
1373: * inserts the specified cell in the cache
1374: */
1375: public void insert(Object cell) {
1376: insert(new Object[] { cell });
1377: }
1378:
1379: /**
1380: * Inserts the specified edge into the graph model. This method does in fact
1381: * nothing, it calls insert with a default connection set.
1382: *
1383: * @param edge
1384: * the edge to be inserted
1385: * @param source
1386: * the source port this edge is connected to
1387: * @param target
1388: * the target port this edge is connected to
1389: */
1390: public void insertEdge(Object edge, Object source, Object target) {
1391: insert(new Object[] { edge }, new Hashtable(),
1392: new ConnectionSet(edge, source, target),
1393: new ParentMap());
1394: }
1395:
1396: /**
1397: * Inserts the specified cells into the graph model. This method is a
1398: * general implementation of cell insertion. If the source and target port
1399: * are null, then no connection set is created. The method uses the
1400: * attributes from the specified edge and the egdge's children to construct
1401: * the insert call. This example shows how to insert an edge with a special
1402: * arrow between two known vertices:
1403: *
1404: * <pre>
1405: * Object source = graph.getDefaultPortForCell(sourceVertex).getCell();
1406: * Object target = graph.getDefaultPortForCell(targetVertex).getCell();
1407: * DefaultEdge edge = new DefaultEdge("Hello, world!");
1408: * edge.setSource(source);
1409: * edge.setTarget(target);
1410: * Map attrs = edge.getAttributes();
1411: * GraphConstants.setLineEnd(attrs, GraphConstants.ARROW_TECHNICAL);
1412: * graph.getGraphLayoutCache().insert(edge);
1413: * </pre>
1414: */
1415: public void insert(Object[] cells) {
1416: insert(cells, new Hashtable(), new ConnectionSet(),
1417: new ParentMap());
1418: }
1419:
1420: /**
1421: * Variant of the insert method that allows to pass a default connection set
1422: * and parent map and nested map.
1423: */
1424: public void insert(Object[] cells, Map nested, ConnectionSet cs,
1425: ParentMap pm) {
1426: if (cells != null) {
1427: if (nested == null)
1428: nested = new Hashtable();
1429: if (cs == null)
1430: cs = new ConnectionSet();
1431: if (pm == null)
1432: pm = new ParentMap();
1433: for (int i = 0; i < cells.length; i++) {
1434: // Using the children of the vertex we construct the parent map.
1435: int childCount = getModel().getChildCount(cells[i]);
1436: for (int j = 0; j < childCount; j++) {
1437: Object child = getModel().getChild(cells[i], j);
1438: pm.addEntry(child, cells[i]);
1439:
1440: // And add their attributes to the nested map
1441: AttributeMap attrs = getModel()
1442: .getAttributes(child);
1443: if (attrs != null)
1444: nested.put(child, attrs);
1445: }
1446:
1447: // A nested map with the vertex as key
1448: // and its attributes as the value
1449: // is required for the model.
1450: Map attrsTmp = (Map) nested.get(cells[i]);
1451: Map attrs = getModel().getAttributes(cells[i]);
1452: if (attrsTmp != null)
1453: attrs.putAll(attrsTmp);
1454: nested.put(cells[i], attrs);
1455:
1456: // Check if we have parameters for a connection set.
1457: Object sourcePort = getModel().getSource(cells[i]);
1458: if (sourcePort != null)
1459: cs.connect(cells[i], sourcePort, true);
1460:
1461: Object targetPort = getModel().getTarget(cells[i]);
1462: if (targetPort != null)
1463: cs.connect(cells[i], targetPort, false);
1464: }
1465: // Create an array with the parent and its children.
1466: cells = DefaultGraphModel.getDescendants(getModel(), cells)
1467: .toArray();
1468:
1469: // Finally call the insert method on the parent class.
1470: insert(cells, nested, cs, pm, null);
1471: }
1472: }
1473:
1474: /**
1475: * Inserts the specified cell as a parent of children. Note: All cells that
1476: * are not yet in the model will be inserted. This example shows how to
1477: * group the current selection and pass the group default bounds in case it
1478: * is later collapsed:
1479: *
1480: * <pre>
1481: * DefaultGraphCell group = new DefaultGraphCell("Hello, world!");
1482: * Object[] cells = DefaultGraphModel.order(graph.getModel(), graph
1483: * .getSelectionCells());
1484: * Rectangle2D bounds = graph.getCellBounds(cells);
1485: * if (bounds != null) {
1486: * bounds = new Rectangle2D.Double(bounds.getX() + bounds.getWidth() / 4,
1487: * bounds.getY() + bounds.getHeight() / 4, bounds.getWidth() / 2,
1488: * bounds.getHeight() / 2);
1489: * GraphConstants.setBounds(group.getAttributes(), bounds);
1490: * }
1491: * graph.getGraphLayoutCache().insertGroup(group, cells);
1492: * </pre>
1493: */
1494: public void insertGroup(Object group, Object[] children) {
1495: if (group != null && children != null && children.length > 0) {
1496: Map nested = new Hashtable();
1497:
1498: // List to store all children that are not in the model
1499: List newCells = new ArrayList(children.length + 1);
1500:
1501: // Plus the group cell at pos 0
1502: if (!getModel().contains(group)) {
1503: newCells.add(group);
1504: }
1505:
1506: // Create a parent map for the group and the children, and
1507: // store the children's attributes in the nested map.
1508: // Note: This implementation assumes that the children have
1509: // not yet been added to the group object. Therefore,
1510: // the insert method will only collect the group
1511: // attributes, but will ignore the child attributes.
1512: ParentMap pm = new ParentMap();
1513: for (int i = 0; i < children.length; i++) {
1514: pm.addEntry(children[i], group);
1515: if (!getModel().contains(children[i])) {
1516: newCells.add(children[i]);
1517: AttributeMap attrs = getModel().getAttributes(
1518: children[i]);
1519: if (attrs != null)
1520: nested.put(children[i], attrs);
1521: }
1522: }
1523: if (newCells.isEmpty())
1524: edit(nested, null, pm, null);
1525: else
1526: insert(newCells.toArray(), nested, null, pm);
1527: }
1528: }
1529:
1530: /**
1531: * Removes <code>cells</code> from the model.
1532: */
1533: public void remove(Object[] cells) {
1534: graphModel.remove(cells);
1535: }
1536:
1537: /**
1538: * Removes cells from the model, including all children and connected edges
1539: * if <code>children</code> or <code>edges</code> is true, respectively.
1540: *
1541: * @param cells
1542: * The cells to remove.
1543: * @param descendants
1544: * Whether to remove all descendants as well.
1545: * @param edges
1546: * Whether to remove all connected edges as well.
1547: */
1548: public void remove(Object[] cells, boolean descendants,
1549: boolean edges) {
1550: if (cells != null && cells.length > 0) {
1551: if (edges) {
1552: Object[] tmp = DefaultGraphModel.getEdges(getModel(),
1553: cells).toArray();
1554: Object[] newCells = new Object[cells.length
1555: + tmp.length];
1556: System.arraycopy(cells, 0, newCells, 0, cells.length);
1557: System.arraycopy(tmp, 0, newCells, cells.length,
1558: tmp.length);
1559: cells = newCells;
1560: }
1561: if (descendants)
1562: cells = DefaultGraphModel.getDescendants(getModel(),
1563: cells).toArray();
1564: remove(cells);
1565: }
1566: }
1567:
1568: /**
1569: * Hides the specified cells with all children if <code>descandants</code>
1570: * is true.
1571: */
1572: public void hideCells(Object[] cells, boolean descandants) {
1573: if (cells != null && cells.length > 0) {
1574: if (descandants)
1575: cells = DefaultGraphModel.getDescendants(getModel(),
1576: cells).toArray();
1577: setVisible(cells, false);
1578: }
1579: }
1580:
1581: /**
1582: * Shows the specified cells with all children if <code>descandants</code>
1583: * is true.
1584: */
1585: public void showCells(Object[] cells, boolean descandants) {
1586: if (cells != null && cells.length > 0) {
1587: if (descandants)
1588: cells = DefaultGraphModel.getDescendants(getModel(),
1589: cells).toArray();
1590: setVisible(cells, true);
1591: }
1592: }
1593:
1594: /**
1595: * Ungroups all groups in cells and returns the children that are not ports.
1596: * Note: This replaces the parents with their group cells in the group
1597: * structure.
1598: */
1599: public Object[] ungroup(Object[] cells) {
1600: if (cells != null && cells.length > 0) {
1601: ArrayList toRemove = new ArrayList();
1602: ArrayList children = new ArrayList();
1603: boolean groupExists = false;
1604: for (int i = 0; i < cells.length; i++) {
1605: boolean childExists = false;
1606: ArrayList tempPortList = new ArrayList();
1607: for (int j = 0; j < getModel().getChildCount(cells[i]); j++) {
1608: Object child = getModel().getChild(cells[i], j);
1609: if (!getModel().isPort(child)) {
1610: children.add(child);
1611: childExists = true;
1612: } else {
1613: tempPortList.add(child);
1614: }
1615: }
1616: if (childExists) {
1617: toRemove.addAll(tempPortList);
1618: toRemove.add(cells[i]);
1619: groupExists = true;
1620: }
1621: }
1622: if (groupExists)
1623: remove(toRemove.toArray());
1624: return children.toArray();
1625: }
1626: return null;
1627: }
1628:
1629: /**
1630: * Toggles the collapsed state of the specified cells.
1631: *
1632: * @param cells
1633: * The cells to toggle the collapsed state for.
1634: * @param collapseOnly
1635: * Whether cells should only be collapsed.
1636: * @param expandOnly
1637: * Whether cells should only be expanded.
1638: *
1639: */
1640: public void toggleCollapsedState(Object[] cells,
1641: boolean collapseOnly, boolean expandOnly) {
1642: List toExpand = new ArrayList();
1643: List toCollapse = new ArrayList();
1644: for (int i = 0; i < cells.length; i++) {
1645: Object cell = cells[i];
1646: CellView view = getMapping(cell, false);
1647: if (view != null) {
1648:
1649: // Adds to list of expansion cells if it is a leaf in the layout
1650: // cache and we do not only want to collapse.
1651: if (view.isLeaf() && !collapseOnly)
1652: toExpand.add(view.getCell());
1653:
1654: // Else adds to list of to-be-collapsed cells if it is not a
1655: // leaf in the layout cache we do not only want to expand.
1656: else if (!view.isLeaf() && !expandOnly)
1657: toCollapse.add(view.getCell());
1658: }
1659: }
1660: if (!toCollapse.isEmpty() || !toExpand.isEmpty())
1661: setCollapsedState(toCollapse.toArray(), toExpand.toArray());
1662: }
1663:
1664: /**
1665: * Collapses all groups by hiding all their descendants.
1666: *
1667: * @param groups
1668: */
1669: public void collapse(Object[] groups) {
1670: setCollapsedState(groups, null);
1671: }
1672:
1673: /**
1674: * Expands all groups by showing all children. (Note: This does not show all
1675: * descandants, but only the first generation of children.)
1676: */
1677: public void expand(Object[] cells) {
1678: setCollapsedState(null, cells);
1679: }
1680:
1681: /**
1682: * Collapses and/or expands the specified cell(s)
1683: *
1684: * @param collapse
1685: * the cells to be collapsed
1686: * @param expand
1687: * the cells to be expanded
1688: */
1689: public void setCollapsedState(Object[] collapse, Object[] expand) {
1690: // Get all descandants for the groups
1691: ConnectionSet cs = new ConnectionSet();
1692:
1693: // Collapse cells
1694: List toHide = DefaultGraphModel.getDescendants(getModel(),
1695: collapse);
1696: if (collapse != null) {
1697: // Remove the groups themselfes
1698: for (int i = 0; i < collapse.length; i++) {
1699: toHide.remove(collapse[i]);
1700: cellWillCollapse(collapse[i]);
1701: }
1702: // Remove the ports (will be hidden automatically)
1703: for (int i = 0; i < collapse.length; i++) {
1704: int childCount = getModel().getChildCount(collapse[i]);
1705: if (childCount > 0) {
1706: for (int j = 0; j < childCount; j++) {
1707: Object child = getModel().getChild(collapse[i],
1708: j);
1709: if (getModel().isPort(child)) {
1710: toHide.remove(child);
1711: }
1712: }
1713: }
1714: }
1715: }
1716:
1717: // Expand cells
1718: Set toShow = new HashSet();
1719: if (expand != null) {
1720: for (int i = 0; i < expand.length; i++) {
1721: int childCount = getModel().getChildCount(expand[i]);
1722: for (int j = 0; j < childCount; j++) {
1723: toShow.add(getModel().getChild(expand[i], j));
1724: }
1725: }
1726: }
1727: setVisible(toShow.toArray(), (toHide != null) ? toHide
1728: .toArray() : null, cs);
1729: }
1730:
1731: /**
1732: * Hook for subclassers to return the first or last visible port to replace
1733: * the current source or target port of the edge. This is called when groups
1734: * are collapsed for the edges that cross the group, ie. go from a child
1735: * cell to a cell which is outside the group. This implementation returns
1736: * the first port of the parent group if source is true, otherwise it
1737: * returns the last port of the parent group.
1738: */
1739: protected Object getParentPort(Object edge, boolean source) {
1740: // Contains the parent of the parent vertex, eg. the group
1741: Object parent = getModel().getParent(
1742: (source) ? DefaultGraphModel.getSourceVertex(
1743: getModel(), edge) : DefaultGraphModel
1744: .getTargetVertex(getModel(), edge));
1745: // Finds a port in the group
1746: int c = getModel().getChildCount(parent);
1747: for (int i = (source) ? c - 1 : 0; i < getModel()
1748: .getChildCount(parent)
1749: && i >= 0; i += (source) ? -1 : +1) {
1750: Object child = getModel().getChild(parent, i);
1751: if (getModel().isPort(child)) {
1752: return child;
1753: }
1754: }
1755: return null;
1756: }
1757:
1758: /**
1759: * Hook for subclassers to return the port to be used for edges that have
1760: * been connected to the group. This is called from expand. This returns the
1761: * first port of the first or last vertex depending on <code>source</code>.
1762: */
1763: protected Object getChildPort(Object edge, boolean source) {
1764: GraphModel model = getModel();
1765: // Contains the parent of the port, eg. the group
1766: Object parent = (source) ? DefaultGraphModel.getSourceVertex(
1767: model, edge) : DefaultGraphModel.getTargetVertex(model,
1768: edge);
1769: // Finds a vertex in the group
1770: int c = model.getChildCount(parent);
1771: for (int i = (source) ? c - 1 : 0; i < c && i >= 0; i += (source) ? -1
1772: : +1) {
1773: Object child = model.getChild(parent, i);
1774: if (!model.isEdge(child) && !model.isPort(child)) {
1775: // Finds a port in the vertex
1776: for (int j = 0; j < model.getChildCount(child); j++) {
1777: Object port = model.getChild(child, j);
1778: if (model.isPort(port)) {
1779: return port;
1780: }
1781: }
1782: }
1783: }
1784: return null;
1785: }
1786:
1787: /**
1788: * Applies the <code>propertyMap</code> and the connection changes to the
1789: * model. The initial <code>edits</code> that triggered the call are
1790: * considered to be part of this transaction. Notifies the model- and undo
1791: * listeners of the change. Note: The passed in attributes may contain
1792: * PortViews.
1793: */
1794: public void edit(Map attributes, ConnectionSet cs, ParentMap pm,
1795: UndoableEdit[] e) {
1796: if (attributes != null || cs != null || pm != null || e != null) {
1797: Object[] visible = null;
1798: if (isPartial() && showsInvisibleEditedCells) {
1799: Set tmp = new HashSet();
1800: if (attributes != null)
1801: tmp.addAll(attributes.keySet());
1802: if (cs != null)
1803: tmp.addAll(cs.getChangedEdges());
1804: if (pm != null)
1805: tmp.addAll(pm.getChangedNodes());
1806: tmp.removeAll(visibleSet);
1807: if (!tmp.isEmpty())
1808: visible = tmp.toArray();
1809: }
1810: GraphLayoutCacheEdit edit = createLocalEdit(null,
1811: attributes, visible, null);
1812: if (edit != null)
1813: e = augment(e, edit);
1814: // Pass to model
1815: graphModel.edit(attributes, cs, pm, e);
1816: }
1817: }
1818:
1819: /**
1820: * A shortcut method that takes a nested map and passes it to the edit
1821: * method.
1822: */
1823: public void edit(Map attributes) {
1824: edit(attributes, null, null, null);
1825: }
1826:
1827: /**
1828: * Applies the <code>attributes</code> to all <code>cells</code> by
1829: * creating a map that contains the attributes for each cell and passing it
1830: * to edit on this layout cache. Example:
1831: *
1832: * <pre>
1833: * Map attrs = new java.util.Hashtable();
1834: * GraphConstants.setBackground(attrs, Color.RED);
1835: * graph.getGraphLayoutCache().edit(graph.getSelectionCells(), attrs);
1836: * </pre>
1837: */
1838: public void edit(Object[] cells, Map attributes) {
1839: if (attributes != null && cells != null && cells.length > 0) {
1840: Map nested = new Hashtable();
1841: for (int i = 0; i < cells.length; i++)
1842: nested.put(cells[i], attributes);
1843: edit(nested, null, null, null);
1844: }
1845: }
1846:
1847: /**
1848: * Applies the <code>attributes</code> to a single <code>cell</code> by
1849: * creating a map that contains the attributes for this cell and passing it
1850: * to edit on this layout cache. Example:
1851: *
1852: * <pre>
1853: * Map attrs = new java.util.Hashtable();
1854: * GraphConstants.setBackground(attrs, Color.RED);
1855: * graph.getGraphLayoutCache().editCell(graph.getSelectionCell(), attrs);
1856: * </pre>
1857: */
1858: public void editCell(Object cell, Map attributes) {
1859: if (attributes != null && cell != null) {
1860: edit(new Object[] { cell }, attributes);
1861: }
1862: }
1863:
1864: protected UndoableEdit[] augment(UndoableEdit[] e, UndoableEdit edit) {
1865: if (edit != null) {
1866: int size = (e != null) ? e.length + 1 : 1;
1867: UndoableEdit[] result = new UndoableEdit[size];
1868: if (e != null)
1869: System.arraycopy(e, 0, result, 0, size - 2);
1870: result[size - 1] = edit;
1871: return result;
1872: }
1873: return e;
1874: }
1875:
1876: /**
1877: * Sends <code>cells</code> to back. Note: This expects an array of cells!
1878: */
1879: public void toBack(Object[] cells) {
1880: if (cells != null && cells.length > 0) {
1881: graphModel.toBack(cells);
1882: }
1883: }
1884:
1885: /**
1886: * Brings <code>cells</code> to front. Note: This expects an array of
1887: * cells!
1888: */
1889: public void toFront(Object[] cells) {
1890: if (cells != null && cells.length > 0) {
1891: graphModel.toFront(cells);
1892: }
1893: }
1894:
1895: /**
1896: * Creates a local edit for the specified change. A local operation contains
1897: * all visibility changes, as well as all changes to attributes that are
1898: * local, and all control attributes. <br>
1899: * Note: You must use cells as keys for the nested map, not cell views.
1900: */
1901: protected GraphLayoutCacheEdit createLocalEdit(Object[] inserted,
1902: Map nested, Object[] visible, Object[] invisible) {
1903: // Create an edit if there are any view-local attributes set
1904: if ((nested != null && !nested.isEmpty())
1905: && (!localAttributes.isEmpty() || isAllAttributesLocal())) {
1906: // Move or Copy Local Attributes to Local View
1907: Map globalMap = new Hashtable();
1908: Map localMap = new Hashtable();
1909: Map localAttr;
1910: Iterator it = nested.entrySet().iterator();
1911: while (it.hasNext()) {
1912: localAttr = new Hashtable();
1913: Map.Entry entry = (Map.Entry) it.next();
1914: // (cell, Hashtable)
1915: Object cell = entry.getKey();
1916: Map attr = (Map) entry.getValue();
1917: // Create Difference of Existing and New Attributes
1918: CellView tmpView = getMapping(cell, false);
1919: if (tmpView != null)
1920: attr = tmpView.getAllAttributes().diff(attr);
1921: // End of Diff
1922: Iterator it2 = attr.entrySet().iterator();
1923: while (it2.hasNext()) {
1924: Map.Entry entry2 = (Map.Entry) it2.next();
1925: // (key, value)
1926: Object key = entry2.getKey();
1927: Object value = entry2.getValue();
1928: boolean isControlAttribute = isControlAttribute(
1929: cell, key, value);
1930: if (isAllAttributesLocal() || isControlAttribute
1931: || isLocalAttribute(cell, key, value)) {
1932: localAttr.put(key, value);
1933: if (!isControlAttribute)
1934: it2.remove();
1935: }
1936: }
1937: if (!localAttr.isEmpty())
1938: localMap.put(cell, localAttr);
1939: if (!attr.isEmpty())
1940: globalMap.put(cell, attr);
1941: }
1942: nested.clear();
1943: nested.putAll(globalMap);
1944: if (visible != null || invisible != null
1945: || !localMap.isEmpty()) {
1946: GraphLayoutCacheEdit edit = new GraphLayoutCacheEdit(
1947: inserted, new Hashtable(localMap), visible,
1948: invisible);
1949: edit.end();
1950: return edit;
1951: }
1952: } else if (visible != null || invisible != null) {
1953: GraphLayoutCacheEdit edit = new GraphLayoutCacheEdit(
1954: inserted, null, visible, invisible);
1955: edit.end();
1956: return edit;
1957: }
1958: return null;
1959: }
1960:
1961: /**
1962: * Returns true if the set of local attributes contains <code>key</code>
1963: */
1964: protected boolean isLocalAttribute(Object cell, Object key,
1965: Object value) {
1966: return localAttributes.contains(key);
1967: }
1968:
1969: /**
1970: * Returns true if <code>key</code> is a control attribute
1971: */
1972: protected boolean isControlAttribute(Object cell, Object key,
1973: Object value) {
1974: return GraphConstants.REMOVEALL.equals(key)
1975: || GraphConstants.REMOVEATTRIBUTES.equals(key);
1976: }
1977:
1978: /**
1979: * Handles the removal of view local attributes. Since these attributes are
1980: * only being stored in the view, the option is provided to copy the values
1981: * for that key into the model. Without this, those values are lost.
1982: *
1983: * @param key
1984: * the key of the view local attribute
1985: * @param addToModel
1986: * whether or not to move the attribute values to the graph model
1987: * @param override
1988: * whether or not to override the key's value in the model cell's
1989: * attribute map if it exists
1990: * @return whether or not the operation completed sucessfully
1991: */
1992: public boolean removeViewLocalAttribute(Object key,
1993: boolean addToModel, boolean override) {
1994: if (localAttributes.contains(key)) {
1995: if (addToModel) {
1996: // Iterate through all views copying this attribute to the
1997: // cell.
1998: copyRemovedViewValue(key, addToModel, override, mapping
1999: .values());
2000: copyRemovedViewValue(key, addToModel, override,
2001: hiddenMapping.values());
2002: }
2003: localAttributes.remove(key);
2004: return true;
2005: }
2006: return false;
2007: }
2008:
2009: /**
2010: * Helper method to copy removed view local attributes to model cell's
2011: *
2012: * @param key
2013: * the key of the view local attribute
2014: * @param addToModel
2015: * whether or not to move the attribute values to the graph model
2016: * @param override
2017: * whether or not to override the key's value in the model cell's
2018: * attribute map if it exists
2019: * @param coll
2020: * the current collection being analysed
2021: */
2022: private void copyRemovedViewValue(Object key, boolean addToModel,
2023: boolean override, Collection coll) {
2024: Iterator iter = coll.iterator();
2025: while (iter.hasNext()) {
2026: CellView cellView = (CellView) iter.next();
2027: Map attributes = cellView.getAttributes();
2028: if (attributes.containsKey(key)) {
2029: if (addToModel) {
2030: Object cell = cellView.getCell();
2031: Map cellAttributes = graphModel.getAttributes(cell);
2032: if (cellAttributes != null) {
2033: boolean cellContainsKey = cellAttributes
2034: .containsKey(key);
2035: // Write the model cell's attribute map key
2036: // if overriding is enabled or if key doesn't
2037: // exist
2038: if (!override || !cellContainsKey) {
2039: Object value = attributes.get(key);
2040: cellAttributes.put(key, value);
2041: }
2042: }
2043: }
2044: attributes.remove(key);
2045: }
2046: }
2047: }
2048:
2049: /**
2050: * An implementation of GraphViewChange.
2051: */
2052: public class GraphLayoutCacheEdit extends CompoundEdit implements
2053: GraphLayoutCacheEvent.GraphLayoutCacheChange {
2054:
2055: protected Object[] cells, previousCells = null;
2056:
2057: protected CellView[] context, hidden;
2058:
2059: protected Map attributes, previousAttributes;
2060:
2061: protected Object[] visible, invisible;
2062:
2063: // Remember which cells have changed for finding their context
2064: protected Set changedCells = new HashSet();
2065:
2066: /**
2067: * Constructs a GraphViewEdit. This modifies the attributes of the
2068: * specified views and may be used to notify UndoListeners.
2069: *
2070: * @param nested
2071: * the map that defines the new attributes
2072: */
2073: public GraphLayoutCacheEdit(Map nested) {
2074: this (null, nested, null, null);
2075: attributes = nested;
2076: }
2077:
2078: /**
2079: * Constructs a GraphViewEdit. This modifies the attributes of the
2080: * specified views and may be used to notify UndoListeners. This should
2081: * also take an array of removed cell views, but it is not possible to
2082: * add further UndoableEdits to an already executed CompoundEdit, such
2083: * as a GraphModel change. Thus, to handle implicit changes -- rather
2084: * than piggybacking on the model's event -- the CompoundEdit's addEdit
2085: * method should be extended to accept and instantly execute sub-
2086: * sequent edits (implicit changes to the view, such as removing a
2087: * mapping, hiding a view or the like).
2088: *
2089: * @param inserted
2090: * an array of inserted cells
2091: * @param attributes
2092: * the map that defines the new attributes
2093: * @param visible
2094: * an array defining which cells are visible
2095: * @param invisible
2096: * an array defining which cells are invisible
2097: */
2098: public GraphLayoutCacheEdit(Object[] inserted, Map attributes,
2099: Object[] visible, Object[] invisible) {
2100: super ();
2101: this .attributes = attributes;
2102: this .previousAttributes = attributes;
2103: this .cells = inserted;
2104: this .visible = visible;
2105: this .invisible = invisible;
2106: }
2107:
2108: public Object getSource() {
2109: return GraphLayoutCache.this ;
2110: }
2111:
2112: public boolean isSignificant() {
2113: return true;
2114: }
2115:
2116: /**
2117: * Returns the cell views that have changed.
2118: */
2119: public Object[] getChanged() {
2120: return changedCells.toArray();
2121: }
2122:
2123: /**
2124: * Returns the cells that habe been made visible.
2125: */
2126: public Object[] getInserted() {
2127: return invisible;
2128: }
2129:
2130: /**
2131: * Returns the cells that have changed.
2132: */
2133: public Object[] getRemoved() {
2134: return visible;
2135: }
2136:
2137: /**
2138: * Returns the views that have not changed explicitly, but implicitly
2139: * because one of their dependent cells has changed.
2140: */
2141: public Object[] getContext() {
2142: return context;
2143: }
2144:
2145: /**
2146: * Returns a map of (cell view, attribute) pairs.
2147: */
2148: public Map getAttributes() {
2149: return attributes;
2150: }
2151:
2152: /**
2153: * Returns a map of (cell view, attribute) pairs.
2154: */
2155: public Map getPreviousAttributes() {
2156: return previousAttributes;
2157: }
2158:
2159: /**
2160: * Redoes a change.
2161: *
2162: * @exception CannotRedoException
2163: * if the change cannot be redone
2164: */
2165: public void redo() throws CannotRedoException {
2166: super .redo();
2167: execute();
2168: }
2169:
2170: /**
2171: * Undoes a change.
2172: *
2173: * @exception CannotUndoException
2174: * if the change cannot be undone
2175: */
2176: public void undo() throws CannotUndoException {
2177: super .undo();
2178: execute();
2179: }
2180:
2181: /**
2182: * Execute this edit such that the next invocation to this method will
2183: * invert the last execution.
2184: */
2185: public void execute() {
2186: GraphModel model = getModel();
2187: changedCells.clear();
2188: // Remember or restore hidden cells
2189: if (hidden != null)
2190: for (int i = 0; i < hidden.length; i++)
2191: if (hidden[i] != null)
2192: mapping.put(hidden[i].getCell(), hidden[i]);
2193: if (!remembersCellViews) // already remembered
2194: hidden = getMapping(invisible);
2195: // Handle visibility
2196: boolean updatePorts = setVisibleImpl(visible, true)
2197: | setVisibleImpl(invisible, false);
2198: if (visible != null) {
2199: for (int i = 0; i < visible.length; i++) {
2200: changedCells.add(visible[i]);
2201:
2202: // Only calls if not inserted
2203: if (cells == null)
2204: cellExpanded(visible[i]);
2205: }
2206: }
2207: if (invisible != null)
2208: for (int i = 0; i < invisible.length; i++)
2209: changedCells.add(invisible[i]);
2210: // Swap arrays
2211: Object[] tmp = visible;
2212: visible = invisible;
2213: invisible = tmp;
2214: // Handle attributes
2215: if (attributes != null) {
2216: previousAttributes = attributes;
2217: changedCells.addAll(attributes.keySet());
2218: attributes = handleAttributes(attributes);
2219: }
2220: if (updatePorts)
2221: updatePorts();
2222: // Add ancestor cells to changed cells
2223: Set parentSet = new HashSet();
2224: Iterator it = changedCells.iterator();
2225: while (it.hasNext()) {
2226: Object parent = model.getParent(it.next());
2227: while (parent != null) {
2228: parentSet.add(parent);
2229: parent = model.getParent(parent);
2230: }
2231: }
2232: changedCells.addAll(parentSet);
2233: // Refresh all changed cells
2234: refresh(getMapping(changedCells.toArray(), false), false);
2235: // Updates the connected edges. Make sure that changedCells
2236: // contains no edges, as these will be removed from the result.
2237: Set ctx = DefaultGraphModel.getEdges(getModel(),
2238: changedCells.toArray());
2239: context = getMapping(ctx.toArray());
2240: refresh(context, false);
2241: tmp = cells;
2242: cells = previousCells;
2243: previousCells = tmp;
2244: reloadRoots();
2245: fireGraphLayoutCacheChanged(GraphLayoutCache.this , this );
2246: }
2247: }
2248:
2249: /**
2250: * Called when a child has been made visible by expanding its parent. This
2251: * implementation translates the child so that it reflects the offset of the
2252: * parent group since the child was last visible (see
2253: * {@link #movesChildrenOnExpand}).
2254: */
2255: protected void cellExpanded(Object cell) {
2256: GraphModel model = getModel();
2257: // Moves the child to the group origin if it is not a port
2258: if (movesChildrenOnExpand && !model.isPort(cell)) {
2259: CellView view = getMapping(cell, false);
2260:
2261: if (view != null) {
2262: CellView parent = getMapping(model.getParent(cell),
2263: false);
2264: if (parent != null) {
2265: if (DefaultGraphModel.isVertex(model, parent)) {
2266: // Computes the offset of the parent group
2267: Rectangle2D src = GraphConstants
2268: .getBounds(parent.getAllAttributes());
2269: Rectangle2D rect = parent.getBounds();
2270: double dx = src.getX() - rect.getX();
2271: double dy = src.getY() - rect.getY();
2272:
2273: // Gets the attributes from the cell view or
2274: // cell and translates the bounds or points
2275: AttributeMap attrs = view.getAttributes();
2276: if (!attrs.contains(GraphConstants.BOUNDS))
2277: attrs = model.getAttributes(view.getCell());
2278: attrs.translate(dx, dy);
2279: }
2280: }
2281: }
2282: }
2283: }
2284:
2285: protected void cellWillCollapse(Object cell) {
2286: GraphModel model = getModel();
2287: if (movesParentsOnCollapse) {
2288: CellView view = getMapping(cell, false);
2289: if (view != null && !view.isLeaf()) {
2290: // Uses view-local attribute if available
2291: AttributeMap attrs = view.getAttributes();
2292: if (!attrs.contains(GraphConstants.BOUNDS)
2293: && !localAttributes
2294: .contains(GraphConstants.BOUNDS))
2295: attrs = model.getAttributes(cell);
2296:
2297: // Moves the group to the origin of the children
2298: Rectangle2D src = GraphConstants.getBounds(attrs);
2299: Rectangle2D b = view.getBounds();
2300: // FIXME: What if the group is exactly at "defaultBounds"?
2301: if (resizesParentsOnCollapse || src == null
2302: || src.equals(VertexView.defaultBounds)) {
2303: src = attrs.createRect(b.getX(), b.getY(), b
2304: .getWidth()
2305: * collapseXScale, b.getHeight()
2306: * collapseYScale);
2307: attrs.applyValue(GraphConstants.BOUNDS, src);
2308: } else {
2309: src.setFrame(b.getX(), b.getY(), src.getWidth(),
2310: src.getHeight());
2311: }
2312: }
2313: }
2314: }
2315:
2316: /**
2317: * Attention: Undo will not work for routing-change if ROUTING and POINTS
2318: * are stored in different locations. This happens if the model holds the
2319: * routing attribute and the routing changes from unrouted to routed. In
2320: * this case the points in the view are already routed according to the new
2321: * scheme when written to the command history (-> no undo).
2322: */
2323: protected Map handleAttributes(Map attributes) {
2324: Map undo = new Hashtable();
2325: CellView[] views = new CellView[attributes.size()];
2326: Iterator it = attributes.entrySet().iterator();
2327: int i = 0;
2328: while (it.hasNext()) {
2329: Map.Entry entry = (Map.Entry) it.next();
2330: CellView cv = getMapping(entry.getKey(), false);
2331: views[i] = cv;
2332: i += 1;
2333: if (cv != null && cv.getAttributes() != null) {
2334: Map deltaNew = (Map) entry.getValue();
2335: // System.out.println("state=" + cv.getAttributes());
2336: // System.out.println("change=" + deltaNew);
2337: Map deltaOld = cv.getAttributes().applyMap(deltaNew);
2338: cv.refresh(this , this , false);
2339: // System.out.println("state'=" + cv.getAttributes());
2340: // System.out.println("change'=" + deltaOld);
2341: undo.put(cv.getCell(), deltaOld);
2342: }
2343: }
2344: // Re-route all child edges
2345: update(views);
2346: return undo;
2347: }
2348:
2349: //
2350: // Static Methods
2351: //
2352: /**
2353: * Translates the specified views by the given amount.
2354: *
2355: * @param views
2356: * an array of cell view to each be translated
2357: * @param dx
2358: * the amount to translate the views in the x-axis
2359: * @param dy
2360: * the amount to translate the views in the x-axis
2361: */
2362: public static void translateViews(CellView[] views, double dx,
2363: double dy) {
2364: for (int i = 0; i < views.length; i++) {
2365: if (views[i] instanceof AbstractCellView) {
2366: ((AbstractCellView) views[i]).translate(dx, dy);
2367: }
2368: }
2369: }
2370:
2371: /**
2372: * Returns a collection of cells that are connected to the specified cell by
2373: * edges. Any cells specified in the exclude set will be ignored.
2374: *
2375: * @param cell
2376: * The cell from which the neighbours will be determined
2377: * @param exclude
2378: * The set of cells to ignore when searching
2379: * @param directed
2380: * whether or not direction of edges should be taken into account
2381: * @param visibleCells
2382: * whether or not to only consider visible cells
2383: * @return Returns the list of neighbours for <code>cell</code>
2384: */
2385: public List getNeighbours(Object cell, Set exclude,
2386: boolean directed, boolean visibleCells) {
2387: // Traverse Graph
2388: GraphModel model = getModel();
2389: Object[] fanout = (directed) ? DefaultGraphModel
2390: .getOutgoingEdges(model, cell) : DefaultGraphModel
2391: .getEdges(model, new Object[] { cell }).toArray();
2392: List neighbours = new ArrayList(fanout.length);
2393: Set localExclude = new HashSet(fanout.length + 8, (float) 0.75);
2394: for (int i = 0; i < fanout.length; i++) {
2395: // if only visible cells are being processed, check that this
2396: // edge is visible before looking for neighbours with it
2397: if (!visibleCells || isVisible(fanout[i])) {
2398: Object neighbour = DefaultGraphModel.getOpposite(model,
2399: fanout[i], cell);
2400: if (neighbour != null
2401: && (exclude == null || !exclude
2402: .contains(neighbour))
2403: && !localExclude.contains(neighbour)
2404: && (!visibleCells || isVisible(neighbour))) {
2405: localExclude.add(neighbour);
2406: neighbours.add(neighbour);
2407: }
2408: }
2409: }
2410: return neighbours;
2411: }
2412:
2413: /**
2414: * Returns the outgoing edges for cell. Cell should be a port or a vertex.
2415: *
2416: * @param cell
2417: * The cell from which the outgoing edges will be determined
2418: * @param exclude
2419: * The set of edges to ignore when searching
2420: * @param visibleCells
2421: * whether or not only visible cells should be processed
2422: * @param selfLoops
2423: * whether or not to include self loops in the returned list
2424: * @return Returns the list of outgoing edges for <code>cell</code>
2425: */
2426: public List getOutgoingEdges(Object cell, Set exclude,
2427: boolean visibleCells, boolean selfLoops) {
2428: return getEdges(cell, exclude, visibleCells, selfLoops, false);
2429: }
2430:
2431: /**
2432: * Returns the incoming edges for cell. Cell should be a port or a vertex.
2433: *
2434: * @param cell
2435: * The cell from which the incoming edges will be determined
2436: * @param exclude
2437: * The set of edges to ignore when searching
2438: * @param visibleCells
2439: * whether or not only visible cells should be processed
2440: * @param selfLoops
2441: * whether or not to include self loops in the returned list
2442: * @return Returns the list of incoming edges for <code>cell</code>
2443: */
2444: public List getIncomingEdges(Object cell, Set exclude,
2445: boolean visibleCells, boolean selfLoops) {
2446: return getEdges(cell, exclude, visibleCells, selfLoops, true);
2447: }
2448:
2449: /**
2450: * Returns the incoming or outgoing edges for cell. Cell should be a port or
2451: * a vertex.
2452: *
2453: * @param cell
2454: * The cell from which the edges will be determined
2455: * @param exclude
2456: * The set of edges to ignore when searching
2457: * @param visibleCells
2458: * whether or not only visible cells should be processed
2459: * @param selfLoops
2460: * whether or not to include self loops in the returned list
2461: * @param incoming
2462: * <code>true</code> if incoming edges are to be obtained,
2463: * <code>false</code> if outgoing edges are to be obtained
2464: * @return Returns the list of incoming or outgoing edges for
2465: * <code>cell</code>
2466: */
2467: protected List getEdges(Object cell, Set exclude,
2468: boolean visibleCells, boolean selfLoops, boolean incoming) {
2469: GraphModel model = getModel();
2470: Object[] edges = DefaultGraphModel.getEdges(model, cell,
2471: incoming);
2472:
2473: List edgeList = new ArrayList(edges.length);
2474: Set localExclude = new HashSet(edges.length);
2475: for (int i = 0; i < edges.length; i++) {
2476: // Check that the edge is neiter in the passed in exclude set or
2477: // the local exclude set. Also, if visibleCells is true check
2478: // the edge is visible in the cache.
2479: if ((exclude == null || !exclude.contains(edges[i]))
2480: && !localExclude.contains(edges[i])
2481: && (!visibleCells || isVisible(edges[i]))) {
2482: // Add the edge to the list if all edges, including self loops
2483: // are allowed. If self loops are not allowed, ensure the
2484: // source and target of the edge are different
2485: if (selfLoops == true
2486: || model.getSource(edges[i]) != model
2487: .getTarget(edges[i])) {
2488: edgeList.add(edges[i]);
2489: }
2490: localExclude.add(edges[i]);
2491: }
2492: }
2493: return edgeList;
2494: }
2495:
2496: /**
2497: * Returns all views, shortcut to getAllDescendants(getRoots())
2498: */
2499: public CellView[] getAllViews() {
2500: return getAllDescendants(getRoots());
2501: }
2502:
2503: /**
2504: * Returns all views, including descendants that have a parent in
2505: * <code>views</code>, especially the PortViews. Note: Iterative
2506: * Implementation using model.getChild and getMapping on this cell mapper.
2507: */
2508: public CellView[] getAllDescendants(CellView[] views) {
2509: Stack stack = new Stack();
2510: for (int i = 0; i < views.length; i++)
2511: if (views[i] != null)
2512: stack.add(views[i]);
2513: ArrayList result = new ArrayList();
2514: while (!stack.isEmpty()) {
2515: CellView tmp = (CellView) stack.pop();
2516: Object[] children = tmp.getChildViews();
2517: for (int i = 0; i < children.length; i++)
2518: stack.add(children[i]);
2519: result.add(tmp);
2520: // Add Port Views
2521: for (int i = 0; i < graphModel.getChildCount(tmp.getCell()); i++) {
2522: Object child = graphModel.getChild(tmp.getCell(), i);
2523: if (graphModel.isPort(child)) {
2524: CellView view = getMapping(child, false);
2525: if (view != null)
2526: stack.add(view);
2527: }
2528: }
2529: }
2530: CellView[] ret = new CellView[result.size()];
2531: result.toArray(ret);
2532: return ret;
2533: }
2534:
2535: /**
2536: * Returns the hiddenMapping.
2537: *
2538: * @return Map
2539: */
2540: public Map getHiddenMapping() {
2541: return hiddenMapping;
2542: }
2543:
2544: /**
2545: * Sets the showsExistingConnections
2546: *
2547: * @param showsExistingConnections
2548: */
2549: public void setShowsExistingConnections(
2550: boolean showsExistingConnections) {
2551: this .showsExistingConnections = showsExistingConnections;
2552: }
2553:
2554: /**
2555: * Returns the showsExistingConnections.
2556: *
2557: * @return boolean
2558: */
2559: public boolean isShowsExistingConnections() {
2560: return showsExistingConnections;
2561: }
2562:
2563: /**
2564: * Sets the showsInsertedConnections
2565: *
2566: * @param showsInsertedConnections
2567: */
2568: public void setShowsInsertedConnections(
2569: boolean showsInsertedConnections) {
2570: this .showsInsertedConnections = showsInsertedConnections;
2571: }
2572:
2573: /**
2574: * Returns the showsInsertedConnections.
2575: *
2576: * @return boolean
2577: */
2578: public boolean isShowsInsertedConnections() {
2579: return showsInsertedConnections;
2580: }
2581:
2582: /**
2583: * Sets the hidesExistingConnections
2584: *
2585: * @param hidesExistingConnections
2586: */
2587: public void setHidesExistingConnections(
2588: boolean hidesExistingConnections) {
2589: this .hidesExistingConnections = hidesExistingConnections;
2590: }
2591:
2592: /**
2593: * Returns the hidesExistingConnections.
2594: *
2595: * @return boolean
2596: */
2597: public boolean isHidesExistingConnections() {
2598: return hidesExistingConnections;
2599: }
2600:
2601: /**
2602: * Sets the hidesDanglingConnections
2603: *
2604: * @param hidesDanglingConnections
2605: */
2606: public void setHidesDanglingConnections(
2607: boolean hidesDanglingConnections) {
2608: this .hidesDanglingConnections = hidesDanglingConnections;
2609: }
2610:
2611: /**
2612: * Returns the hidesDanglingConnections.
2613: *
2614: * @return boolean
2615: */
2616: public boolean isHidesDanglingConnections() {
2617: return hidesDanglingConnections;
2618: }
2619:
2620: /**
2621: * Sets the rememberCellViews.
2622: *
2623: * @param rememberCellViews
2624: * The rememberCellViews to set
2625: */
2626: public void setRemembersCellViews(boolean rememberCellViews) {
2627: this .remembersCellViews = rememberCellViews;
2628: }
2629:
2630: /**
2631: * Returns the remembersCellViews.
2632: *
2633: * @return boolean
2634: */
2635: public boolean isRemembersCellViews() {
2636: return remembersCellViews;
2637: }
2638:
2639: /**
2640: * Sets the hiddenSet.
2641: *
2642: * @param hiddenSet
2643: * The hiddenSet to set
2644: */
2645: public void setHiddenSet(Map hiddenSet) {
2646: this .hiddenMapping = hiddenSet;
2647: }
2648:
2649: /**
2650: * @return Returns the localAttributes.
2651: */
2652: public Set getLocalAttributes() {
2653: return localAttributes;
2654: }
2655:
2656: /**
2657: * @param localAttributes
2658: * The localAttributes to set.
2659: */
2660: public void setLocalAttributes(Set localAttributes) {
2661: this .localAttributes = localAttributes;
2662: }
2663:
2664: /**
2665: * @return Returns the askLocalAttribute.
2666: */
2667: public boolean isAllAttributesLocal() {
2668: return allAttributesLocal;
2669: }
2670:
2671: /**
2672: * @param allAttributesLocal
2673: * The allAttributesLocal to set.
2674: */
2675: public void setAllAttributesLocal(boolean allAttributesLocal) {
2676: this .allAttributesLocal = allAttributesLocal;
2677: }
2678:
2679: /**
2680: * Returns true if cells should be auto-sized when their values change
2681: *
2682: * @return true if cells should be auto-sized when their values change
2683: */
2684: public boolean isAutoSizeOnValueChange() {
2685: return autoSizeOnValueChange;
2686: }
2687:
2688: /**
2689: * Determines whether cells should be auto-sized when their values change.
2690: * Fires a property change event if the new setting is different from the
2691: * existing setting.
2692: *
2693: * @param flag
2694: * a boolean value, true if cells should be auto-sized when their
2695: * values change
2696: */
2697: public void setAutoSizeOnValueChange(boolean flag) {
2698: this .autoSizeOnValueChange = flag;
2699: }
2700:
2701: /**
2702: * @return Returns the selectsAllInsertedCells.
2703: */
2704: public boolean isSelectsAllInsertedCells() {
2705: return selectsAllInsertedCells;
2706: }
2707:
2708: /**
2709: * @param selectsAllInsertedCells
2710: * The selectsAllInsertedCells to set.
2711: */
2712: public void setSelectsAllInsertedCells(
2713: boolean selectsAllInsertedCells) {
2714: this .selectsAllInsertedCells = selectsAllInsertedCells;
2715: }
2716:
2717: /**
2718: * @return Returns the selectsLocalInsertedCells.
2719: */
2720: public boolean isSelectsLocalInsertedCells() {
2721: return selectsLocalInsertedCells;
2722: }
2723:
2724: /**
2725: * @param selectsLocalInsertedCells
2726: * The selectsLocalInsertedCells to set.
2727: */
2728: public void setSelectsLocalInsertedCells(
2729: boolean selectsLocalInsertedCells) {
2730: this .selectsLocalInsertedCells = selectsLocalInsertedCells;
2731: }
2732:
2733: /**
2734: * @return Returns the reconnectsEdgesToVisibleParent.
2735: * @deprecated edges are moved to parent view and back automatically
2736: */
2737: public boolean isReconnectsEdgesToVisibleParent() {
2738: return reconnectsEdgesToVisibleParent;
2739: }
2740:
2741: /**
2742: * @param reconnectsEdgesToVisibleParent
2743: * The reconnectsEdgesToVisibleParent to set.
2744: * @deprecated edges are moved to parent view and back automatically
2745: */
2746: public void setReconnectsEdgesToVisibleParent(
2747: boolean reconnectsEdgesToVisibleParent) {
2748: this .reconnectsEdgesToVisibleParent = reconnectsEdgesToVisibleParent;
2749: }
2750:
2751: /**
2752: * @return Returns the showsChangedConnections.
2753: */
2754: public boolean isShowsChangedConnections() {
2755: return showsChangedConnections;
2756: }
2757:
2758: /**
2759: * @param showsChangedConnections
2760: * The showsChangedConnections to set.
2761: */
2762: public void setShowsChangedConnections(
2763: boolean showsChangedConnections) {
2764: this .showsChangedConnections = showsChangedConnections;
2765: }
2766:
2767: /**
2768: * @return Returns the moveChildrenOnExpand.
2769: */
2770: public boolean isMovesChildrenOnExpand() {
2771: return movesChildrenOnExpand;
2772: }
2773:
2774: /**
2775: * @param moveChildrenOnExpand
2776: * The moveChildrenOnExpand to set.
2777: */
2778: public void setMovesChildrenOnExpand(boolean moveChildrenOnExpand) {
2779: this .movesChildrenOnExpand = moveChildrenOnExpand;
2780: }
2781:
2782: /**
2783: * @return Returns the collapseXScale.
2784: */
2785: public double getCollapseXScale() {
2786: return collapseXScale;
2787: }
2788:
2789: /**
2790: * @param collapseXScale
2791: * The collapseXScale to set.
2792: */
2793: public void setCollapseXScale(double collapseXScale) {
2794: this .collapseXScale = collapseXScale;
2795: }
2796:
2797: /**
2798: * @return Returns the collapseYScale.
2799: */
2800: public double getCollapseYScale() {
2801: return collapseYScale;
2802: }
2803:
2804: /**
2805: * @param collapseYScale
2806: * The collapseYScale to set.
2807: */
2808: public void setCollapseYScale(double collapseYScale) {
2809: this .collapseYScale = collapseYScale;
2810: }
2811:
2812: /**
2813: * @return Returns the movesParentsOnCollapse.
2814: */
2815: public boolean isMovesParentsOnCollapse() {
2816: return movesParentsOnCollapse;
2817: }
2818:
2819: /**
2820: * @param movesParentsOnCollapse
2821: * The movesParentsOnCollapse to set.
2822: */
2823: public void setMovesParentsOnCollapse(boolean movesParentsOnCollapse) {
2824: this .movesParentsOnCollapse = movesParentsOnCollapse;
2825: }
2826:
2827: /**
2828: * @return Returns the resizesParentsOnCollapse.
2829: */
2830: public boolean isResizesParentsOnCollapse() {
2831: return resizesParentsOnCollapse;
2832: }
2833:
2834: /**
2835: * @param resizesParentsOnCollapse
2836: * The resizesParentsOnCollapse to set.
2837: */
2838: public void setResizesParentsOnCollapse(
2839: boolean resizesParentsOnCollapse) {
2840: this .resizesParentsOnCollapse = resizesParentsOnCollapse;
2841: }
2842:
2843: /**
2844: * Serialization support.
2845: */
2846: private void writeObject(ObjectOutputStream s) throws IOException {
2847: s.defaultWriteObject();
2848: // Write out the hidden mapping
2849: Map map = new Hashtable(hiddenMapping);
2850: s.writeObject(map);
2851: }
2852:
2853: /**
2854: * Serialization support.
2855: */
2856: private void readObject(ObjectInputStream s) throws IOException,
2857: ClassNotFoundException {
2858: s.defaultReadObject();
2859: // Read the hidden mapping
2860: Map map = (Map) s.readObject();
2861: hiddenMapping = new WeakHashMap(map);
2862: }
2863:
2864: }
|