
org.jgraph.graph.DefaultGraphSelectionModel Maven / Gradle / Ivy
Show all versions of freak-core Show documentation
/*
* @(#)DefaultGraphSelectionModel.java 1.0 1/1/02
*
* Copyright (c) 2001-2004, Gaudenz Alder
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of JGraph nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package org.jgraph.graph;
import org.jgraph.JGraph;
import org.jgraph.event.GraphSelectionEvent;
import org.jgraph.event.GraphSelectionListener;
import javax.swing.event.EventListenerList;
import javax.swing.event.SwingPropertyChangeSupport;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.util.*;
/**
* Default implementation of GraphSelectionModel. Listeners are notified
*
* @version 1.0 1/1/02
* @author Gaudenz Alder
*/
public class DefaultGraphSelectionModel
implements GraphSelectionModel, Cloneable, Serializable {
/** Property name for selectionMode. */
public static final String SELECTION_MODE_PROPERTY = "selectionMode";
/** Value that represents selected state in cellStates. */
public static final int SELECTED = -1;
/** Object value that represents the unselected state in cellStates. */
public static final Integer UNSELECTED = new Integer(0);
/** Reference to the parent graph. Used to find parents and childs. */
protected JGraph graph;
/** Used to message registered listeners. */
protected SwingPropertyChangeSupport changeSupport;
/** Event listener list. */
protected EventListenerList listenerList = new EventListenerList();
/**
* Mode for the selection, will be either SINGLE_TREE_SELECTION,
* CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
*/
protected int selectionMode;
/** Boolean that indicates if the model allows stepping-into groups. */
protected boolean childrenSelectable = true;
/** Maps the cells to their selection state. */
protected Map cellStates = new Hashtable();
/** List that contains the selected items. */
protected List selection = new ArrayList();
/** Constructs a DefaultGraphSelectionModel for the specified graph. */
public DefaultGraphSelectionModel(JGraph graph) {
this.graph = graph;
}
/**
* Sets the selection mode, which must be one of SINGLE_TREE_SELECTION,
*/
public void setSelectionMode(int mode) {
int oldMode = selectionMode;
selectionMode = mode;
if (selectionMode != GraphSelectionModel.MULTIPLE_GRAPH_SELECTION
&& selectionMode != GraphSelectionModel.SINGLE_GRAPH_SELECTION)
selectionMode = GraphSelectionModel.MULTIPLE_GRAPH_SELECTION;
if (oldMode != selectionMode && changeSupport != null)
changeSupport.firePropertyChange(
SELECTION_MODE_PROPERTY,
new Integer(oldMode),
new Integer(selectionMode));
}
/**
* Returns the selection mode, one of SINGLE_TREE_SELECTION
,
* DISCONTIGUOUS_TREE_SELECTION
or
* CONTIGUOUS_TREE_SELECTION
.
*/
public int getSelectionMode() {
return selectionMode;
}
/**
* Sets if the selection model allows the selection
* of children.
*/
public void setChildrenSelectable(boolean flag) {
childrenSelectable = flag;
}
/**
* Returns true if the selection model allows the selection
* of children.
*/
public boolean isChildrenSelectable() {
return childrenSelectable;
}
/**
* Hook for subclassers for fine-grained control over stepping-into cells.
* This implementation returns childrenSelectable
.
*/
protected boolean isChildrenSelectable(Object cell) {
return childrenSelectable;
}
/**
* Sets the selection to path. If this represents a change, then
* the TreeSelectionListeners are notified. If path
is
* null, this has the same effect as invoking clearSelection
.
*
* @param cell new path to select
*/
public void setSelectionCell(Object cell) {
if (cell == null)
setSelectionCells(null);
else
setSelectionCells(new Object[] { cell });
}
/**
* Sets the selection to cells
. If this represents a
* change the GraphSelectionListeners are notified. Potentially
* paths will be held by this object; in other words don't change
* any of the objects in the array once passed in.
* If paths
is
* null, this has the same effect as invoking clearSelection
.
*
The lead path is set to the last path in pPaths
.
*
If the selection mode is CONTIGUOUS_TREE_SELECTION
,
* and adding the new paths would make the selection discontiguous,
* the selection is reset to the first TreePath in paths
.
*
* @param cells new selection
*/
public void setSelectionCells(Object[] cells) {
if (cells != null) {
if (selectionMode == GraphSelectionModel.SINGLE_GRAPH_SELECTION
&& cells.length > 0)
cells = new Object[] { cells[cells.length - 1] };
cellStates.clear();
Vector change = new Vector();
List newSelection = new ArrayList();
for (int i = 0; i < cells.length; i++) {
if (cells[i] != null) {
change.addElement(
new CellPlaceHolder(
cells[i],
!selection.remove(cells[i])));
select(newSelection, cells[i]);
}
}
Iterator it = selection.iterator();
while (it.hasNext())
change.addElement(new CellPlaceHolder(it.next(), false));
selection = newSelection;
if (change.size() > 0) {
notifyCellChange(change);
}
}
}
/**
* Adds path to the current selection. If path is not currently
* in the selection the TreeSelectionListeners are notified. This has
* no effect if path
is null.
*
* @param cell the new path to add to the current selection
*/
public void addSelectionCell(Object cell) {
if (cell != null)
addSelectionCells(new Object[] { cell });
}
/**
* Adds cells to the current selection. If any of the paths in
* paths are not currently in the selection the TreeSelectionListeners
* are notified. This has
* no effect if paths
is null.
*
The lead path is set to the last element in paths
.
*
If the selection mode is CONTIGUOUS_TREE_SELECTION
,
* and adding the new paths would make the selection discontiguous.
* Then two things can result: if the TreePaths in paths
* are contiguous, then the selection becomes these TreePaths,
* otherwise the TreePaths aren't contiguous and the selection becomes
* the first TreePath in paths
.
*
* @param cells the new path to add to the current selection
*/
public void addSelectionCells(Object[] cells) {
if (cells != null) {
if (selectionMode == GraphSelectionModel.SINGLE_GRAPH_SELECTION)
setSelectionCells(cells);
else {
Vector change = new Vector();
for (int i = 0; i < cells.length; i++) {
if (cells[i] != null) {
boolean newness = select(selection, cells[i]);
if (newness) {
change.addElement(
new CellPlaceHolder(cells[i], true));
Object parent =
graph.getModel().getParent(cells[i]);
if (parent != null)
change.addElement(
new CellPlaceHolder(parent, false));
}
}
}
if (change.size() > 0)
notifyCellChange(change);
}
}
}
/**
* Removes path from the selection. If path is in the selection
* The TreeSelectionListeners are notified. This has no effect if
* path
is null.
*
* @param cell the path to remove from the selection
*/
public void removeSelectionCell(Object cell) {
if (cell != null)
removeSelectionCells(new Object[] { cell });
}
/**
* Removes paths from the selection. If any of the paths in paths
* are in the selection the TreeSelectionListeners are notified.
* This has no effect if paths
is null.
*
* @param cells the path to remove from the selection
*/
public void removeSelectionCells(Object[] cells) {
if (cells != null) {
Vector change = new Vector();
for (int i = 0; i < cells.length; i++) {
if (cells[i] != null) {
boolean removed = deselect(cells[i]);
if (removed) {
change.addElement(new CellPlaceHolder(cells[i], false));
Object parent = graph.getModel().getParent(cells[i]);
if (parent != null)
change.addElement(
new CellPlaceHolder(parent, false));
}
}
}
if (change.size() > 0)
notifyCellChange(change);
}
}
/**
* Returns the cells that are currently selectable.
*/
public Object[] getSelectables() {
if (isChildrenSelectable()) {
List result = new ArrayList();
// Roots Are Always Selectable
Stack s = new Stack();
Object[] cells =
graph.getGraphLayoutCache().getCells(
graph.getGraphLayoutCache().getRoots());
for (int i = 0; i < cells.length; i++)
s.add(cells[i]);
GraphModel model = graph.getModel();
// Children of Selected Cells Are Selectable
while (!s.isEmpty()) {
Object cell = s.pop();
if (!model.isPort(cell))
result.add(cell);
if (isChildrenSelectable(cell)
&& getSelectedChildCount(cell) != 0) {
for (int i = 0; i < model.getChildCount(cell); i++)
s.add(model.getChild(cell, i));
}
}
return result.toArray();
}
return graph.getRoots();
}
/**
* Returns the first cell in the selection. This is useful if there
* if only one item currently selected.
*/
public Object getSelectionCell() {
if (selection != null && selection.size() > 0)
return selection.toArray()[0];
return null;
}
/**
* Returns the cells in the selection. This will return null (or an
* empty array) if nothing is currently selected.
*/
public Object[] getSelectionCells() {
if (selection != null)
return selection.toArray();
return null;
}
/**
* Returns the number of paths that are selected.
*/
public int getSelectionCount() {
return (selection == null) ? 0 : selection.size();
}
/**
* Returns true if the cell, cell
,
* is in the current selection.
*/
public boolean isCellSelected(Object cell) {
int count = getSelectedChildCount(cell);
return (count == SELECTED);
}
/**
* Returns true if the cell, cell
,
* has selected children.
*/
public boolean isChildrenSelected(Object cell) {
int count = getSelectedChildCount(cell);
return (count > 0);
}
/**
* Returns true if the selection is currently empty.
*/
public boolean isSelectionEmpty() {
return (selection.isEmpty());
}
/**
* Empties the current selection. If this represents a change in the
* current selection, the selection listeners are notified.
*/
public void clearSelection() {
if (selection != null) {
Vector change = new Vector();
Iterator it = cellStates.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
change.addElement(new CellPlaceHolder(entry.getKey(), false));
}
selection.clear();
cellStates.clear();
if (change.size() > 0)
notifyCellChange(change);
}
}
//
// Internal Datastructures
//
/**
* Returns the number of selected childs for cell
.
*/
protected int getSelectedChildCount(Object cell) {
if (cell != null) {
Integer state = (Integer) cellStates.get(cell);
if (state == null) {
state = UNSELECTED;
cellStates.put(cell, state);
}
return state.intValue();
}
return 0;
}
/**
* Sets the number of selected childs for cell
* to count
.
*/
protected void setSelectedChildCount(Object cell, int count) {
Integer i = new Integer(count);
cellStates.put(cell, i);
}
/**
* Selects a single cell and updates all datastructures.
* No listeners are notified. Override this method to control
* individual cell selection.
*/
protected boolean select(List list, Object cell) {
if (!isCellSelected(cell)
&& graph.getGraphLayoutCache().isVisible(cell)) {
GraphModel model = graph.getModel();
// Deselect and Update All Parents
Object parent = model.getParent(cell);
while (parent != null) {
int count = getSelectedChildCount(parent);
// Deselect Selected Parents
if (count == SELECTED)
count = 0;
// Increase Child Count
count++;
setSelectedChildCount(parent, count);
// Remove From Selection
selection.remove(parent);
// Next Parent
parent = model.getParent(parent);
}
// Deselect All Children
Object[] tmp = new Object[] { cell };
Set childs = DefaultGraphModel.getDescendants(model, tmp);
// Remove Current Cell From Flat-View
childs.remove(cell);
Iterator it = childs.iterator();
while (it.hasNext()) {
Object child = it.next();
if (child != null && !model.isPort(child)) {
// Remove Child From Selection
selection.remove(child);
// Remove Child State
cellStates.remove(child);
}
}
// Set Selected State for Current
setSelectedChildCount(cell, SELECTED);
// Add Current To HashSet and Return
return list.add(cell);
}
return false;
}
/**
* Deselects a single cell and updates all datastructures.
* No listeners are notified.
*/
protected boolean deselect(Object cell) {
if (isCellSelected(cell)) {
// Update All Parents
Object parent = graph.getModel().getParent(cell);
boolean firstParent = true;
int change = -1;
while (parent != null && change != 0) {
int count = getSelectedChildCount(parent);
count += change;
// Select First Parent If No More Children
if (count == 0 && firstParent) {
change = 0;
count = SELECTED;
selection.add(parent);
}
// Update Selection Count
setSelectedChildCount(parent, count);
// Next Parent
parent = graph.getModel().getParent(parent);
firstParent = false;
}
// Remove State of Current Cell
cellStates.remove(cell);
// Remove Current from Selection and Return
return selection.remove(cell);
}
return false;
}
//
// Listeners
//
/**
* Adds x to the list of listeners that are notified each time the
* set of selected TreePaths changes.
*
* @param x the new listener to be added
*/
public void addGraphSelectionListener(GraphSelectionListener x) {
listenerList.add(GraphSelectionListener.class, x);
}
/**
* Removes x from the list of listeners that are notified each time
* the set of selected TreePaths changes.
*
* @param x the listener to remove
*/
public void removeGraphSelectionListener(GraphSelectionListener x) {
listenerList.remove(GraphSelectionListener.class, x);
}
/**
* Notifies all listeners that are registered for
* tree selection events on this object.
* @see #addGraphSelectionListener
* @see EventListenerList
*/
protected void fireValueChanged(GraphSelectionEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// TreeSelectionEvent 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] == GraphSelectionListener.class) {
// Lazily create the event:
// if (e == null)
// e = new ListSelectionEvent(this, firstIndex, lastIndex);
((GraphSelectionListener) listeners[i + 1]).valueChanged(e);
}
}
}
/**
* Returns an array of all the listeners of the given type that
* were added to this model.
*
* @return all of the objects receiving listenerType notifications
* from this model
*
* @since 1.3
*/
public EventListener[] getListeners(Class listenerType) {
return listenerList.getListeners(listenerType);
}
/**
* Adds a PropertyChangeListener to the listener list.
* The listener is registered for all properties.
*
* A PropertyChangeEvent will get fired when the selection mode
* changes.
*
* @param listener the PropertyChangeListener to be added
*/
public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
if (changeSupport == null) {
changeSupport = new SwingPropertyChangeSupport(this);
}
changeSupport.addPropertyChangeListener(listener);
}
/**
* Removes a PropertyChangeListener from the listener list.
* This removes a PropertyChangeListener that was registered
* for all properties.
*
* @param listener the PropertyChangeListener to be removed
*/
public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
if (changeSupport == null) {
return;
}
changeSupport.removePropertyChangeListener(listener);
}
/**
* Notifies listeners of a change in path. changePaths should contain
* instances of PathPlaceHolder.
*/
protected void notifyCellChange(Vector changedCells) {
int cCellCount = changedCells.size();
boolean[] newness = new boolean[cCellCount];
Object[] cells = new Object[cCellCount];
CellPlaceHolder placeholder;
for (int counter = 0; counter < cCellCount; counter++) {
placeholder = (CellPlaceHolder) changedCells.elementAt(counter);
newness[counter] = placeholder.isNew;
cells[counter] = placeholder.cell;
}
GraphSelectionEvent event =
new GraphSelectionEvent(this, cells, newness);
fireValueChanged(event);
}
/**
* Returns a clone of this object with the same selection.
* This method does not duplicate
* selection listeners and property listeners.
*
* @exception CloneNotSupportedException never thrown by instances of
* this class
*/
public Object clone() throws CloneNotSupportedException {
DefaultGraphSelectionModel clone =
(DefaultGraphSelectionModel) super.clone();
clone.changeSupport = null;
if (selection != null)
clone.selection = new ArrayList(selection);
clone.listenerList = new EventListenerList();
return clone;
}
/**
* Holds a path and whether or not it is new.
*/
protected class CellPlaceHolder {
protected boolean isNew;
protected Object cell;
protected CellPlaceHolder(Object cell, boolean isNew) {
this.cell = cell;
this.isNew = isNew;
}
/**
* Returns the cell.
* @return Object
*/
public Object getCell() {
return cell;
}
/**
* Returns the isNew.
* @return boolean
*/
public boolean isNew() {
return isNew;
}
/**
* Sets the cell.
* @param cell The cell to set
*/
public void setCell(Object cell) {
this.cell = cell;
}
/**
* Sets the isNew.
* @param isNew The isNew to set
*/
public void setNew(boolean isNew) {
this.isNew = isNew;
}
}
}