Package org.jgraph.graph

Source Code of org.jgraph.graph.GraphLayoutCache

/*
* @(#)GraphLayoutCache.java 1.0 03-JUL-04
*
* Copyright (c) 2001-2004 Gaudenz Alder
*/
package org.jgraph.graph;

import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import javax.swing.event.EventListenerList;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoableEdit;

import org.jgraph.event.GraphLayoutCacheEvent;
import org.jgraph.event.GraphLayoutCacheListener;
import org.jgraph.event.GraphModelEvent;

/**
* An object that defines the view of a graphmodel. This object maps between
* model cells and views and provides a set of methods to change these views.
* The view may also contain its own set of attributes and is therefore an
* extension of an Observable, which may be observed by the GraphUI. It uses the
* model to send its changes to the command history.
*
* @version 1.0 1/1/02
* @author Gaudenz Alder
*/
public class GraphLayoutCache implements CellMapper, Serializable {

  /**
   * True if the cells should be auto-sized when their values change. Default
   * is false.
   */
  protected boolean autoSizeOnValueChange = false;

  /**
   * Boolean indicating whether existing connections should me made visible if
   * their sources or targets are made visible, given the opposite end of the
   * edge is already visible or made visible, too. Default is true.
   */
  protected boolean showsExistingConnections = true;

  /**
   * Boolean indicating whether connections should be made visible when
   * reconnected and their source and target port is visible. Default is true.
   */
  protected boolean showsChangedConnections = true;

  /**
   * Boolean indicating whether edited cells should be made visible if they
   * are changed via
   * {@link #edit(Map, ConnectionSet, ParentMap, UndoableEdit[])}. Default is
   * true.
   */
  protected boolean showsInvisibleEditedCells = true;

  /**
   * Boolean indicating whether inserted should be made visible if they are
   * inserted via
   * {@link #insert(Object[], Map, ConnectionSet, ParentMap, UndoableEdit[])}.
   * Default is true.
   */
  protected boolean showsInsertedCells = true;

  /**
   * Boolean indicating whether inserted edges should me made visible if their
   * sources or targets are already visible. Default is true.
   */
  protected boolean showsInsertedConnections = true;

  /**
   * Boolean indicating whether existing connections should be hidden if their
   * source or target and no parent of the ports is visible, either by hiding
   * the cell or by changing the source or target of the edge to a hidden
   * cell. Default is true.
   */
  protected boolean hidesExistingConnections = true;

  /**
   * Boolean indicating whether existing connections should be hidden if their
   * source or target port is removed from the model. Default is false.
   */
  protected boolean hidesDanglingConnections = false;

  /**
   * Boolean indicating whether cellviews should be remembered once visible in
   * this GraphLayoutCache. Default is true.
   */
  protected boolean remembersCellViews = true;

  /**
   * Boolean indicating whether inserted cells should automatically be
   * selected. Default is true. This is ignored if the cache is partial. Note:
   * Despite the name of this field the implementation is located in the
   * BasicGraphUI.GraphModelHandler.graphChanged method.
   */
  protected boolean selectsAllInsertedCells = true;

  /**
   * Boolean indicating whether cells that are inserted using the local insert
   * method should automatically be selected. Default is true. This is ignored
   * if the cache is not partial and selectsAllInsertedCells is true, in which
   * case the cells will be selected through another mechanism. Note: Despite
   * the name of this field the implementation is located in the
   * BasicGraphUI.GraphLayoutCacheObserver.changed method.
   */
  protected boolean selectsLocalInsertedCells = true;

  /**
   * Boolean indicating whether children should be moved to the parent group's
   * origin on expand. Default is true.
   */
  protected boolean movesChildrenOnExpand = true;

  /**
   * Boolean indicating whether parents should be moved to the child area
   * origin on collapse. Default is true.
   */
  protected boolean movesParentsOnCollapse = true;

  /**
   * Boolean indicating whether parents should always be resized to the child
   * area on collapse. If false the size is only initially updated if it has
   * not yet been assigned. Default is false.
   */
  protected boolean resizesParentsOnCollapse = false;

  /**
   * Specified the initial x- and y-scaling factor for initial collapsed group
   * bounds. Default is 1.0, ie. no scaling.
   */
  protected double collapseXScale = 1.0, collapseYScale = 1.0;

  /**
   * Boolean indicating whether edges should be reconneted to visible parents
   * on collapse/expand. Default is false.
   *
   * @deprecated edges are moved to parent view and back automatically
   */
  protected boolean reconnectsEdgesToVisibleParent = false;

  /**
   * The list of listeners that listen to the model.
   */
  protected transient EventListenerList listenerList = new EventListenerList();

  /**
   * Reference to the graphModel
   */
  protected GraphModel graphModel;

  /**
   * Maps cells to views. The hidden mapping is used to remembed cell views
   * that are hidden, based on the remembersCellViews setting.
   */
  protected transient Map mapping = new Hashtable(),
      hiddenMapping = new Hashtable();

  /**
   * Factory to create the views.
   */
  protected CellViewFactory factory = null;

  /**
   * The set of visible cells.
   */
  protected transient Set visibleSet = new HashSet();

  /**
   * Ordered list of roots for the view.
   */
  protected transient List roots = new ArrayList();

  /**
   * Cached array of all ports for the view.
   */
  protected transient PortView[] ports;

  /**
   * Only portions of the model are visible.
   */
  protected boolean partial = false;

  /**
   * Controls if all attributes are local. If this is false then the
   * createLocalEdit will check the localAttributes set to see if a specific
   * attribute is local, otherwise it will assume that all attributes are
   * local. This allows to make all attributes local without actually knowing
   * them. Default is false.
   */
  protected boolean allAttributesLocal = false;

  /**
   * A set containing all attribute keys that are stored in the cell views, in
   * other words, the view-local attributes.
   */
  protected Set localAttributes = new HashSet();

  /**
   * Constructs a graph layout cache.
   */
  public GraphLayoutCache() {
    this(new DefaultGraphModel(), new DefaultCellViewFactory());
  }

  /**
   * Constructs a view for the specified model that uses <code>factory</code>
   * to create its views.
   *
   * @param model
   *            the model that constitues the data source
   */
  public GraphLayoutCache(GraphModel model, CellViewFactory factory) {
    this(model, factory, false);
  }

  /**
   * Constructs a view for the specified model that uses <code>factory</code>
   * to create its views.
   *
   * @param model
   *            the model that constitues the data source
   */
  public GraphLayoutCache(GraphModel model, CellViewFactory factory,
      boolean partial) {
    this(model, factory, null, null, partial);
  }

  /**
   * Constructs a view for the specified model that uses <code>factory</code>
   * to create its views.
   *
   * @param model
   *            the model that constitues the data source
   */
  public GraphLayoutCache(GraphModel model, CellViewFactory factory,
      CellView[] cellViews, CellView[] hiddenCellViews, boolean partial) {
    this.factory = factory;
    this.partial = partial;
    if (cellViews != null) {
      graphModel = model;
      for (int i = 0; i < cellViews.length; i++) {
        if (cellViews[i] != null) {
          putMapping(cellViews[i].getCell(), cellViews[i]);
          if (partial)
            visibleSet.add(cellViews[i].getCell());
        }
      }
      insertViews(cellViews);
      // Notify observers for autosizing?
    } else {
      setModel(model);
    }
    if (hiddenCellViews != null) {
      for (int i = 0; i < hiddenCellViews.length; i++)
        hiddenMapping.put(hiddenCellViews[i].getCell(),
            hiddenCellViews[i]);
    }
  }

  //
  // GraphLayoutCacheListeners
  //

  /**
   * Adds a listener for the GraphLayoutCacheEvent posted after the graph
   * layout cache changes.
   *
   * @see #removeGraphLayoutCacheListener
   * @param l
   *            the listener to add
   */
  public void addGraphLayoutCacheListener(GraphLayoutCacheListener l) {
    listenerList.add(GraphLayoutCacheListener.class, l);
  }

  /**
   * Removes a listener previously added with <B>addGraphLayoutCacheListener()
   * </B>.
   *
   * @see #addGraphLayoutCacheListener
   * @param l
   *            the listener to remove
   */
  public void removeGraphLayoutCacheListener(GraphLayoutCacheListener l) {
    listenerList.remove(GraphLayoutCacheListener.class, l);
  }

  /**
   * Invoke this method after you've changed how the cells are to be
   * represented in the graph.
   */
  public void cellViewsChanged(final CellView[] cellViews) {
    if (cellViews != null) {
      fireGraphLayoutCacheChanged(this,
          new GraphLayoutCacheEvent.GraphLayoutCacheChange() {

            public Object[] getInserted() {
              return null;
            }

            public Object[] getRemoved() {
              return null;
            }

            public Map getPreviousAttributes() {
              return null;
            }

            public Object getSource() {
              return this;
            }

            public Object[] getChanged() {
              return cellViews;
            }

            public Map getAttributes() {
              return null;
            }

            public Object[] getContext() {
              return null;
            }

          });
    }
  }

