001: /*
002: * @(#)DefaultGraphSelectionModel.java 1.0 03-JUL-04
003: *
004: * Copyright (c) 2001-2004 Gaudenz Alder
005: *
006: */
007: package org.jgraph.graph;
008:
009: import java.beans.PropertyChangeListener;
010: import java.io.Serializable;
011: import java.util.ArrayList;
012: import java.util.EventListener;
013: import java.util.Hashtable;
014: import java.util.Iterator;
015: import java.util.LinkedHashSet;
016: import java.util.List;
017: import java.util.Map;
018: import java.util.Set;
019: import java.util.Stack;
020: import java.util.Vector;
021:
022: import javax.swing.event.EventListenerList;
023: import javax.swing.event.SwingPropertyChangeSupport;
024:
025: import org.jgraph.JGraph;
026: import org.jgraph.event.GraphSelectionEvent;
027: import org.jgraph.event.GraphSelectionListener;
028:
029: /**
030: * Default implementation of GraphSelectionModel. Listeners are notified
031: *
032: * @version 1.0 1/1/02
033: * @author Gaudenz Alder
034: */
035: public class DefaultGraphSelectionModel implements GraphSelectionModel,
036: Cloneable, Serializable {
037:
038: /** Property name for selectionMode. */
039: public static final String SELECTION_MODE_PROPERTY = "selectionMode";
040:
041: /** Value that represents selected state in cellStates. */
042: public static final int SELECTED = -1;
043:
044: /** Object value that represents the unselected state in cellStates. */
045: public static final Integer UNSELECTED = new Integer(0);
046:
047: /** Reference to the parent graph. Used to find parents and childs. */
048: protected JGraph graph;
049:
050: /** Used to message registered listeners. */
051: protected SwingPropertyChangeSupport changeSupport;
052:
053: /** Event listener list. */
054: protected EventListenerList listenerList = new EventListenerList();
055:
056: /**
057: * Mode for the selection, will be either SINGLE_TREE_SELECTION,
058: * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
059: */
060: protected int selectionMode;
061:
062: /** Boolean that indicates if the model allows stepping-into groups. */
063: protected boolean childrenSelectable = true;
064:
065: /** Maps the cells to their selection state. */
066: protected Map cellStates = new Hashtable();
067:
068: /** List that contains the selected items. */
069: protected Set selection = new LinkedHashSet();
070:
071: /** Constructs a DefaultGraphSelectionModel for the specified graph. */
072: public DefaultGraphSelectionModel(JGraph graph) {
073: this .graph = graph;
074: }
075:
076: /**
077: * Sets the selection mode, which must be one of SINGLE_TREE_SELECTION,
078: */
079: public void setSelectionMode(int mode) {
080: int oldMode = selectionMode;
081:
082: selectionMode = mode;
083: if (selectionMode != GraphSelectionModel.MULTIPLE_GRAPH_SELECTION
084: && selectionMode != GraphSelectionModel.SINGLE_GRAPH_SELECTION)
085: selectionMode = GraphSelectionModel.MULTIPLE_GRAPH_SELECTION;
086: if (oldMode != selectionMode && changeSupport != null)
087: changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY,
088: new Integer(oldMode), new Integer(selectionMode));
089: }
090:
091: /**
092: * Returns the selection mode, one of <code>SINGLE_TREE_SELECTION</code>,
093: * <code>DISCONTIGUOUS_TREE_SELECTION</code> or
094: * <code>CONTIGUOUS_TREE_SELECTION</code>.
095: */
096: public int getSelectionMode() {
097: return selectionMode;
098: }
099:
100: /**
101: * Sets if the selection model allows the selection of children.
102: */
103: public void setChildrenSelectable(boolean flag) {
104: childrenSelectable = flag;
105: }
106:
107: /**
108: * Returns true if the selection model allows the selection of children.
109: */
110: public boolean isChildrenSelectable() {
111: return childrenSelectable;
112: }
113:
114: /**
115: * Hook for subclassers for fine-grained control over stepping-into cells.
116: * This implementation returns <code>childrenSelectable</code>&&
117: * isCellSelected.
118: */
119: protected boolean isChildrenSelectable(Object cell) {
120: AttributeMap attr = graph.getModel().getAttributes(cell);
121: if (attr != null && childrenSelectable)
122: return GraphConstants.isChildrenSelectable(attr);
123: return childrenSelectable;
124: }
125:
126: /**
127: * Selects the specified cell.
128: *
129: * @param cell
130: * the cell to select
131: */
132: public void setSelectionCell(Object cell) {
133: if (cell == null)
134: setSelectionCells(null);
135: else
136: setSelectionCells(new Object[] { cell });
137: }
138:
139: /**
140: * Sets the selection to <code>cells</code>. If this represents a change
141: * the GraphSelectionListeners are notified. Potentially paths will be held
142: * by this object; in other words don't change any of the objects in the
143: * array once passed in.
144: *
145: * @param cells
146: * new selection
147: */
148: public void setSelectionCells(Object[] cells) {
149: if (cells != null) {
150: if (selectionMode == GraphSelectionModel.SINGLE_GRAPH_SELECTION
151: && cells.length > 0)
152: cells = new Object[] { cells[cells.length - 1] };
153: cellStates.clear();
154: Vector change = new Vector();
155: Set newSelection = new LinkedHashSet();
156: for (int i = 0; i < cells.length; i++) {
157: if (cells[i] != null) {
158: selection.remove(cells[i]);
159: change.addElement(new CellPlaceHolder(cells[i],
160: !selection.remove(cells[i])));
161: select(newSelection, cells[i]);
162: Object parent = graph.getModel()
163: .getParent(cells[i]);
164: if (parent != null)
165: change.addElement(new CellPlaceHolder(parent,
166: false));
167: }
168: }
169: Iterator it = selection.iterator();
170: while (it.hasNext()) {
171: Object cell = it.next();
172: while (cell != null) {
173: change.addElement(new CellPlaceHolder(cell, false));
174: cell = graph.getModel().getParent(cell);
175: }
176: }
177: selection = newSelection;
178: if (change.size() > 0) {
179: notifyCellChange(change);
180: }
181: }
182: }
183:
184: /**
185: * Adds the specified cell to the current selection
186: *
187: * @param cell
188: * the cell to add to the current selection
189: */
190: public void addSelectionCell(Object cell) {
191: if (cell != null)
192: addSelectionCells(new Object[] { cell });
193: }
194:
195: /**
196: * Adds cells to the current selection.
197: *
198: * @param cells
199: * the cells to be added to the current selection
200: */
201: public void addSelectionCells(Object[] cells) {
202: if (cells != null) {
203: if (selectionMode == GraphSelectionModel.SINGLE_GRAPH_SELECTION)
204: setSelectionCells(cells);
205: else {
206: Vector change = new Vector();
207: for (int i = 0; i < cells.length; i++) {
208: if (cells[i] != null) {
209: boolean newness = select(selection, cells[i]);
210: if (newness) {
211: change.addElement(new CellPlaceHolder(
212: cells[i], true));
213: Object parent = graph.getModel().getParent(
214: cells[i]);
215: if (parent != null)
216: change.addElement(new CellPlaceHolder(
217: parent, false));
218: }
219: }
220: }
221: if (change.size() > 0)
222: notifyCellChange(change);
223: }
224: }
225: }
226:
227: /**
228: * Removes the specified cell from the selection.
229: *
230: * @param cell
231: * the cell to remove from the current selection
232: */
233: public void removeSelectionCell(Object cell) {
234: if (cell != null)
235: removeSelectionCells(new Object[] { cell });
236: }
237:
238: /**
239: * Removes the specified cells from the selection.
240: *
241: * @param cells
242: * the cells to remove from the current selection
243: */
244: public void removeSelectionCells(Object[] cells) {
245: if (cells != null) {
246: Vector change = new Vector();
247: for (int i = 0; i < cells.length; i++) {
248: if (cells[i] != null) {
249: boolean removed = deselect(cells[i]);
250: if (removed) {
251: change.addElement(new CellPlaceHolder(cells[i],
252: false));
253: Object parent = graph.getModel().getParent(
254: cells[i]);
255: if (parent != null)
256: change.addElement(new CellPlaceHolder(
257: parent, false));
258: }
259: }
260: }
261: if (change.size() > 0)
262: notifyCellChange(change);
263: }
264: }
265:
266: /**
267: * Returns the cells that are currently selectable. The array is ordered so
268: * that the top-most cell appears first. <br>
269: */
270: public Object[] getSelectables() {
271: if (isChildrenSelectable()) {
272: List result = new ArrayList();
273: // Roots Are Always Selectable
274: Stack s = new Stack();
275: GraphModel model = graph.getModel();
276: for (int i = 0; i < model.getRootCount(); i++)
277: s.add(model.getRootAt(i));
278: while (!s.isEmpty()) {
279: Object cell = s.pop();
280: AttributeMap attrs = graph.getAttributes(cell);
281: if (!model.isPort(cell)
282: && (attrs == null || GraphConstants
283: .isSelectable(attrs)))
284: result.add(cell);
285: if (isChildrenSelectable(cell)) {
286: for (int i = 0; i < model.getChildCount(cell); i++)
287: s.add(model.getChild(cell, i));
288: }
289: }
290: return result.toArray();
291: }
292: return graph.getRoots();
293: }
294:
295: /**
296: * Returns the first cell in the selection. This is useful if there if only
297: * one item currently selected.
298: */
299: public Object getSelectionCell() {
300: if (selection != null && selection.size() > 0)
301: return selection.toArray()[0];
302: return null;
303: }
304:
305: /**
306: * Returns the cells in the selection. This will return null (or an empty
307: * array) if nothing is currently selected.
308: */
309: public Object[] getSelectionCells() {
310: if (selection != null)
311: return selection.toArray();
312: return null;
313: }
314:
315: /**
316: * Returns the number of paths that are selected.
317: */
318: public int getSelectionCount() {
319: return (selection == null) ? 0 : selection.size();
320: }
321:
322: /**
323: * Returns true if the cell, <code>cell</code>, is in the current
324: * selection.
325: */
326: public boolean isCellSelected(Object cell) {
327: int count = getSelectedChildCount(cell);
328: return (count == SELECTED);
329: }
330:
331: /**
332: * Returns true if the cell, <code>cell</code>, has selected children.
333: */
334: public boolean isChildrenSelected(Object cell) {
335: int count = getSelectedChildCount(cell);
336: return (count > 0);
337: }
338:
339: /**
340: * Returns true if the selection is currently empty.
341: */
342: public boolean isSelectionEmpty() {
343: return (selection.isEmpty());
344: }
345:
346: /**
347: * Empties the current selection. If this represents a change in the current
348: * selection, the selection listeners are notified.
349: */
350: public void clearSelection() {
351: if (selection != null) {
352: Vector change = new Vector();
353: Iterator it = cellStates.entrySet().iterator();
354: while (it.hasNext()) {
355: Map.Entry entry = (Map.Entry) it.next();
356: Object cell = entry.getKey();
357: while (cell != null) {
358: change.addElement(new CellPlaceHolder(cell, false));
359: cell = graph.getModel().getParent(cell);
360: }
361: }
362: selection.clear();
363: cellStates.clear();
364: if (change.size() > 0)
365: notifyCellChange(change);
366: }
367: }
368:
369: //
370: // Internal Datastructures
371: //
372:
373: /**
374: * Returns the number of selected childs for <code>cell</code>.
375: */
376: protected int getSelectedChildCount(Object cell) {
377: if (cell != null) {
378: Integer state = (Integer) cellStates.get(cell);
379: if (state == null) {
380: state = UNSELECTED;
381: cellStates.put(cell, state);
382: }
383: return state.intValue();
384: }
385: return 0;
386: }
387:
388: /**
389: * Sets the number of selected childs for <code>cell</code> to
390: * <code>count</code>.
391: */
392: protected void setSelectedChildCount(Object cell, int count) {
393: Integer i = new Integer(count);
394: cellStates.put(cell, i);
395: }
396:
397: /**
398: * Selects a single cell and updates all datastructures. No listeners are
399: * notified. Override this method to control individual cell selection.
400: */
401: protected boolean select(Set set, Object cell) {
402: AttributeMap attrs = graph.getAttributes(cell);
403: if (!isCellSelected(cell)
404: && graph.getGraphLayoutCache().isVisible(cell)
405: && (attrs == null || GraphConstants.isSelectable(attrs))) {
406: GraphModel model = graph.getModel();
407: // Deselect and Update All Parents
408: Object parent = model.getParent(cell);
409: while (parent != null) {
410: int count = getSelectedChildCount(parent);
411: // Deselect Selected Parents
412: if (count == SELECTED)
413: count = 0;
414: // Increase Child Count
415: count++;
416: setSelectedChildCount(parent, count);
417: // Remove From Selection
418: selection.remove(parent);
419: // Next Parent
420: parent = model.getParent(parent);
421: }
422: // Deselect All Children
423: Object[] tmp = new Object[] { cell };
424: List childs = DefaultGraphModel.getDescendants(model, tmp);
425: // Remove Current Cell From Flat-View
426: // TODO check performance of next line
427: childs.remove(cell);
428: Iterator it = childs.iterator();
429: while (it.hasNext()) {
430: Object child = it.next();
431: if (child != null && !model.isPort(child)) {
432: // Remove Child From Selection
433: selection.remove(child);
434: // Remove Child State
435: cellStates.remove(child);
436: }
437: }
438: // Set Selected State for Current
439: setSelectedChildCount(cell, SELECTED);
440: // Add Current To HashSet and Return
441: return set.add(cell);
442: }
443: return false;
444: }
445:
446: /**
447: * Deselects a single cell and updates all datastructures. No listeners are
448: * notified.
449: */
450: protected boolean deselect(Object cell) {
451: if (isCellSelected(cell)) {
452: // Update All Parents
453: Object parent = graph.getModel().getParent(cell);
454: boolean firstParent = true;
455: int change = -1;
456: while (parent != null && change != 0) {
457: int count = getSelectedChildCount(parent);
458: count += change;
459: // Select First Parent If No More Children
460: if (count == 0 && firstParent) {
461: change = 0;
462: count = SELECTED;
463: selection.add(parent);
464: }
465: // Update Selection Count
466: setSelectedChildCount(parent, count);
467: // Next Parent
468: parent = graph.getModel().getParent(parent);
469: firstParent = false;
470: }
471: // Remove State of Current Cell
472: cellStates.remove(cell);
473: // Remove Current from Selection and Return
474: return selection.remove(cell);
475: }
476: return false;
477: }
478:
479: //
480: // Listeners
481: //
482:
483: /**
484: * Adds x to the list of listeners that are notified each time the set of
485: * selected TreePaths changes.
486: *
487: * @param x
488: * the new listener to be added
489: */
490: public void addGraphSelectionListener(GraphSelectionListener x) {
491: listenerList.add(GraphSelectionListener.class, x);
492: }
493:
494: /**
495: * Removes x from the list of listeners that are notified each time the set
496: * of selected TreePaths changes.
497: *
498: * @param x
499: * the listener to remove
500: */
501: public void removeGraphSelectionListener(GraphSelectionListener x) {
502: listenerList.remove(GraphSelectionListener.class, x);
503: }
504:
505: /**
506: * Notifies all listeners that are registered for tree selection events on
507: * this object.
508: *
509: * @see #addGraphSelectionListener
510: * @see EventListenerList
511: */
512: protected void fireValueChanged(GraphSelectionEvent e) {
513: // Guaranteed to return a non-null array
514: Object[] listeners = listenerList.getListenerList();
515: // TreeSelectionEvent e = null;
516: // Process the listeners last to first, notifying
517: // those that are interested in this event
518: for (int i = listeners.length - 2; i >= 0; i -= 2) {
519: if (listeners[i] == GraphSelectionListener.class) {
520: // Lazily create the event:
521: // if (e == null)
522: // e = new ListSelectionEvent(this, firstIndex, lastIndex);
523: ((GraphSelectionListener) listeners[i + 1])
524: .valueChanged(e);
525: }
526: }
527: }
528:
529: /**
530: * Returns an array of all the listeners of the given type that were added
531: * to this model.
532: *
533: * @return all of the objects receiving <em>listenerType</em>
534: * notifications from this model
535: *
536: * @since 1.3
537: */
538: public EventListener[] getListeners(Class listenerType) {
539: return listenerList.getListeners(listenerType);
540: }
541:
542: /**
543: * Adds a PropertyChangeListener to the listener list. The listener is
544: * registered for all properties.
545: * <p>
546: * A PropertyChangeEvent will get fired when the selection mode changes.
547: *
548: * @param listener
549: * the PropertyChangeListener to be added
550: */
551: public synchronized void addPropertyChangeListener(
552: PropertyChangeListener listener) {
553: if (changeSupport == null) {
554: changeSupport = new SwingPropertyChangeSupport(this );
555: }
556: changeSupport.addPropertyChangeListener(listener);
557: }
558:
559: /**
560: * Removes a PropertyChangeListener from the listener list. This removes a
561: * PropertyChangeListener that was registered for all properties.
562: *
563: * @param listener
564: * the PropertyChangeListener to be removed
565: */
566:
567: public synchronized void removePropertyChangeListener(
568: PropertyChangeListener listener) {
569: if (changeSupport == null) {
570: return;
571: }
572: changeSupport.removePropertyChangeListener(listener);
573: }
574:
575: /**
576: * Notifies listeners of a change in path. <code>changePaths</code> should
577: * contain instances of PathPlaceHolder.
578: */
579: protected void notifyCellChange(Vector changedCells) {
580: int cCellCount = changedCells.size();
581: boolean[] newness = new boolean[cCellCount];
582: Object[] cells = new Object[cCellCount];
583: CellPlaceHolder placeholder;
584:
585: for (int counter = 0; counter < cCellCount; counter++) {
586: placeholder = (CellPlaceHolder) changedCells
587: .elementAt(counter);
588: newness[counter] = placeholder.isNew;
589: cells[counter] = placeholder.cell;
590: }
591:
592: GraphSelectionEvent event = new GraphSelectionEvent(this ,
593: cells, newness);
594:
595: fireValueChanged(event);
596: }
597:
598: /**
599: * Returns a clone of this object with the same selection. This method does
600: * not duplicate selection listeners and property listeners.
601: *
602: * @exception CloneNotSupportedException
603: * never thrown by instances of this class
604: */
605: public Object clone() throws CloneNotSupportedException {
606: DefaultGraphSelectionModel clone = (DefaultGraphSelectionModel) super
607: .clone();
608: clone.changeSupport = null;
609: if (selection != null)
610: clone.selection = new LinkedHashSet(selection);
611: clone.listenerList = new EventListenerList();
612: return clone;
613: }
614:
615: /**
616: * Holds a path and whether or not it is new.
617: */
618: protected class CellPlaceHolder {
619: protected boolean isNew;
620:
621: protected Object cell;
622:
623: protected CellPlaceHolder(Object cell, boolean isNew) {
624: this .cell = cell;
625: this .isNew = isNew;
626: }
627:
628: /**
629: * Returns the cell.
630: *
631: * @return Object
632: */
633: public Object getCell() {
634: return cell;
635: }
636:
637: /**
638: * Returns the isNew.
639: *
640: * @return boolean
641: */
642: public boolean isNew() {
643: return isNew;
644: }
645:
646: /**
647: * Sets the cell.
648: *
649: * @param cell
650: * The cell to set
651: */
652: public void setCell(Object cell) {
653: this .cell = cell;
654: }
655:
656: /**
657: * Sets the isNew.
658: *
659: * @param isNew
660: * The isNew to set
661: */
662: public void setNew(boolean isNew) {
663: this.isNew = isNew;
664: }
665:
666: }
667:
668: }
|