  /*
   * Notify all listeners that have registered interest for notification on
   * this event type. The event instance is lazily created using the
   * parameters passed into the fire method.
   *
   * @see EventListenerList
   */
  protected void fireGraphLayoutCacheChanged(Object source,
      GraphLayoutCacheEvent.GraphLayoutCacheChange edit) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    GraphLayoutCacheEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == GraphLayoutCacheListener.class) {
        // Lazily create the event:
        if (e == null)
          e = new GraphLayoutCacheEvent(source, edit);
        ((GraphLayoutCacheListener) listeners[i + 1])
            .graphLayoutCacheChanged(e);
      }
    }
  }

  /**
   * Return an array of all GraphLayoutCacheListener that were added to this
   * model.
   */
  public GraphLayoutCacheListener[] getGraphLayoutCacheListeners() {
    return (GraphLayoutCacheListener[]) listenerList
        .getListeners(GraphLayoutCacheListener.class);
  }

  //
  // Accessors
  //

  /**
   * Sets the factory that creates the cell views.
   */
  public void setFactory(CellViewFactory factory) {
    this.factory = factory;
  }

  /**
   * Returns the factory that was passed to the constructor.
   */
  public CellViewFactory getFactory() {
    return factory;
  }

  /**
   * Sets the current model.
   */
  public void setModel(GraphModel model) {
    roots.clear();
    mapping.clear();
    hiddenMapping.clear();
    visibleSet.clear();
    graphModel = model;
    if (!isPartial()) {
      Object[] cells = DefaultGraphModel.getRoots(getModel());
      CellView[] cellViews = getMapping(cells, true);
      insertViews(cellViews);
    }
    // Update PortView Cache and Notify Observers
    updatePorts();
    cellViewsChanged(getRoots());
  }

  /**
   * @return Returns an unordered array of all visible cellviews.
   */
  public CellView[] getCellViews() {
    Collection coll = mapping.values();
    CellView[] result = new CellView[coll.size()];
    coll.toArray(result);
    return result;
  }

  /**
   * Returns the bounding box for the specified cell views.
   */
  public static Rectangle2D getBounds(CellView[] views) {
    if (views != null && views.length > 0) {
      Rectangle2D r = views[0].getBounds();
      Rectangle2D ret = (r != null) ? (Rectangle2D) r.clone() : null;
      for (int i = 1; i < views.length; i++) {
        r = views[i].getBounds();
        if (r != null) {
          if (ret == null)
            ret = (r != null) ? (Rectangle2D) r.clone() : null;
          else
            Rectangle2D.union(ret, r, ret);
        }
      }
      return ret;
    }
    return null;
  }
 
  /**
   * A helper method to return various arrays of cells that are visible in
   * this cache. For example, to get all selected vertices in a graph, do
   * <code>graph.getSelectionCells(graph.getGraphLayoutCache().getCells(false, true,
   false, false));</code>
   */
  public Object[] getCells(boolean groups, boolean vertices, boolean ports,
      boolean edges) {
    CellView[] views = getCellViews();
    List result = new ArrayList(views.length);
    GraphModel model = getModel();
    for (int i = 0; i < views.length; i++) {
      Object cell = views[i].getCell();
      boolean isEdge = model.isEdge(cell);
      if ((ports || !model.isPort(cell))) {
        if (((((ports || vertices) && !isEdge) || (edges && isEdge)) && views[i]
            .isLeaf())
            || (groups && !views[i].isLeaf()))
          result.add(views[i].getCell());

      }
    }
    return result.toArray();
  }

  /**
   * Returns a nested map of (cell, map) pairs that represent all attributes
   * of all cell views in this view.
   *
   * @see #getCellViews
   */
  public Map createNestedMap() {
    CellView[] cellViews = getCellViews();
    Map nested = new Hashtable();
    for (int i = 0; i < cellViews.length; i++) {
      nested.put(cellViews[i].getCell(), new Hashtable((Map) cellViews[i]
          .getAllAttributes().clone()));
    }
    return nested;
  }

  /**
   * @return Returns an unordered array of all hidden cellviews.
   */
  public CellView[] getHiddenCellViews() {
    Collection coll = hiddenMapping.values();
    CellView[] result = new CellView[coll.size()];
    coll.toArray(result);
    return result;
  }

  // Remaps all existing views using the CellViewFactory
  // and replaces the respective root views.
  public synchronized void reload() {
    List newRoots = new ArrayList();
    Map oldMapping = new Hashtable(mapping);
    mapping.clear();
    Iterator it = oldMapping.keySet().iterator();
    while (it.hasNext()) {
      Object cell = it.next();
      CellView oldView = (CellView) oldMapping.get(cell);
      CellView newView = getMapping(cell, true);
      newView.changeAttributes(oldView.getAttributes());
      // newView.refresh(getModel(), this, false);
      if (roots.contains(oldView))
        newRoots.add(newView);
    }
    // replace hidden
    hiddenMapping.clear();
    roots = newRoots;
  }

  /**
   * Returns the current model.
   */
  public GraphModel getModel() {
    return graphModel;
  }

  /**
   * Returns the roots of the view.
   */
  public CellView[] getRoots() {
    CellView[] views = new CellView[roots.size()];
    roots.toArray(views);
    return views;
  }

  /**
   * Return all root cells that intersect the given rectangle.
   */
  public CellView[] getRoots(Rectangle2D clip) {
    java.util.List result = new ArrayList();
    CellView[] views = getRoots();
    for (int i = 0; i < views.length; i++)
      if (views[i].getBounds().intersects(clip))
        result.add(views[i]);
    views = new CellView[result.size()];
    result.toArray(views);
    return views;
  }

  /**
   * Returns a an array with the visible cells in <code>cells</code>.
   */
  public Object[] getVisibleCells(Object[] cells) {
    if (cells != null) {
      List result = new ArrayList(cells.length);
      for (int i = 0; i < cells.length; i++)
        if (isVisible(cells[i]))
          result.add(cells[i]);
      return result.toArray();
    }
    return null;
  }

  /**
   * Returns the ports of the view.
   */
  public PortView[] getPorts() {
    return ports;
  }

  /**
   * Updates the cached array of ports.
   */
  protected void updatePorts() {
    Object[] roots = DefaultGraphModel.getRoots(graphModel);
    List list = DefaultGraphModel.getDescendants(graphModel, roots);
    if (list != null) {
      ArrayList result = new ArrayList();
      Iterator it = list.iterator();
      while (it.hasNext()) {
        Object cell = it.next();
        if (graphModel.isPort(cell)) {
          CellView portView = getMapping(cell, false);
          if (portView != null) {
            result.add(portView);
            portView.refresh(getModel(), this, false);
          }
        }
      }
      ports = new PortView[result.size()];
      result.toArray(ports);
    }
  }

  public void refresh(CellView[] views, boolean create) {
    if (views != null)
      for (int i = 0; i < views.length; i++)
        refresh(views[i], create);
  }

  public void refresh(CellView view, boolean create) {
    if (view != null) {
      view.refresh(getModel(), this, create);
      CellView[] children = view.getChildViews();
      for (int i = 0; i < children.length; i++)
        refresh(children[i], create);
    }
  }

  public void update(CellView[] views) {
    if (views != null)
      for (int i = 0; i < views.length; i++)
        update(views[i]);
  }

  public void update(CellView view) {
    if (view != null) {
      view.update();
      CellView[] children = view.getChildViews();
      for (int i = 0; i < children.length; i++)
        update(children[i]);
    }
  }

  //
  // Update View based on Model Change
  //
  /**
   * Called from BasicGraphUI.ModelHandler to update the view based on the
   * specified GraphModelEvent.
   */
  public void graphChanged(GraphModelEvent.GraphModelChange change) {
    // Get Old Attributes From GraphModelChange (Undo) -- used to remap
    // removed cells
    CellView[] views = change.getViews(this);
    if (views != null) {
      // Only ex-visible views are piggybacked
      for (int i = 0; i < views.length; i++)
        if (views[i] != null) {
          // Do not use putMapping because cells are invisible
          mapping.put(views[i].getCell(), views[i]);
        }
      // Ensure visible state
      setVisibleImpl(getCells(views), true);
    }
    // Fetch View Order Of Changed Cells (Before any changes)
    Object[] changed = change.getChanged();
    // Fetch Views to Insert before Removal (Special case: two step process,
    // see setModel)
    getMapping(change.getInserted(), true);
    // Remove and Hide Roots
    views = removeCells(change.getRemoved());
    // Store Removed Attributes In GraphModelChange (Undo)
    change.putViews(this, views);
    // Insert New Roots
    // insertViews(insertViews);
    // Hide edges with invisible source or target
    if (isPartial()) {
      // Then show
      showCellsForChange(change);
      // First hide
      hideCellsForChange(change);
    }
    // Refresh Changed Cells
    if (changed != null && changed.length > 0) {
      // Restore All Cells in Model Order (Replace Roots)
      for (int i = 0; i < changed.length; i++) {
        CellView view = getMapping(changed[i], false);
        if (view != null) {
          view.refresh(getModel(), this, true);
          // Update child edges in groups (routing)
          update(view);
        }
      }
    }
    reloadRoots();
    // Refresh Context of Changed Cells (=Connected Edges)
    refresh(getMapping(getContext(change), false), false);
    updatePorts();
  }

  /**
   * Completely reloads all roots from the model in the order returned by
   * DefaultGraphModel.getAll. This uses the current visibleSet and mapping to
   * fetch the cell views for the cells.
   */
  protected void reloadRoots() {
    // Reorder roots
    Object[] orderedCells = DefaultGraphModel.getAll(graphModel);
    List newRoots = new ArrayList();
    for (int i = 0; i < orderedCells.length; i++) {
      CellView view = getMapping(orderedCells[i], false);
      if (view != null) {
        view.refresh(getModel(), this, true);
        if (view.getParentView() == null) {
          newRoots.add(view);
        }
      }
    }
    roots = newRoots;
  }

  /**
   * Hook for subclassers to augment the context for a graphChange. This means
   * you can add additional cells that should be refreshed on a special change
   * event. eg. parallel edges when one is removed or added.
   */
  protected Object[] getContext(GraphModelEvent.GraphModelChange change) {
    return change.getContext();
  }

  protected void hideCellsForChange(GraphModelEvent.GraphModelChange change) {
    // Hide visible edges between invisible vertices
    // 1. Remove attached edges of removed cells
    // 2. Remove edges who's source or target has changed to
    // invisible.
    Object[] tmp = change.getRemoved();
    Set removed = new HashSet();
    if (tmp != null)
      for (int i = 0; i < tmp.length; i++)
        removed.add(tmp[i]);
    if (hidesDanglingConnections || hidesExistingConnections) {
      Object[] changed = change.getChanged();
      for (int i = 0; i < changed.length; i++) {
        CellView view = getMapping(changed[i], false);
        if (view instanceof EdgeView) {
          EdgeView edge = (EdgeView) view;
          Object oldSource = (edge.getSource() == null) ? null : edge
              .getSource().getCell();
          Object oldTarget = (edge.getTarget() == null) ? null : edge
              .getTarget().getCell();
          Object newSource = graphModel.getSource(changed[i]);
          Object newTarget = graphModel.getTarget(changed[i]);
          boolean hideExisting = (hidesExistingConnections && ((newSource != null && !hasVisibleParent(
              newSource, null)) || (newTarget != null && !hasVisibleParent(
              newTarget, null))));
          if ((hidesDanglingConnections && (removed
              .contains(oldSource) || removed.contains(oldTarget)))
              || hideExisting) {
            setVisibleImpl(new Object[] { changed[i] }, false);
          }
        }
      }
    }
  }

  /**
   * Checks if the port or one of its parents is visible.
   */
  protected boolean hasVisibleParent(Object cell, Set invisible) {
    boolean isVisible = false;
    do {
      isVisible = (invisible == null || !invisible.contains(cell)) ? isVisible(cell)
          : false;
      cell = getModel().getParent(cell);
    } while (cell != null && !isVisible);
    return isVisible;
  }

  protected void showCellsForChange(GraphModelEvent.GraphModelChange change) {
    Object[] inserted = change.getInserted();
    if (inserted != null && showsInsertedConnections) {
      for (int i = 0; i < inserted.length; i++) {
        if (!isVisible(inserted[i])) {
          Object source = graphModel.getSource(inserted[i]);
          Object target = graphModel.getTarget(inserted[i]);
          if ((source != null || target != null)
              && (isVisible(source) && isVisible(target)))
            setVisibleImpl(new Object[] { inserted[i] }, true);
        }
      }
    }
    if (change.getConnectionSet() != null) {
      Set changedSet = change.getConnectionSet().getChangedEdges();
      if (changedSet != null && showsChangedConnections) {
        Object[] changed = changedSet.toArray();
        for (int i = 0; i < changed.length; i++) {
          if (!isVisible(changed[i])) {
            Object source = graphModel.getSource(changed[i]);
            Object target = graphModel.getTarget(changed[i]);
            if ((source != null || target != null)
                && (isVisible(source) && isVisible(target))
                && !isVisible(changed[i]))
              setVisibleImpl(new Object[] { changed[i] }, true);
          }
        }
      }
    }
  }

  /**
   * Adds the specified model root cells to the view. Do not add a view that
   * is already in roots.
   */
  public void insertViews(CellView[] views) {
    if (views != null) {
      refresh(views, true);
      for (int i = 0; i < views.length; i++) {
        if (views[i] != null
            && getMapping(views[i].getCell(), false) != null) {
          CellView parentView = views[i].getParentView();
          Object parent = (parentView != null) ? parentView.getCell()
              : null;
          if (!graphModel.isPort(views[i].getCell())
              && parent == null) {
            roots.add(views[i]);
          }
        }
      }
    }
  }

  /**
   * Removes the specified model root cells from the view by removing the
   * mapping between the cell and its view and makes the cells invisible.
   */
  public CellView[] removeCells(Object[] cells) {
    if (cells != null) {
      CellView[] views = new CellView[cells.length];
      for (int i = 0; i < cells.length; i++) {
        views[i] = removeMapping(cells[i]);
        if (views[i] != null) {
          views[i].removeFromParent();
          roots.remove(views[i]);
          visibleSet.remove(views[i].getCell());
        }
      }
      return views;
    }
    return null;
  }

  //
  // Cell Mapping
  //
  /**
   * Takes an array of views and returns the array of the corresponding cells
   * by using <code>getCell</code> for each view.
   */
  public Object[] getCells(CellView[] views) {
    if (views != null) {
      Object[] cells = new Object[views.length];
      for (int i = 0; i < views.length; i++)
        if (views[i] != null)
          cells[i] = views[i].getCell();
      return cells;
    }
    return null;
  }

  /**
   * Returns the view for the specified cell. If create is true and no view is
   * found then a view is created using createView(Object).
   */
  public CellView getMapping(Object cell, boolean create) {
    if (cell == null)
      return null;
    CellView view = (CellView) mapping.get(cell);
    if (view == null && create && isVisible(cell)) {
      view = (CellView) hiddenMapping.get(cell);
      if (view != null) {
        putMapping(cell, view);
        hiddenMapping.remove(cell);
      } else {
        view = factory.createView(graphModel, cell);
        putMapping(cell, view);
        view.refresh(getModel(), this, true); // Create Dependent
        // Views
        view.update();
      }
    }
    return view;
  }

  /**
   * Returns the views for the specified array of cells without creating these
   * views on the fly.
   */
  public CellView[] getMapping(Object[] cells) {
    return getMapping(cells, false);
  }

  /**
   * Returns the views for the specified array of cells. Returned array may
   * contain null pointers if the respective cell is not mapped in this view
   * and <code>create</code> is <code>false</code>.
   */
  public CellView[] getMapping(Object[] cells, boolean create) {
    if (cells != null) {
      CellView[] result = new CellView[cells.length];
      for (int i = 0; i < cells.length; i++)
        result[i] = getMapping(cells[i], create);
      return result;
    }
    return null;
  }

  /**
   * Associates the specified model cell with the specified view.
   */
  public void putMapping(Object cell, CellView view) {
    if (cell != null && view != null)
      mapping.put(cell, view);
  }

  /**
   * Removes the associaten for the specified model cell and returns the view
   * that was previously associated with the cell. Updates the portlist if
   * necessary.
   */
  public CellView removeMapping(Object cell) {
    if (cell != null) {
      CellView view = (CellView) mapping.remove(cell);
      return view;
    }
    return null;
  }

  //
  // Partial View
  //
  // Null is always visible!
  public boolean isVisible(Object cell) {
    return !isPartial() || visibleSet.contains(cell) || cell == null;
  }

  public Set getVisibleSet() {
    return new HashSet(visibleSet);
  }

  public void setVisibleSet(Set visible) {
    visibleSet = visible;
  }

  public void setVisible(Object cell, boolean visible) {
    setVisible(new Object[] { cell }, visible);
  }

  public void setVisible(Object[] cells, boolean visible) {
    if (visible)
      setVisible(cells, null);
    else
      setVisible(null, cells);
  }

  public void setVisible(Object[] visible, Object[] invisible) {
    setVisible(visible, invisible, null);
  }

  public void setVisible(Object[] visible, Object[] invisible,
      ConnectionSet cs) {
    GraphLayoutCacheEdit edit = new GraphLayoutCacheEdit(null, null,
        visible, invisible);
    edit.end();
    graphModel.edit(null, cs, null, new UndoableEdit[] { edit });
  }

  // This is used to augment the array passed to the setVisible method.
  protected Object[] addVisibleDependencies(Object[] cells, boolean visible) {
    if (cells != null) {
      if (visible) {
        // Make ports and source and target vertex visible
        Set all = new HashSet();
        for (int i = 0; i < cells.length; i++) {
          all.add(cells[i]);
          // Add ports
          all.addAll(getPorts(cells[i]));
          // Add source vertex and ports
          Collection coll = getParentPorts(graphModel
              .getSource(cells[i]));
          if (coll != null)
            all.addAll(coll);
          // Add target vertex and ports
          coll = getParentPorts(graphModel.getTarget(cells[i]));
          if (coll != null)
            all.addAll(coll);
        }
        if (showsExistingConnections) {
          Set tmp = DefaultGraphModel.getEdges(getModel(), cells);
          Iterator it = tmp.iterator();
          while (it.hasNext()) {
            Object obj = it.next();
            Object source = graphModel.getSource(obj);
            Object target = graphModel.getTarget(obj);
            if ((isVisible(source) || all.contains(source))
                && (isVisible(target) || all.contains(target)))
              all.add(obj);
          }
        }
        all.removeAll(visibleSet);
        all.remove(null);
        return all.toArray();
      } else {
        if (hidesExistingConnections) {
          Set all = new HashSet();
          for (int i = 0; i < cells.length; i++) {
            all.addAll(getPorts(cells[i]));
            all.add(cells[i]);
          }
          Iterator it = DefaultGraphModel.getEdges(graphModel, cells)
              .iterator();
          while (it.hasNext()) {
            Object edge = it.next();
            Object newSource = graphModel.getSource(edge);
            Object newTarget = graphModel.getTarget(edge);
            // Note: At this time the cells are not yet hidden
            if ((newSource != null && !hasVisibleParent(newSource,
                all))
                || (newTarget != null && !hasVisibleParent(
                    newTarget, all))) {
              all.add(edge);
            }
          }
          all.remove(null);
          return all.toArray();
        }
      }
    }
    return cells;
  }

  // You must call update ports if this method returns true.
  public boolean setVisibleImpl(Object[] cells, boolean visible) {
    cells = addVisibleDependencies(cells, visible);
    if (cells != null && isPartial()) {
      boolean updatePorts = false;
      // Update Visible Set
      CellView[] views = new CellView[cells.length];
      if (!visible)
        views = removeCells(cells);
      for (int i = 0; i < cells.length; i++) {
        if (cells[i] != null) {
          if (visible) {
            visibleSet.add(cells[i]);
            views[i] = getMapping(cells[i], true);
          } else {
            if (views[i] != null) {
              if (graphModel.contains(views[i].getCell())
                  && remembersCellViews)
                hiddenMapping.put(views[i].getCell(), views[i]);
              updatePorts = true;
            }
          }
        }
      }
      // Make Cell Views Visible (if not already in place)
      if (visible) {
        Set parentSet = new HashSet();
        for (int i = 0; i < views.length; i++) {
          if (views[i] != null) {
            CellView view = views[i];
            // Remove all children from roots
            CellView[] children = AbstractCellView
                .getDescendantViews(new CellView[] { view });
            for (int j = 0; j < children.length; j++)
              roots.remove(children[j]);
            view.refresh(getModel(), this, false);
            // Link cellView into graphLayoutCache
            CellView parentView = view.getParentView();
            if (parentView != null)
              parentSet.add(parentView);
            updatePorts = true;
          }
        }
        CellView[] parentViews = new CellView[parentSet.size()];
        parentSet.toArray(parentViews);
        refresh(parentViews, true);
      }
      return updatePorts;
    }
    return false;
  }

  protected Collection getParentPorts(Object cell) {
    // does nothing if a parent is already visible
    Object parent = graphModel.getParent(cell);
    while (parent != null) {
      if (isVisible(parent))
        return null;
      parent = graphModel.getParent(parent);
    }

    // Else returns the parent and all ports
    parent = graphModel.getParent(cell);
    Collection collection = getPorts(parent);
    collection.add(parent);
    return collection;
  }

  protected Collection getPorts(Object cell) {
    LinkedList list = new LinkedList();
    for (int i = 0; i < graphModel.getChildCount(cell); i++) {
      Object child = graphModel.getChild(cell, i);
      if (graphModel.isPort(child))
        list.add(child);
    }
    return list;
  }

  //
  // Change Support
  //
  public boolean isPartial() {
    return partial;
  }

  /**
   * Messaged when the user has altered the value for the item identified by
   * cell to newValue. If newValue signifies a truly new value the model
   * should post a graphCellsChanged event. This calls
   * augmentNestedMapForValueChange.
   */
  public void valueForCellChanged(Object cell, Object newValue) {
    Map nested = null;
    if (isAutoSizeOnValueChange()) {
      CellView view = getMapping(cell, false);
      if (view != null) {
        AttributeMap attrs = view.getAllAttributes();
        Rectangle2D bounds = GraphConstants.getBounds(attrs);
        // Force the model to store the old bounds
        Rectangle2D dummyBounds = attrs.createRect(bounds.getX(),
            bounds.getY(), 0, 0);
        nested = GraphConstants.createAttributes(new Object[] { cell },
            new Object[] { GraphConstants.RESIZE,
                GraphConstants.BOUNDS }, new Object[] {
                Boolean.TRUE, dummyBounds });
      }
    } else {
      nested = new Hashtable();
      nested.put(cell, new Hashtable());
    }
    augmentNestedMapForValueChange(nested, cell, newValue);
    edit(nested, null, null, null);
  }

  /**
   * Hook for subclassers to add more stuff for value changes. Currently this
   * adds the new value to the change.
   */
  protected void augmentNestedMapForValueChange(Map nested, Object cell,
      Object newValue) {
    Map attrs = (Map) nested.get(cell);
    if (attrs != null)
      GraphConstants.setValue(attrs, newValue);
  }

  /**
   * Inserts the <code>cells</code> and connections into the model, and
   * absorbs the local attributes. This implementation sets the inserted cells
   * visible and selects the new roots depending on graph.selectNewCells.
   */
  public void insert(Object[] roots, Map attributes, ConnectionSet cs,
      ParentMap pm, UndoableEdit[] e) {
    Object[] visible = null;
    if (isPartial() && showsInsertedCells) {
      List tmp = DefaultGraphModel.getDescendants(graphModel, roots);
      tmp.removeAll(visibleSet);
      if (!tmp.isEmpty())
        visible = tmp.toArray();
    }
    // Absorb local attributes
    GraphLayoutCacheEdit edit = createLocalEdit(roots, attributes, visible,
        null);
    if (edit != null)
      e = augment(e, edit);
    graphModel.insert(roots, attributes, cs, pm, e);
  }

  /**
   * Inserts the cloned cells from the clone map and clones the passed-in
   * arguments according to the clone map before insertion and returns the
   * clones in order of the cells. This example shows how to clone the current
   * selection and get a reference to the clones:
   *
   * <pre>
   * Object[] cells = graph.getDescendants(graph.order(graph.getSelectionCells()));
   * ConnectionSet cs = ConnectionSet.create(graphModel, cells, false);
   * ParentMap pm = ParentMap.create(graphModel, cells, false, true);
   * cells = graphLayoutCache.insertClones(cells, graph.cloneCells(cells),
   *     attributes, cs, pm, 0, 0);
   * </pre>
   */
  public Object[] insertClones(Object[] cells, Map clones, Map nested,
      ConnectionSet cs, ParentMap pm, double dx, double dy) {
    if (cells != null) {
      if (cs != null)
        cs = cs.clone(clones);
      if (pm != null)
        pm = pm.clone(clones);
      if (nested != null) {
        nested = GraphConstants.replaceKeys(clones, nested);
        AttributeMap.translate(nested.values(), dx, dy);
      }
      // Replace cells in order
      Object[] newCells = new Object[cells.length];
      for (int i = 0; i < cells.length; i++)
        newCells[i] = clones.get(cells[i]);
      // Insert into cache/model
      insert(newCells, nested, cs, pm, null);
      return newCells;
    }
    return null;
  }

  /**
   * Inserts the specified vertex into the graph model. This method does in
   * fact nothing, it calls insert edge with the vertex and the source and
   * target port set to null. This example shows how to add a vertex with a
   * port and a black border:
   *
   * <pre>
   * DefaultGraphCell vertex = new DefaultGraphCell(&quot;Hello, world!&quot;);
   * Map attrs = vertex.getAttributes();
   * GraphConstants.setOpaque(attrs, false);
   * GraphConstants.setBorderColor(attrs, Color.black);
   * DefaultPort port = new DefaultPort();
   * vertex.add(port);
   * port.setParent(vertex);
   * graph.getGraphLayoutCache().insert(vertex);
   * </pre>
   *
   * @param cell
   *            inserts the specified cell in the cache
   */
  public void insert(Object cell) {
    insert(new Object[] { cell });
  }

  /**
   * Inserts the specified edge into the graph model. This method does in fact
   * nothing, it calls insert with a default connection set.
   *
   * @param edge
   *            the edge to be inserted
   * @param source
   *            the source port this edge is connected to
   * @param target
   *            the target port this edge is connected to
   */
  public void insertEdge(Object edge, Object source, Object target) {
    insert(new Object[] { edge }, new Hashtable(), new ConnectionSet(edge,
        source, target), new ParentMap());
  }

  /**
   * Inserts the specified cells into the graph model. This method is a
   * general implementation of cell insertion. If the source and target port
   * are null, then no connection set is created. The method uses the
   * attributes from the specified edge and the egdge's children to construct
   * the insert call. This example shows how to insert an edge with a special
   * arrow between two known vertices:
   *
   * <pre>
   * Object source = graph.getDefaultPortForCell(sourceVertex).getCell();
   * Object target = graph.getDefaultPortForCell(targetVertex).getCell();
   * DefaultEdge edge = new DefaultEdge(&quot;Hello, world!&quot;);
   * edge.setSource(source);
   * edge.setTarget(target);
   * Map attrs = edge.getAttributes();
   * GraphConstants.setLineEnd(attrs, GraphConstants.ARROW_TECHNICAL);
   * graph.getGraphLayoutCache().insert(edge);
   * </pre>
   */
  public void insert(Object[] cells) {
    insert(cells, new Hashtable(), new ConnectionSet(), new ParentMap());
  }

  /**
   * Variant of the insert method that allows to pass a default connection set
   * and parent map and nested map.
   */
  public void insert(Object[] cells, Map nested, ConnectionSet cs,
      ParentMap pm) {
    if (cells != null) {
      if (nested == null)
        nested = new Hashtable();
      if (cs == null)
        cs = new ConnectionSet();
      if (pm == null)
        pm = new ParentMap();
      for (int i = 0; i < cells.length; i++) {
        // Using the children of the vertex we construct the parent map.
        int childCount = getModel().getChildCount(cells[i]);
        for (int j = 0; j < childCount; j++) {
          Object child = getModel().getChild(cells[i], j);
          pm.addEntry(child, cells[i]);

          // And add their attributes to the nested map
          AttributeMap attrs = getModel().getAttributes(child);
          if (attrs != null)
            nested.put(child, attrs);
        }

        // A nested map with the vertex as key
        // and its attributes as the value
        // is required for the model.
        Map attrsTmp = (Map) nested.get(cells[i]);
        Map attrs = getModel().getAttributes(cells[i]);
        if (attrsTmp != null)
          attrs.putAll(attrsTmp);
        nested.put(cells[i], attrs);

        // Check if we have parameters for a connection set.
        Object sourcePort = getModel().getSource(cells[i]);
        if (sourcePort != null)
          cs.connect(cells[i], sourcePort, true);

        Object targetPort = getModel().getTarget(cells[i]);
        if (targetPort != null)
          cs.connect(cells[i], targetPort, false);
      }
      // Create an array with the parent and its children.
      cells = DefaultGraphModel.getDescendants(getModel(), cells)
          .toArray();

      // Finally call the insert method on the parent class.
      insert(cells, nested, cs, pm, null);
    }
  }

  /**
   * Inserts the specified cell as a parent of children. Note: All cells that
   * are not yet in the model will be inserted. This example shows how to
   * group the current selection and pass the group default bounds in case it
   * is later collapsed:
   *
   * <pre>
   * DefaultGraphCell group = new DefaultGraphCell(&quot;Hello, world!&quot;);
   * Object[] cells = DefaultGraphModel.order(graph.getModel(), graph
   *     .getSelectionCells());
   * Rectangle2D bounds = graph.getCellBounds(cells);
   * if (bounds != null) {
   *   bounds = new Rectangle2D.Double(bounds.getX() + bounds.getWidth() / 4,
   *       bounds.getY() + bounds.getHeight() / 4, bounds.getWidth() / 2,
   *       bounds.getHeight() / 2);
   *   GraphConstants.setBounds(group.getAttributes(), bounds);
   * }
   * graph.getGraphLayoutCache().insertGroup(group, cells);
   * </pre>
   */
  public void insertGroup(Object group, Object[] children) {
    if (group != null && children != null && children.length > 0) {
      Map nested = new Hashtable();

      // List to store all children that are not in the model
      List newCells = new ArrayList(children.length + 1);

      // Plus the group cell at pos 0
      if (!getModel().contains(group)) {
        newCells.add(group);
      }

      // Create a parent map for the group and the children, and
      // store the children's attributes in the nested map.
      // Note: This implementation assumes that the children have
      // not yet been added to the group object. Therefore,
      // the insert method will only collect the group
      // attributes, but will ignore the child attributes.
      ParentMap pm = new ParentMap();
      for (int i = 0; i < children.length; i++) {
        pm.addEntry(children[i], group);
        if (!getModel().contains(children[i])) {
          newCells.add(children[i]);
          AttributeMap attrs = getModel().getAttributes(children[i]);
          if (attrs != null)
            nested.put(children[i], attrs);
        }
      }
      if (newCells.isEmpty())
        edit(nested, null, pm, null);
      else
        insert(newCells.toArray(), nested, null, pm);
    }
  }

  /**
   * Removes <code>cells</code> from the model.
   */
  public void remove(Object[] cells) {
    graphModel.remove(cells);
  }

  /**
   * Removes cells from the model, including all children and connected edges
   * if <code>children</code> or <code>edges</code> is true, respectively.
   *
   * @param cells
   *            The cells to remove.
   * @param descendants
   *            Whether to remove all descendants as well.
   * @param edges
   *            Whether to remove all connected edges as well.
   */
  public void remove(Object[] cells, boolean descendants, boolean edges) {
    if (cells != null && cells.length > 0) {
      if (edges) {
        Object[] tmp = DefaultGraphModel.getEdges(getModel(), cells)
            .toArray();
        Object[] newCells = new Object[cells.length + tmp.length];
        System.arraycopy(cells, 0, newCells, 0, cells.length);
        System.arraycopy(tmp, 0, newCells, cells.length, tmp.length);
        cells = newCells;
      }
      if (descendants)
        cells = DefaultGraphModel.getDescendants(getModel(), cells)
            .toArray();
      remove(cells);
    }
  }

  /**
   * Hides the specified cells with all children if <code>descandants</code>
   * is true.
   */
  public void hideCells(Object[] cells, boolean descandants) {
    if (cells != null && cells.length > 0) {
      if (descandants)
        cells = DefaultGraphModel.getDescendants(getModel(), cells)
            .toArray();
      setVisible(cells, false);
    }
  }

  /**
   * Shows the specified cells with all children if <code>descandants</code>
   * is true.
   */
  public void showCells(Object[] cells, boolean descandants) {
    if (cells != null && cells.length > 0) {
      if (descandants)
        cells = DefaultGraphModel.getDescendants(getModel(), cells)
            .toArray();
      setVisible(cells, true);
    }
  }
 
  /**
   * Ungroups all groups in cells and returns the children that are not ports.
   * Note: This replaces the parents with their group cells in the group
   * structure.
   */
  public Object[] ungroup(Object[] cells) {
    if (cells != null && cells.length > 0) {
      ArrayList toRemove = new ArrayList();
      ArrayList children = new ArrayList();
      boolean groupExists = false;
      for (int i = 0; i < cells.length; i++) {
        boolean childExists = false;
        for (int j = 0; j < getModel().getChildCount(cells[i]); j++) {
          Object child = getModel().getChild(cells[i], j);
          if (!getModel().isPort(child)) {
            children.add(child);
            childExists = true;
          } else {
            toRemove.add(child);
          }
        }
        if (childExists) {
          toRemove.add(cells[i]);
          groupExists = true;
        }
      }
      if (groupExists)
        remove(toRemove.toArray());
      return children.toArray();
    }
    return null;
  }

  /**
   * Toggles the collapsed state of the specified cells.
   *
   * @param cells
   *            The cells to toggle the collapsed state for.
   * @param collapseOnly
   *            Whether cells should only be collapsed.
   * @param expandOnly
   *            Whether cells should only be expanded.
   *
   */
  public void toggleCollapsedState(Object[] cells, boolean collapseOnly,
      boolean expandOnly) {
    List toExpand = new ArrayList();
    List toCollapse = new ArrayList();
    for (int i = 0; i < cells.length; i++) {
      Object cell = cells[i];
      CellView view = getMapping(cell, false);
      if (view != null) {

        // Adds to list of expansion cells if it is a leaf in the layout
        // cache and we do not only want to collapse.
        if (view.isLeaf() && !collapseOnly)
          toExpand.add(view.getCell());

        // Else adds to list of to-be-collapsed cells if it is not a
        // leaf in the layout cache we do not only want to expand.
        else if (!view.isLeaf() && !expandOnly)
          toCollapse.add(view.getCell());
      }
    }
    if (!toCollapse.isEmpty() || !toExpand.isEmpty())
      setCollapsedState(toCollapse.toArray(), toExpand.toArray());
  }

  /**
   * Collapses all groups by hiding all their descendants.
   *
   * @param groups
   */
  public void collapse(Object[] groups) {
    setCollapsedState(groups, null);
  }

  /**
   * Expands all groups by showing all children. (Note: This does not show all
   * descandants, but only the first generation of children.)
   */
  public void expand(Object[] cells) {
    setCollapsedState(null, cells);
  }

  /**
   * Collapses and/or expands the specified cell(s)
   *
   * @param collapse
   *            the cells to be collapsed
   * @param expand
   *            the cells to be expanded
   */
  public void setCollapsedState(Object[] collapse, Object[] expand) {
    // Get all descandants for the groups
    ConnectionSet cs = new ConnectionSet();

    // Collapse cells
    List toHide = DefaultGraphModel.getDescendants(getModel(), collapse);
    if (collapse != null) {
      // Remove the groups themselfes
      for (int i = 0; i < collapse.length; i++) {
        toHide.remove(collapse[i]);
        cellWillCollapse(collapse[i]);
      }
      // Remove the ports (will be hidden automatically)
      for (int i = 0; i < collapse.length; i++) {
        int childCount = getModel().getChildCount(collapse[i]);
        if (childCount > 0) {
          for (int j = 0; j < childCount; j++) {
            Object child = getModel().getChild(collapse[i], j);
            if (getModel().isPort(child)) {
              toHide.remove(child);
            }
          }
        }
      }
    }

    // Expand cells
    Set toShow = new HashSet();
    if (expand != null) {
      for (int i = 0; i < expand.length; i++) {
        int childCount = getModel().getChildCount(expand[i]);
        for (int j = 0; j < childCount; j++) {
          toShow.add(getModel().getChild(expand[i], j));
        }
      }
    }
    setVisible(toShow.toArray(),
        (toHide != null) ? toHide.toArray() : null, cs);
  }

  /**
   * Hook for subclassers to return the first or last visible port to replace
   * the current source or target port of the edge. This is called when groups
   * are collapsed for the edges that cross the group, ie. go from a child
   * cell to a cell which is outside the group. This implementation returns
   * the first port of the parent group if source is true, otherwise it
   * returns the last port of the parent group.
   */
  protected Object getParentPort(Object edge, boolean source) {
    // Contains the parent of the parent vertex, eg. the group
    Object parent = getModel().getParent(
        (source) ? DefaultGraphModel.getSourceVertex(getModel(), edge)
            : DefaultGraphModel.getTargetVertex(getModel(), edge));
    // Finds a port in the group
    int c = getModel().getChildCount(parent);
    for (int i = (source) ? c - 1 : 0; i < getModel().getChildCount(parent)
        && i >= 0; i += (source) ? -1 : +1) {
      Object child = getModel().getChild(parent, i);
      if (getModel().isPort(child)) {
        return child;
      }
    }
    return null;
  }

  /**
   * Hook for subclassers to return the port to be used for edges that have
   * been connected to the group. This is called from expand. This returns the
   * first port of the first or last vertex depending on <code>source</code>.
   */
  protected Object getChildPort(Object edge, boolean source) {
    GraphModel model = getModel();
    // Contains the parent of the port, eg. the group
    Object parent = (source) ? DefaultGraphModel.getSourceVertex(model,
        edge) : DefaultGraphModel.getTargetVertex(model, edge);
    // Finds a vertex in the group
    int c = model.getChildCount(parent);
    for (int i = (source) ? c - 1 : 0; i < c && i >= 0; i += (source) ? -1
        : +1) {
      Object child = model.getChild(parent, i);
      if (!model.isEdge(child) && !model.isPort(child)) {
        // Finds a port in the vertex
        for (int j = 0; j < model.getChildCount(child); j++) {
          Object port = model.getChild(child, j);
          if (model.isPort(port)) {
            return port;
          }
        }
      }
    }
    return null;
  }

  /**
   * Applies the <code>propertyMap</code> and the connection changes to the
   * model. The initial <code>edits</code> that triggered the call are
   * considered to be part of this transaction. Notifies the model- and undo
   * listeners of the change. Note: The passed in attributes may contain
   * PortViews.
   */
  public void edit(Map attributes, ConnectionSet cs, ParentMap pm,
      UndoableEdit[] e) {
    if (attributes != null || cs != null || pm != null || e != null) {
      Object[] visible = null;
      if (isPartial() && showsInvisibleEditedCells) {
        Set tmp = new HashSet();
        if (attributes != null)
          tmp.addAll(attributes.keySet());
        if (cs != null)
          tmp.addAll(cs.getChangedEdges());
        if (pm != null)
          tmp.addAll(pm.getChangedNodes());
        tmp.removeAll(visibleSet);
        if (!tmp.isEmpty())
          visible = tmp.toArray();
      }
      GraphLayoutCacheEdit edit = createLocalEdit(null, attributes,
          visible, null);
      if (edit != null)
        e = augment(e, edit);
      // Pass to model
      graphModel.edit(attributes, cs, pm, e);
    }
  }

  /**
   * A shortcut method that takes a nested map and passes it to the edit
   * method.
   */
  public void edit(Map attributes) {
    edit(attributes, null, null, null);
  }

  /**
   * Applies the <code>attributes</code> to all <code>cells</code> by
   * creating a map that contains the attributes for each cell and passing it
   * to edit on this layout cache. Example:
   *
   * <pre>
   * Map attrs = new java.util.Hashtable();
   * GraphConstants.setBackground(attrs, Color.RED);
   * graph.getGraphLayoutCache().edit(graph.getSelectionCells(), attrs);
   * </pre>
   */
  public void edit(Object[] cells, Map attributes) {
    if (attributes != null && cells != null && cells.length > 0) {
      Map nested = new Hashtable();
      for (int i = 0; i < cells.length; i++)
        nested.put(cells[i], attributes);
      edit(nested, null, null, null);
    }
  }

  /**
   * Applies the <code>attributes</code> to a single <code>cell</code> by
   * creating a map that contains the attributes for this cell and passing it
   * to edit on this layout cache. Example:
   *
   * <pre>
   * Map attrs = new java.util.Hashtable();
   * GraphConstants.setBackground(attrs, Color.RED);
   * graph.getGraphLayoutCache().editCell(graph.getSelectionCell(), attrs);
   * </pre>
   */
  public void editCell(Object cell, Map attributes) {
    if (attributes != null && cell != null) {
      edit(new Object[] { cell }, attributes);
    }
  }

  protected UndoableEdit[] augment(UndoableEdit[] e, UndoableEdit edit) {
    if (edit != null) {
      int size = (e != null) ? e.length + 1 : 1;
      UndoableEdit[] result = new UndoableEdit[size];
      if (e != null)
        System.arraycopy(e, 0, result, 0, size - 2);
      result[size - 1] = edit;
      return result;
    }
    return e;
  }

  /**
   * Sends <code>cells</code> to back. Note: This expects an array of cells!
   */
  public void toBack(Object[] cells) {
    if (cells != null && cells.length > 0) {
      graphModel.toBack(cells);
    }
  }

  /**
   * Brings <code>cells</code> to front. Note: This expects an array of
   * cells!
   */
  public void toFront(Object[] cells) {
    if (cells != null && cells.length > 0) {
      graphModel.toFront(cells);
    }
  }

  /**
   * Creates a local edit for the specified change. A local operation contains
   * all visibility changes, as well as all changes to attributes that are
   * local, and all control attributes. <br>
   * Note: You must use cells as keys for the nested map, not cell views.
   */
  protected GraphLayoutCacheEdit createLocalEdit(Object[] inserted,
      Map nested, Object[] visible, Object[] invisible) {
    // Create an edit if there are any view-local attributes set
    if ((nested != null && !nested.isEmpty())
        && (!localAttributes.isEmpty() || isAllAttributesLocal())) {
      // Move or Copy Local Attributes to Local View
      Map globalMap = new Hashtable();
      Map localMap = new Hashtable();
      Map localAttr;
      Iterator it = nested.entrySet().iterator();
      while (it.hasNext()) {
        localAttr = new Hashtable();
        Map.Entry entry = (Map.Entry) it.next();
        // (cell, Hashtable)
        Object cell = entry.getKey();
        Map attr = (Map) entry.getValue();
        // Create Difference of Existing and New Attributes
        CellView tmpView = getMapping(cell, false);
        if (tmpView != null)
          attr = tmpView.getAllAttributes().diff(attr);
        // End of Diff
        Iterator it2 = attr.entrySet().iterator();
        while (it2.hasNext()) {
          Map.Entry entry2 = (Map.Entry) it2.next();
          // (key, value)
          Object key = entry2.getKey();
          Object value = entry2.getValue();
          boolean isControlAttribute = isControlAttribute(cell, key,
              value);
          if (isAllAttributesLocal() || isControlAttribute
              || isLocalAttribute(cell, key, value)) {
            localAttr.put(key, value);
            if (!isControlAttribute)
              it2.remove();
          }
        }
        if (!localAttr.isEmpty())
          localMap.put(cell, localAttr);
        if (!attr.isEmpty())
          globalMap.put(cell, attr);
      }
      nested.clear();
      nested.putAll(globalMap);
      if (visible != null || invisible != null || !localMap.isEmpty()) {
        GraphLayoutCacheEdit edit = new GraphLayoutCacheEdit(inserted,
            new Hashtable(localMap), visible, invisible);
        edit.end();
        return edit;
      }
    } else if (visible != null || invisible != null) {
      GraphLayoutCacheEdit edit = new GraphLayoutCacheEdit(inserted,
          null, visible, invisible);
      edit.end();
      return edit;
    }
    return null;
  }

  /**
   * Returns true if the set of local attributes contains <code>key</code>
   */
  protected boolean isLocalAttribute(Object cell, Object key, Object value) {
    return localAttributes.contains(key);
  }

  /**
   * Returns true if <code>key</code> is a control attribute
   */
  protected boolean isControlAttribute(Object cell, Object key, Object value) {
    return GraphConstants.REMOVEALL.equals(key)
        || GraphConstants.REMOVEATTRIBUTES.equals(key);
  }

  /**
   * An implementation of GraphViewChange.
   */
  public class GraphLayoutCacheEdit extends CompoundEdit implements
      GraphLayoutCacheEvent.GraphLayoutCacheChange {

    protected Object[] cells, previousCells = null;

    protected CellView[] context, hidden;

    protected Map attributes, previousAttributes;

    protected Object[] visible, invisible;

    // Remember which cells have changed for finding their context
    protected Set changedCells = new HashSet();

    /**
     * Constructs a GraphViewEdit. This modifies the attributes of the
     * specified views and may be used to notify UndoListeners.
     *
     * @param nested
     *            the map that defines the new attributes
     */
    public GraphLayoutCacheEdit(Map nested) {
      this(null, nested, null, null);
      attributes = nested;
    }

    /**
     * Constructs a GraphViewEdit. This modifies the attributes of the
     * specified views and may be used to notify UndoListeners. This should
     * also take an array of removed cell views, but it is not possible to
     * add further UndoableEdits to an already executed CompoundEdit, such
     * as a GraphModel change. Thus, to handle implicit changes -- rather
     * than piggybacking on the model's event -- the CompoundEdit's addEdit
     * method should be extended to accept and instantly execute sub-
     * sequent edits (implicit changes to the view, such as removing a
     * mapping, hiding a view or the like).
     *
     * @param inserted
     *            an array of inserted cells
     * @param attributes
     *            the map that defines the new attributes
     * @param visible
     *            an array defining which cells are visible
     * @param invisible
     *            an array defining which cells are invisible
     */
    public GraphLayoutCacheEdit(Object[] inserted, Map attributes,
        Object[] visible, Object[] invisible) {
      super();
      this.attributes = attributes;
      this.previousAttributes = attributes;
      this.cells = inserted;
      this.visible = visible;
      this.invisible = invisible;
    }

    public Object getSource() {
      return GraphLayoutCache.this;
    }

    public boolean isSignificant() {
      return true;
    }

    /**
     * Returns the cell views that have changed.
     */
    public Object[] getChanged() {
      return changedCells.toArray();
    }

    /**
     * Returns the cells that habe been made visible.
     */
    public Object[] getInserted() {
      return invisible;
    }

    /**
     * Returns the cells that have changed.
     */
    public Object[] getRemoved() {
      return visible;
    }

    /**
     * Returns the views that have not changed explicitly, but implicitly
     * because one of their dependent cells has changed.
     */
    public Object[] getContext() {
      return context;
    }

    /**
     * Returns a map of (cell view, attribute) pairs.
     */
    public Map getAttributes() {
      return attributes;
    }

    /**
     * Returns a map of (cell view, attribute) pairs.
     */
    public Map getPreviousAttributes() {
      return previousAttributes;
    }

    /**
     * Redoes a change.
     *
     * @exception CannotRedoException
     *                if the change cannot be redone
     */
    public void redo() throws CannotRedoException {
      super.redo();
      execute();
    }

    /**
     * Undoes a change.
     *
     * @exception CannotUndoException
     *                if the change cannot be undone
     */
    public void undo() throws CannotUndoException {
      super.undo();
      execute();
    }

    /**
     * Execute this edit such that the next invocation to this method will
     * invert the last execution.
     */
    public void execute() {
      GraphModel model = getModel();
      changedCells.clear();
      // Remember or restore hidden cells
      if (hidden != null)
        for (int i = 0; i < hidden.length; i++)
          if (hidden[i] != null)
            mapping.put(hidden[i].getCell(), hidden[i]);
      if (!remembersCellViews) // already remembered
        hidden = getMapping(invisible);
      // Handle visibility
      boolean updatePorts = setVisibleImpl(visible, true)
          | setVisibleImpl(invisible, false);
      if (visible != null) {
        for (int i = 0; i < visible.length; i++) {
          changedCells.add(visible[i]);

          // Only calls if not inserted
          if (cells == null)
            cellExpanded(visible[i]);
        }
      }
      if (invisible != null)
        for (int i = 0; i < invisible.length; i++)
          changedCells.add(invisible[i]);
      // Swap arrays
      Object[] tmp = visible;
      visible = invisible;
      invisible = tmp;
      // Handle attributes
      if (attributes != null) {
        previousAttributes = attributes;
        changedCells.addAll(attributes.keySet());
        attributes = handleAttributes(attributes);
      }
      if (updatePorts)
        updatePorts();
      // Add ancestor cells to changed cells
      Set parentSet = new HashSet();
      Iterator it = changedCells.iterator();
      while (it.hasNext()) {
        Object parent = model.getParent(it.next());
        while (parent != null) {
          parentSet.add(parent);
          parent = model.getParent(parent);
        }
      }
      changedCells.addAll(parentSet);
      // Refresh all changed cells
      refresh(getMapping(changedCells.toArray(), false), false);
      // Updates the connected edges. Make sure that changedCells
      // contains no edges, as these will be removed from the result.
      Set ctx = DefaultGraphModel.getEdges(getModel(), changedCells
          .toArray());
      context = getMapping(ctx.toArray());
      refresh(context, false);
      tmp = cells;
      cells = previousCells;
      previousCells = tmp;
      reloadRoots();
      fireGraphLayoutCacheChanged(GraphLayoutCache.this, this);
    }
  }

  /**
   * Called when a child has been made visible by expanding its parent. This
   * implementation translates the child so that it reflects the offset of the
   * parent group since the child was last visible (see
   * {@link #movesChildrenOnExpand}).
   */
  protected void cellExpanded(Object cell) {
    GraphModel model = getModel();
    // Moves the child to the group origin if it is not a port
    if (movesChildrenOnExpand && !model.isPort(cell)) {
      CellView view = getMapping(cell, false);

      if (view != null) {
        CellView parent = getMapping(model.getParent(cell), false);
        if (parent != null) {
          if (parent instanceof VertexView) {

            // Computes the offset of the parent group
            VertexView vertex = (VertexView) parent;
            Rectangle2D src = GraphConstants.getBounds(parent
                .getAllAttributes());
            Rectangle2D rect = vertex.getBounds();
            double dx = src.getX() - rect.getX();
            double dy = src.getY() - rect.getY();

            // Gets the attributes from the cell view or
            // cell and translates the bounds or points
            AttributeMap attrs = view.getAttributes();
            if (!attrs.contains(GraphConstants.BOUNDS))
              attrs = model.getAttributes(view.getCell());
            attrs.translate(dx, dy);
          }
        }
      }
    }
  }

  protected void cellWillCollapse(Object cell) {
    GraphModel model = getModel();
    if (movesParentsOnCollapse) {
      CellView view = getMapping(cell, false);
      if (view != null && !view.isLeaf()) {
        // Uses view-local attribute if available
        AttributeMap attrs = view.getAttributes();
        if (!attrs.contains(GraphConstants.BOUNDS)
            && !localAttributes.contains(GraphConstants.BOUNDS))
          attrs = model.getAttributes(cell);

        // Moves the group to the origin of the children
        Rectangle2D src = GraphConstants.getBounds(attrs);
        Rectangle2D b = (Rectangle2D) view.getBounds();
        // FIXME: What if the group is exactly at "defaultBounds"?
        if (resizesParentsOnCollapse || src == null
            || src.equals(VertexView.defaultBounds)) {
          src = attrs.createRect(b.getX(), b.getY(), b.getWidth()
              * collapseXScale, b.getHeight() * collapseYScale);
          attrs.applyValue(GraphConstants.BOUNDS, src);
        } else {
          src.setFrame(b.getX(), b.getY(), src.getWidth(), src
              .getHeight());
        }
      }
    }
  }

  /**
   * Attention: Undo will not work for routing-change if ROUTING and POINTS
   * are stored in different locations. This happens if the model holds the
   * routing attribute and the routing changes from unrouted to routed. In
   * this case the points in the view are already routed according to the new
   * scheme when written to the command history (-> no undo).
   */
  protected Map handleAttributes(Map attributes) {
    Map undo = new Hashtable();
    CellView[] views = new CellView[attributes.size()];
    Iterator it = attributes.entrySet().iterator();
    int i = 0;
    while (it.hasNext()) {
      Map.Entry entry = (Map.Entry) it.next();
      CellView cv = getMapping(entry.getKey(), false);
      views[i] = cv;
      i += 1;
      if (cv != null && cv.getAttributes() != null) {
        Map deltaNew = (Map) entry.getValue();
        // System.out.println("state=" + cv.getAttributes());
        // System.out.println("change=" + deltaNew);
        Map deltaOld = cv.getAttributes().applyMap(deltaNew);
        cv.refresh(getModel(), this, false);
        // System.out.println("state'=" + cv.getAttributes());
        // System.out.println("change'=" + deltaOld);
        undo.put(cv.getCell(), deltaOld);
      }
    }
    // Re-route all child edges
    update(views);
    return undo;
  }

  //
  // Static Methods
  //
  /**
   * Translates the specified views by the given amount.
   *
   * @param views
   *            an array of cell view to each be translated
   * @param dx
   *            the amount to translate the views in the x-axis
   * @param dy
   *            the amount to translate the views in the x-axis
   */
  public static void translateViews(CellView[] views, double dx, double dy) {
    for (int i = 0; i < views.length; i++) {
      if (views[i] instanceof AbstractCellView) {
        ((AbstractCellView) views[i]).translate(dx, dy);
      }
    }
  }

  /**
   * Returns a collection of cells that are connected to the specified cell by
   * edges. Any cells specified in the exclude set will be ignored.
   *
   * @param cell
   *            The cell from which the neighbours will be determined
   * @param exclude
   *            The set of cells to ignore when searching
   * @param directed
   *            whether or not direction of edges should be taken into account
   * @param visibleCells
   *            whether or not to only consider visible cells
   * @return Returns the list of neighbours for <code>cell</code>
   */
  public List getNeighbours(Object cell, Set exclude, boolean directed,
      boolean visibleCells) {
    // Traverse Graph
    GraphModel model = getModel();
    Object[] fanout = (directed) ? DefaultGraphModel.getOutgoingEdges(
        model, cell) : DefaultGraphModel.getEdges(model,
        new Object[] { cell }).toArray();
    List neighbours = new ArrayList(fanout.length);
    Set localExclude = new HashSet(fanout.length + 8, (float) 0.75);
    for (int i = 0; i < fanout.length; i++) {
      Object neighbour = DefaultGraphModel.getOpposite(model, fanout[i],
          cell);
      if (neighbour != null
          && (exclude == null || !exclude.contains(neighbour))
          && !localExclude.contains(neighbour)
          && (!visibleCells || isVisible(neighbour))) {
        localExclude.add(neighbour);
        neighbours.add(neighbour);
      }
    }
    return neighbours;
  }

  /**
   * Returns the outgoing edges for cell. Cell should be a port or a vertex.
   *
   * @param cell
   *            The cell from which the outgoing edges will be determined
   * @param exclude
   *            The set of edges to ignore when searching
   * @param visibleCells
   *            whether or not only visible cells should be processed
   * @param selfLoops
   *            whether or not to include self loops in the returned list
   * @return Returns the list of outgoing edges for <code>cell</code>
   */
  public List getOutgoingEdges(Object cell, Set exclude,
      boolean visibleCells, boolean selfLoops) {
    return getEdges(cell, exclude, visibleCells, selfLoops, false);
  }

  /**
   * Returns the incoming edges for cell. Cell should be a port or a vertex.
   *
   * @param cell
   *            The cell from which the incoming edges will be determined
   * @param exclude
   *            The set of edges to ignore when searching
   * @param visibleCells
   *            whether or not only visible cells should be processed
   * @param selfLoops
   *            whether or not to include self loops in the returned list
   * @return Returns the list of incoming edges for <code>cell</code>
   */
  public List getIncomingEdges(Object cell, Set exclude,
      boolean visibleCells, boolean selfLoops) {
    return getEdges(cell, exclude, visibleCells, selfLoops, true);
  }

  /**
   * Returns the incoming or outgoing edges for cell. Cell should be a port or
   * a vertex.
   *
   * @param cell
   *            The cell from which the edges will be determined
   * @param exclude
   *            The set of edges to ignore when searching
   * @param visibleCells
   *            whether or not only visible cells should be processed
   * @param selfLoops
   *            whether or not to include self loops in the returned list
   * @param incoming
   *            <code>true</code> if incoming edges are to be obtained,
   *            <code>false</code> if outgoing edges are to be obtained
   * @return Returns the list of incoming or outgoing edges for
   *         <code>cell</code>
   */
  protected List getEdges(Object cell, Set exclude, boolean visibleCells,
      boolean selfLoops, boolean incoming) {
    GraphModel model = getModel();
    Object[] edges = DefaultGraphModel.getEdges(model, cell, incoming);

    List edgeList = new ArrayList(edges.length);
    Set localExclude = new HashSet(edges.length);
    for (int i = 0; i < edges.length; i++) {
      // Check that the edge is neiter in the passed in exclude set or
      // the local exclude set. Also, if visibleCells is true check
      // the edge is visible in the cache.
      if ((exclude == null || !exclude.contains(edges[i]))
          && !localExclude.contains(edges[i])
          && (!visibleCells || isVisible(edges[i]))) {
        // Add the edge to the list if all edges, including self loops
        // are allowed. If self loops are not allowed, ensure the
        // source and target of the edge are different
        if (selfLoops == true
            || model.getSource(edges[i]) != model
                .getTarget(edges[i])) {
          edgeList.add(edges[i]);
        }
        localExclude.add(edges[i]);
      }
    }
    return edgeList;
  }

  /**
   * Returns all views, shortcut to getAllDescendants(getRoots())
   */
  public CellView[] getAllViews() {
    return getAllDescendants(getRoots());
  }
 
  /**
   * Returns all views, including descendants that have a parent in
   * <code>views</code>, especially the PortViews. Note: Iterative
   * Implementation using model.getChild and getMapping on this cell mapper.
   */
  public CellView[] getAllDescendants(CellView[] views) {
    Stack stack = new Stack();
    for (int i = 0; i < views.length; i++)
      if (views[i] != null)
        stack.add(views[i]);
    ArrayList result = new ArrayList();
    while (!stack.isEmpty()) {
      CellView tmp = (CellView) stack.pop();
      Object[] children = tmp.getChildViews();
      for (int i = 0; i < children.length; i++)
        stack.add(children[i]);
      result.add(tmp);
      // Add Port Views
      for (int i = 0; i < graphModel.getChildCount(tmp.getCell()); i++) {
        Object child = graphModel.getChild(tmp.getCell(), i);
        if (graphModel.isPort(child)) {
          CellView view = getMapping(child, false);
          if (view != null)
            stack.add(view);
        }
      }
    }
    CellView[] ret = new CellView[result.size()];
    result.toArray(ret);
    return ret;
  }

  /**
   * Returns the hiddenMapping.
   *
   * @return Map
   */
  public Map getHiddenMapping() {
    return hiddenMapping;
  }

  /**
   * Sets the showsExistingConnections
   *
   * @param showsExistingConnections
   */
  public void setShowsExistingConnections(boolean showsExistingConnections) {
    this.showsExistingConnections = showsExistingConnections;
  }

  /**
   * Returns the showsExistingConnections.
   *
   * @return boolean
   */
  public boolean isShowsExistingConnections() {
    return showsExistingConnections;
  }

  /**
   * Sets the showsInsertedConnections
   *
   * @param showsInsertedConnections
   */
  public void setShowsInsertedConnections(boolean showsInsertedConnections) {
    this.showsInsertedConnections = showsInsertedConnections;
  }

  /**
   * Returns the showsInsertedConnections.
   *
   * @return boolean
   */
  public boolean isShowsInsertedConnections() {
    return showsInsertedConnections;
  }

  /**
   * Sets the hidesExistingConnections
   *
   * @param hidesExistingConnections
   */
  public void setHidesExistingConnections(boolean hidesExistingConnections) {
    this.hidesExistingConnections = hidesExistingConnections;
  }

  /**
   * Returns the hidesExistingConnections.
   *
   * @return boolean
   */
  public boolean isHidesExistingConnections() {
    return hidesExistingConnections;
  }

  /**
   * Sets the hidesDanglingConnections
   *
   * @param hidesDanglingConnections
   */
  public void setHidesDanglingConnections(boolean hidesDanglingConnections) {
    this.hidesDanglingConnections = hidesDanglingConnections;
  }

  /**
   * Returns the hidesDanglingConnections.
   *
   * @return boolean
   */
  public boolean isHidesDanglingConnections() {
    return hidesDanglingConnections;
  }

  /**
   * Sets the rememberCellViews.
   *
   * @param rememberCellViews
   *            The rememberCellViews to set
   */
  public void setRemembersCellViews(boolean rememberCellViews) {
    this.remembersCellViews = rememberCellViews;
  }

  /**
   * Returns the remembersCellViews.
   *
   * @return boolean
   */
  public boolean isRemembersCellViews() {
    return remembersCellViews;
  }

  /**
   * Sets the hiddenSet.
   *
   * @param hiddenSet
   *            The hiddenSet to set
   */
  public void setHiddenSet(Map hiddenSet) {
    this.hiddenMapping = hiddenSet;
  }

  /**
   * @return Returns the localAttributes.
   */
  public Set getLocalAttributes() {
    return localAttributes;
  }

  /**
   * @param localAttributes
   *            The localAttributes to set.
   */
  public void setLocalAttributes(Set localAttributes) {
    this.localAttributes = localAttributes;
  }

  /**
   * @return Returns the askLocalAttribute.
   */
  public boolean isAllAttributesLocal() {
    return allAttributesLocal;
  }

  /**
   * @param allAttributesLocal
   *            The allAttributesLocal to set.
   */
  public void setAllAttributesLocal(boolean allAttributesLocal) {
    this.allAttributesLocal = allAttributesLocal;
  }

  /**
   * Returns true if cells should be auto-sized when their values change
   *
   * @return true if cells should be auto-sized when their values change
   */
  public boolean isAutoSizeOnValueChange() {
    return autoSizeOnValueChange;
  }

  /**
   * Determines whether cells should be auto-sized when their values change.
   * Fires a property change event if the new setting is different from the
   * existing setting.
   *
   * @param flag
   *            a boolean value, true if cells should be auto-sized when their
   *            values change
   */
  public void setAutoSizeOnValueChange(boolean flag) {
    this.autoSizeOnValueChange = flag;
  }

  /**
   * @return Returns the selectsAllInsertedCells.
   */
  public boolean isSelectsAllInsertedCells() {
    return selectsAllInsertedCells;
  }

  /**
   * @param selectsAllInsertedCells
   *            The selectsAllInsertedCells to set.
   */
  public void setSelectsAllInsertedCells(boolean selectsAllInsertedCells) {
    this.selectsAllInsertedCells = selectsAllInsertedCells;
  }

  /**
   * @return Returns the selectsLocalInsertedCells.
   */
  public boolean isSelectsLocalInsertedCells() {
    return selectsLocalInsertedCells;
  }

  /**
   * @param selectsLocalInsertedCells
   *            The selectsLocalInsertedCells to set.
   */
  public void setSelectsLocalInsertedCells(boolean selectsLocalInsertedCells) {
    this.selectsLocalInsertedCells = selectsLocalInsertedCells;
  }

  /**
   * @return Returns the reconnectsEdgesToVisibleParent.
   * @deprecated edges are moved to parent view and back automatically
   */
  public boolean isReconnectsEdgesToVisibleParent() {
    return reconnectsEdgesToVisibleParent;
  }

  /**
   * @param reconnectsEdgesToVisibleParent
   *            The reconnectsEdgesToVisibleParent to set.
   * @deprecated edges are moved to parent view and back automatically
   */
  public void setReconnectsEdgesToVisibleParent(
      boolean reconnectsEdgesToVisibleParent) {
    this.reconnectsEdgesToVisibleParent = reconnectsEdgesToVisibleParent;
  }

  /**
   * @return Returns the showsChangedConnections.
   */
  public boolean isShowsChangedConnections() {
    return showsChangedConnections;
  }

  /**
   * @param showsChangedConnections
   *            The showsChangedConnections to set.
   */
  public void setShowsChangedConnections(boolean showsChangedConnections) {
    this.showsChangedConnections = showsChangedConnections;
  }

  /**
   * @return Returns the moveChildrenOnExpand.
   */
  public boolean isMovesChildrenOnExpand() {
    return movesChildrenOnExpand;
  }

  /**
   * @param moveChildrenOnExpand
   *            The moveChildrenOnExpand to set.
   */
  public void setMovesChildrenOnExpand(boolean moveChildrenOnExpand) {
    this.movesChildrenOnExpand = moveChildrenOnExpand;
  }

  /**
   * @return Returns the collapseXScale.
   */
  public double getCollapseXScale() {
    return collapseXScale;
  }

  /**
   * @param collapseXScale
   *            The collapseXScale to set.
   */
  public void setCollapseXScale(double collapseXScale) {
    this.collapseXScale = collapseXScale;
  }

  /**
   * @return Returns the collapseYScale.
   */
  public double getCollapseYScale() {
    return collapseYScale;
  }

  /**
   * @param collapseYScale
   *            The collapseYScale to set.
   */
  public void setCollapseYScale(double collapseYScale) {
    this.collapseYScale = collapseYScale;
  }

  /**
   * @return Returns the movesParentsOnCollapse.
   */
  public boolean isMovesParentsOnCollapse() {
    return movesParentsOnCollapse;
  }

  /**
   * @param movesParentsOnCollapse
   *            The movesParentsOnCollapse to set.
   */
  public void setMovesParentsOnCollapse(boolean movesParentsOnCollapse) {
    this.movesParentsOnCollapse = movesParentsOnCollapse;
  }

  /**
   * @return Returns the resizesParentsOnCollapse.
   */
  public boolean isResizesParentsOnCollapse() {
    return resizesParentsOnCollapse;
  }

  /**
   * @param resizesParentsOnCollapse
   *            The resizesParentsOnCollapse to set.
   */
  public void setResizesParentsOnCollapse(boolean resizesParentsOnCollapse) {
    this.resizesParentsOnCollapse = resizesParentsOnCollapse;
  }

}
TOP

Related Classes of org.jgraph.graph.GraphLayoutCache

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.