summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java
diff options
context:
space:
mode:
Diffstat (limited to 'projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java')
-rw-r--r--projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java2930
1 files changed, 1311 insertions, 1619 deletions
diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java
index 86bfa69..b0070d4 100644
--- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java
+++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java
@@ -48,1704 +48,1396 @@ import net.wotonomy.ui.EOAssociation;
import net.wotonomy.ui.EODisplayGroup;
/**
-* TreeModelAssociation binds a JTree or similar component
-* that uses a TreeModel to a display group's
-* list of displayable objects, each of which may have
-* a list of child objects managed by another display
-* group, and so on. TreeModelAssociation works exactly
-* like a ListAssociation, with the additional capability
-* to specify a "Children" aspect, that will allow child
-* objects to be retrieved from a parent display group.
-*
-* <ul>
-*
-* <li>titles: a property convertable to a string for
-* display in the nodes of the tree. The objects in
-* the bound display group will be used to populate the
-* initial root nodes of the tree (more accurately,
-* children of the offscreen root node in the tree).</li>
-*
-* <li>children: a property of a node value that returns
-* zero, one or many objects, each of which will correspond
-* to a child node for the corresponding node in the tree.
-* The data source of the bound display group is replaced
-* a data source that populates the display group with
-* the selection in the tree component as determined by
-* calling fetchObjectsIntoChildrenGroup.
-* If this aspect is not bound, the tree behaves like a list.
-* <br><br>
-* Binding this aspect with a null display group is the same
-* as binding it with the titles display group.
-* In this configuration the contents of the titles
-* display group will be replaced with the selection in the
-* tree component, as specified above, replacing the existing
-* data source.
-* <br><br>
-* In that case, the display groups for the nodes in
-* the tree will still use the original data source
-* for resolving their children key, and programmatically
-* setting the contents of the display group will still
-* repopulate the root nodes of the tree.
-* </li>
-*
-* <li>isLeaf: a property of a node value that returns
-* a value convertable to a boolean value (aside from
-* an actual boolean value, zeroes evaluate to true,
-* as does any String containing "yes" or "true" or that
-* is convertable to a number equal to zero; other values
-* evaluate to false).
-* <br><br>
-* If the isLeaf aspect is not bound,
-* the tree must force nodes to load their children to
-* determine whether they are leaf nodes (in effect
-* loading the grandchildren for any expanded node).
-* If bound, child loading is deferred until the node
-* is actually expanded.
-* <br><br>
-* For example, binding this value to a null
-* display group and the key "false" will result in a
-* deferred-loading tree that works much like Windows
-* Explorer's network volume browser - all nodes appear
-* with "pluses" until they are expanded.
-* <br><br>
-* Note that the display group is ignored: the property
-* will be applied directly to the object corresponding
-* to the node.</li>
-*
-* </ul>
-*
-* This class acts as the TreeModel for the controlled
-* component: calling yourcomponent.getModel() will
-* return this association. The tree model methods on
-* this class are public and may be used to affect changes
-* on the controlled components.<br><br>
-*
-* The titles display group's contents are inserted
-* into a new display group that acts as the root node.
-* After that point, changes in the titles display group
-* will cause the tree model to reset itself, creating
-* a new display group for the root node.
-* <br><br>
-*
-* If a separate display group is bound to the children
-* aspect, it will
-* be used to hold the selected objects and their siblings
-* and selection will be maintained there, and the titles
-* display group selection will not be updated.
-* Any editing or detail associations should in that case
-* be attached to the children display group, not the titles
-* group. <br><br>
-*
-* Each node in the tree is an EODisplayGroup that
-* contains the child objects of the object it represents
-* in the tree. These objects can be programmatically
-* inserted, updated, or removed using DisplayGroup
-* methods. Each node's takes its parent group's
-* sortOrderings until a sort ordering is explicitly
-* specified - setting a sort ordering to null will resume
-* using the parent group's sort ordering.<br><br>
-*
-* Each node in the tree also implements MutableTreeNode.
-* The value that a node represents is the titles property
-* value of the object in the parent's displayed objects
-* list at the index corresponding to the index of the node.
-* Calling toString on a node returns the string representation
-* of the titles property value, and setUserObject will update
-* that value directly in the corresponding object.
-* Moving a node from one parent to another will remove the
-* actual object in the parent display group and insert it
-* into the destination display group.<br><br>
-*
-* In short, any nodes obtained from this class'
-* implementation of TreeModel may be cast as either
-* EODisplayGroup or MutableTreeNode and maybe be
-* programmatically manipulated in either manner.
-*
-* @author michael@mpowers.net
-* @author $Author: cgruber $
-* @version $Revision: 904 $
-*/
-public class TreeModelAssociation extends EOAssociation
- implements TreeModel, TreeSelectionListener
-{
- static final NSArray aspects =
- new NSArray( new Object[] {
- TitlesAspect, ChildrenAspect, IsLeafAspect
- } );
- static final NSArray aspectSignatures =
- new NSArray( new Object[] {
- AttributeToOneAspectSignature
- } );
- static final NSArray objectKeysTaken =
- new NSArray( new Object[] {
- "model"
- } );
-
- private final static NSSelector getSelectionModel =
- new NSSelector( "getSelectionModel" );
- private final static NSSelector setModel =
- new NSSelector( "setModel",
- new Class[] { TreeModel.class } );
-
- EODisplayGroup titlesDisplayGroup, childrenDisplayGroup, leafDisplayGroup;
- String titlesKey, childrenKey, leafKey;
- DisplayGroupNode rootNode;
- Vector listeners;
- Object rootLabel;
-
- TreeSelectionModel selectionModel;
- boolean selectionPaintedImmediately;
-
- boolean insertingChild;
- boolean insertingAfter;
-
- EOObserverProxy recentChangesObserver;
-
- private boolean pleaseSelectRootNode;
-
- /**
- * Constructor expecting a JTree or any other object
- * that has void setModel(TreeModel) and TreeModel getSelectionModel()
- * methods. This tree association will be used for the TreeModel.
- * The root node will be labeled "Root". <br><br>
- *
- * As an alternate way to use a TreeModelAssociation, you may pass a
- * TreeSelectionModel to the constructor and then manually set your
- * component to use this class as its TreeModel.
- */
- public TreeModelAssociation ( Object anObject )
- {
- super( anObject );
-
- titlesDisplayGroup = null;
- titlesKey = null;
- childrenDisplayGroup = null;
- childrenKey = null;
- leafDisplayGroup = null;
- leafKey = null;
- listeners = new Vector();
-
- selectionPaintedImmediately = false;
-
- // after display group nodes process recent changes
- recentChangesObserver = new EOObserverProxy(
- this, new NSSelector( "processRecentChanges" ),
- EODelayedObserver.ObserverPrioritySixth );
- EOObserverCenter.addObserver( recentChangesObserver, this );
-
- insertingChild = true;
- insertingAfter = true;
-
- pleaseSelectRootNode = false;
-
- rootLabel = "Root";
- rootNode = createNode( null, null );
- }
-
- /**
- * Constructor expecting a JTree or similar component
- * and specifying a label for the root node.
- */
- public TreeModelAssociation( Object anObject, Object aRootLabel )
- {
- this( anObject );
- rootLabel = aRootLabel;
- rootNode.setUserObject( aRootLabel );
- }
-
- /**
- * Gets the current root label.
- */
- public Object rootLabel()
- {
- return rootLabel;
- }
-
- /**
- * Gets the current root label.
- */
- public Object getRootLabel()
- {
- return rootLabel();
- }
-
- /**
- * Sets the root label.
- */
- public void setRootLabel( Object aLabel )
- {
- rootLabel = aLabel;
- }
-
- /**
- * Returns a List of aspect signatures whose contents
- * correspond with the aspects list. Each element is
- * a string whose characters represent a capability of
- * the corresponding aspect. <ul>
- * <li>"A" attribute: the aspect can be bound to
- * an attribute.</li>
- * <li>"1" to-one: the aspect can be bound to a
- * property that returns a single object.</li>
- * <li>"M" to-one: the aspect can be bound to a
- * property that returns multiple objects.</li>
- * </ul>
- * An empty signature "" means that the aspect can
- * bind without needing a key.
- * This implementation returns "A1M" for each
- * element in the aspects array.
- */
- public static NSArray aspectSignatures ()
- {
- return aspectSignatures;
- }
-
- /**
- * Returns a List that describes the aspects supported
- * by this class. Each element in the list is the string
- * name of the aspect. This implementation returns an
- * empty list.
- */
- public static NSArray aspects ()
- {
- return aspects;
- }
-
- /**
- * Returns a List of EOAssociation subclasses that,
- * for the objects that are usable for this association,
- * are less suitable than this association.
- */
- public static NSArray associationClassesSuperseded ()
- {
- return new NSArray();
- }
-
- /**
- * Returns whether this class can control the specified
- * object.
- */
- public static boolean isUsableWithObject ( Object anObject )
- {
- return setModel.implementedByObject( anObject );
- }
-
- /**
- * Returns a List of properties of the controlled object
- * that are controlled by this class. For example,
- * "stringValue", or "selected".
- */
- public static NSArray objectKeysTaken ()
- {
- return objectKeysTaken;
- }
-
- /**
- * Returns the aspect that is considered primary
- * or default. This is typically "value" or somesuch.
- */
- public static String primaryAspect ()
- {
- return TitlesAspect;
- }
-
- /**
- * Returns whether this association can bind to the
- * specified display group on the specified key for
- * the specified aspect.
- */
- public boolean canBindAspect (
- String anAspect, EODisplayGroup aDisplayGroup, String aKey )
- {
- return ( aspects.containsObject( anAspect ) );
- }
-
- /**
- * Binds the specified aspect of this association to the
- * specified key on the specified display group.
- */
- public void bindAspect (
- String anAspect, EODisplayGroup aDisplayGroup, String aKey )
- {
- if ( TitlesAspect.equals( anAspect ) )
- {
- titlesDisplayGroup = aDisplayGroup;
- titlesKey = aKey;
- }
- if ( ChildrenAspect.equals( anAspect ) )
- {
- childrenDisplayGroup = aDisplayGroup;
- childrenKey = aKey;
- }
- if ( IsLeafAspect.equals( anAspect ) )
- {
- leafDisplayGroup = aDisplayGroup;
- leafKey = aKey;
- }
- if ( childrenDisplayGroup == null )
- {
- childrenDisplayGroup = titlesDisplayGroup;
- }
- super.bindAspect( anAspect, aDisplayGroup, aKey );
- }
-
- /**
- * Establishes a connection between this association
- * and the controlled object. Subclasses should begin
- * listening for events from their controlled object here.
- */
- public void establishConnection ()
- {
- if ( titlesDisplayGroup == null )
- {
- throw new WotonomyException(
- "TreeModelAssociation: Titles aspect must be bound" );
- }
-
- // populate the root node
- rootNode = createNode( titlesDisplayGroup, null );
- rootNode.setObjectArray( titlesDisplayGroup.displayedObjects() );
- rootNode.setSortOrderings( titlesDisplayGroup.sortOrderings() );
-
- EODataSource dataSource = childrenDisplayGroup.dataSource();
- if ( dataSource == null ) dataSource = titlesDisplayGroup.dataSource();
- while ( dataSource instanceof DelegatingTreeDataSource )
- { // unwrap any existing delegating data sources
- dataSource = ((DelegatingTreeDataSource)dataSource).delegateDataSource;
- }
- // create a new delegating data source
- childrenDisplayGroup.setDataSource(
- new DelegatingTreeDataSource( this, dataSource ) );
-
- //TODO: find out why omitting this line causes weird selection behavior
- childrenDisplayGroup.setSortOrderings( new NSArray() );
-
- // check for alternate usage
- if ( object() instanceof TreeSelectionModel )
- {
- selectionModel = (TreeSelectionModel) object();
- }
- else // use specified object
- {
- try
- {
- setModel.invoke( object(), new Object[] { this } );
- selectionModel = (TreeSelectionModel)
- getSelectionModel.invoke( object(), new Object[] {} );
- }
- catch ( Exception exc )
- {
- throw new WotonomyException( exc );
- }
- }
-
- addAsListener();
- super.establishConnection();
-/*
- fireRootStructureChanged();
-
-// titlesGroupChanged = true;
-// subjectChanged();
-
- // update the children group
- removeAsListener();
- childrenDisplayGroup.fetch();
- addAsListener();
-
- // update selection
- selectFromDisplayGroup( titlesDisplayGroup );
-*/
- }
-
- protected void fireRootStructureChanged()
- {
- int count = rootNode.displayedObjects().count();
- int[] childIndices = new int[ count ];
- Object[] children = new Object[ count ];
- for ( int i = 0; i < count; i++ )
- {
- childIndices[i] = i;
- children[i] = rootNode.getChildNodeAt( i );
- }
-
- // must fire a tree structure changed with children,
- // otherwise the tree gets weird selection behavior
- fireTreeStructureChanged( this, new Object[] { rootNode },
- childIndices, children );
- }
-
- /**
- * Breaks the connection between this association and
- * its object. Override to stop listening for events
- * from the object.
- */
- public void breakConnection ()
- {
- if ( childrenDisplayGroup != null )
- {
- if ( childrenDisplayGroup.dataSource() instanceof DelegatingTreeDataSource )
- {
- if ( titlesDisplayGroup == childrenDisplayGroup )
- {
- titlesDisplayGroup.setDataSource( ((DelegatingTreeDataSource)
- childrenDisplayGroup.dataSource()).delegateDataSource );
- }
- else
- {
- childrenDisplayGroup.setDataSource( null );
- }
- }
- }
-
- removeAsListener();
- super.breakConnection();
- }
-
- protected void addAsListener()
- {
- isListening = true;
- selectionModel.addTreeSelectionListener( this );
- }
-
- protected void removeAsListener()
- {
- isListening = false;
- selectionModel.removeTreeSelectionListener( this );
- }
-
- protected boolean isListening = false;
- private boolean pleaseIgnore = false;
- protected boolean titlesGroupChanged = false;
- protected boolean childrenGroupChanged = false;
-
- /**
- * Overridden to better discriminate what is changed.
- */
- public void objectWillChange( Object anObject )
- {
- if ( ! isListening ) return;
-
- if ( anObject == titlesDisplayGroup )
- {
- titlesGroupChanged = true;
- }
- if ( anObject == childrenDisplayGroup )
- {
- childrenGroupChanged = true;
- if ( childrenDisplayGroup.qualifier() != null )
- {
- if ( ( rootNode.qualifier() == null ) ||
- ! childrenDisplayGroup.qualifier().equals( rootNode.qualifier() ) )
- {
- // quietly move qualifier from children group to root node
- rootNode.setQualifier( childrenDisplayGroup.qualifier() );
- childrenDisplayGroup.setQualifier( null );
- rootNode.updateDisplayedObjects();
- }
- }
- }
- super.objectWillChange( anObject );
- }
-
- /**
- * Called when either the selection or the contents
- * of an associated display group have changed.
- */
- public void subjectChanged ()
- {
- // titles aspect
- if ( titlesGroupChanged )
- {
- if ( titlesDisplayGroup.contentsChanged() )
- {
- NSArray displayedObjects = titlesDisplayGroup.displayedObjects();
- NSArray childrenObjects;
- if ( titlesDisplayGroup != childrenDisplayGroup
- || displayedObjects.count()
- != (childrenObjects = objectsFetchedIntoChildrenGroup()).count()
- || ! displayedObjects.containsAll( childrenObjects ) )
- {
- populateFromDisplayGroup( displayedObjects );
- }
- }
- }
-
- if ( childrenDisplayGroup.selectionChanged() && !childrenDisplayGroup.contentsChanged() )
- {
- selectFromDisplayGroup( childrenDisplayGroup );
- }
-
- titlesGroupChanged = false;
- childrenGroupChanged = false;
- }
-
- /**
- * Called by subjectChanged() in response to an external change in the titles display group.
- */
- void populateFromDisplayGroup( List displayedObjects )
- {
- // trigger processRecentChanges
- willChange();
-
- // workaround: see below
- int previousCount = rootNode.previouslyDisplayedObjects.length;
-
- // update the root node
- rootNode.setObjectArray( displayedObjects );
-
- //FIXME: workaround for what appears to be a bug in JTree:
- // if root node is not visible and has no children, insert events are ignored
- if ( previousCount == 0 )
- {
- fireRootStructureChanged();
- }
- }
-
- /**
- * Package access so DisplayGroupNode can replace the selection after an update.
- */
- void selectFromDisplayGroup( EODisplayGroup aDisplayGroup )
- { // System.out.println( "selectFromDisplayGroup: " + aDisplayGroup.selectedObjects() );
-
- removeAsListener();
-
- TreePath[] paths = selectionModel.getSelectionPaths();
- NSArray selectedObjects = aDisplayGroup.selectedObjects();
-
- // assemble current selection list
- List treeSelection = new LinkedList();
- if ( paths != null )
- {
- for ( int i = 0; i < paths.length; i ++ )
- {
- treeSelection.add(
- ((DisplayGroupNode)paths[i].getLastPathComponent()).getUserObject() );
- }
- }
-
- if ( ! ( selectedObjects.size() == treeSelection.size()
- && treeSelection.containsAll( selectedObjects ) ) )
- {
- selectionModel.clearSelection();
-
- // workaround to select root node from valueChanged()
- if ( pleaseSelectRootNode )
- {
- selectionModel.addSelectionPath( new TreePath( this.getRoot() ) );
- pleaseSelectRootNode = false;
- }
-
- //FIXME: display group is assumed to have only one instance of each object
- for ( int i = 0; i < selectedObjects.count(); i++ )
- {
- //FIXME: selects only the first instance for now
- //selectionModel.addSelectionPaths(
- // getPathsForObject(
- // selectedObjects.objectAtIndex( i ) ) );
- selectionModel.addSelectionPath(
- getPathForObject(
- selectedObjects.objectAtIndex( i ) ) );
- }
- }
-
- addAsListener();
- }
-
- /**
- * Returns the first node found that represents the
- * specified object, or null if not found.
- * This implementation simply calls getPathForObject.
- */
- public Object getNodeForObject( Object anObject )
- {
- TreePath result = getPathForObject( anObject );
- if ( result != null )
- {
- return result.getLastPathComponent();
- }
- return null;
- }
-
- /**
- * Returns the object represented by the specified node
- * which must be a display group node from this tree.
- */
- public Object getObjectForNode( Object aNode )
- {
- if ( aNode instanceof DisplayGroupNode )
- {
- return ((DisplayGroupNode)aNode).getUserObject();
- }
-
- // not a display group node
- throw new WotonomyException(
- "Not a display group node: " + aNode );
- }
-
- /**
- * Returns the tree path for the specified node,
- * which must be a display group node from this tree.
- */
- public TreePath getPathForNode( Object aNode )
- {
- if ( aNode instanceof DisplayGroupNode )
- {
- return ((DisplayGroupNode)aNode).treePath();
- }
-
- // not a display group node
- throw new WotonomyException(
- "Not a display group node: " + aNode );
- }
-
- /**
- * Returns the first tree path for the node that represents
- * the specified object, or null if the object does not exist in this tree.
- * This implementation does a breadth-first search of the tree
- * for the object, looking only at nodes that have been loaded.
- * This means that if the object does not exist in the tree,
- * the entire tree must be traversed.
- */
- public TreePath getPathForObject( Object anObject )
- {
- return getPathForObjectInPath( anObject, new TreePath( this.getRoot() ) );
- }
-
- /**
- * Returns the tree path for the node that represents
- * the specified object,
- * or null if the object does not exist in this tree.
- * This implementation does a breadth-first search of the tree
- * for the object, looking only at nodes that have been loaded.
- * This means that the entire tree is traversed.
- */
- public TreePath[] getPathsForObject( Object anObject )
- {
- return getPathsForObjectInPath( anObject, new TreePath( this.getRoot() ) );
- }
-
- /**
- * A breadth-first search of the tree starting
- * at the specified tree path, comparing by reference.
- * Returns immediately with the first match.
- */
- private TreePath getPathForObjectInPath( Object anObject, TreePath aPath )
- {
- LinkedList queue = new LinkedList();
-
- // add the specified path
- queue.addLast( aPath );
-
- return processQueue( anObject, queue, null );
- }
-
- /**
- * A breadth-first search of the tree starting
- * at the specified tree path, comparing by reference.
- * The entire branch is searched before returning
- * an array of all matches.
- */
- private TreePath[] getPathsForObjectInPath( Object anObject, TreePath aPath )
- {
- LinkedList queue = new LinkedList();
-
- // add the specified path
- queue.addLast( aPath );
-
- List result = new LinkedList();
- processQueue( anObject, queue, result );
- TreePath[] paths = new TreePath[ result.size() ];
- for ( int i = 0; i < paths.length; i++ )
- {
- paths[i] = (TreePath) result.get(i);
- }
- return paths;
- }
-
- /**
- * Processes the specified queue, appending results to aResult if it exists,
- * or returning immediately with a TreePath is aResult is null.
- */
- private TreePath processQueue( Object anObject, LinkedList aQueue, List aResult )
- {
- TreePath path;
- while ( ! aQueue.isEmpty() )
- {
- path = (TreePath) aQueue.removeFirst();
- path = checkNode( anObject, path, aQueue );
- if ( path != null )
- {
- if ( aResult != null )
- {
- aResult.add( path );
- }
- else
- {
- return path;
- }
- }
- }
- return null;
- }
-
- /**
- * Compares the specified object by reference each of the children of
- * the node at the end of the specified tree path, adding nodes that
- * do not match but have fetched object to the end of the specified queue.
- * Returns the path of the first child node that matches the specified object,
- * or null if no match was found.
- */
- private TreePath checkNode( Object anObject, TreePath aPath, LinkedList aQueue )
- {
- TreePath result = null;
- Object child;
- Object parent = aPath.getLastPathComponent();
- int count = getChildCount( parent );
-
- for ( int i = 0; i < count; i++ )
- {
- child = getChild( parent, i );
-
- // add to queue if node has fetched children
- if ( ((DisplayGroupNode)child).isFetched )
- {
- aQueue.addLast( aPath.pathByAddingChild( child ) );
- }
-
- // compares by reference
- if ( ((DisplayGroupNode)child).object() == anObject )
- {
- // assumes same object cannot be in display group twice
- result = aPath.pathByAddingChild( child );
+ * TreeModelAssociation binds a JTree or similar component that uses a TreeModel
+ * to a display group's list of displayable objects, each of which may have a
+ * list of child objects managed by another display group, and so on.
+ * TreeModelAssociation works exactly like a ListAssociation, with the
+ * additional capability to specify a "Children" aspect, that will allow child
+ * objects to be retrieved from a parent display group.
+ *
+ * <ul>
+ *
+ * <li>titles: a property convertable to a string for display in the nodes of
+ * the tree. The objects in the bound display group will be used to populate the
+ * initial root nodes of the tree (more accurately, children of the offscreen
+ * root node in the tree).</li>
+ *
+ * <li>children: a property of a node value that returns zero, one or many
+ * objects, each of which will correspond to a child node for the corresponding
+ * node in the tree. The data source of the bound display group is replaced a
+ * data source that populates the display group with the selection in the tree
+ * component as determined by calling fetchObjectsIntoChildrenGroup. If this
+ * aspect is not bound, the tree behaves like a list. <br>
+ * <br>
+ * Binding this aspect with a null display group is the same as binding it with
+ * the titles display group. In this configuration the contents of the titles
+ * display group will be replaced with the selection in the tree component, as
+ * specified above, replacing the existing data source. <br>
+ * <br>
+ * In that case, the display groups for the nodes in the tree will still use the
+ * original data source for resolving their children key, and programmatically
+ * setting the contents of the display group will still repopulate the root
+ * nodes of the tree.</li>
+ *
+ * <li>isLeaf: a property of a node value that returns a value convertable to a
+ * boolean value (aside from an actual boolean value, zeroes evaluate to true,
+ * as does any String containing "yes" or "true" or that is convertable to a
+ * number equal to zero; other values evaluate to false). <br>
+ * <br>
+ * If the isLeaf aspect is not bound, the tree must force nodes to load their
+ * children to determine whether they are leaf nodes (in effect loading the
+ * grandchildren for any expanded node). If bound, child loading is deferred
+ * until the node is actually expanded. <br>
+ * <br>
+ * For example, binding this value to a null display group and the key "false"
+ * will result in a deferred-loading tree that works much like Windows
+ * Explorer's network volume browser - all nodes appear with "pluses" until they
+ * are expanded. <br>
+ * <br>
+ * Note that the display group is ignored: the property will be applied directly
+ * to the object corresponding to the node.</li>
+ *
+ * </ul>
+ *
+ * This class acts as the TreeModel for the controlled component: calling
+ * yourcomponent.getModel() will return this association. The tree model methods
+ * on this class are public and may be used to affect changes on the controlled
+ * components.<br>
+ * <br>
+ *
+ * The titles display group's contents are inserted into a new display group
+ * that acts as the root node. After that point, changes in the titles display
+ * group will cause the tree model to reset itself, creating a new display group
+ * for the root node. <br>
+ * <br>
+ *
+ * If a separate display group is bound to the children aspect, it will be used
+ * to hold the selected objects and their siblings and selection will be
+ * maintained there, and the titles display group selection will not be updated.
+ * Any editing or detail associations should in that case be attached to the
+ * children display group, not the titles group. <br>
+ * <br>
+ *
+ * Each node in the tree is an EODisplayGroup that contains the child objects of
+ * the object it represents in the tree. These objects can be programmatically
+ * inserted, updated, or removed using DisplayGroup methods. Each node's takes
+ * its parent group's sortOrderings until a sort ordering is explicitly
+ * specified - setting a sort ordering to null will resume using the parent
+ * group's sort ordering.<br>
+ * <br>
+ *
+ * Each node in the tree also implements MutableTreeNode. The value that a node
+ * represents is the titles property value of the object in the parent's
+ * displayed objects list at the index corresponding to the index of the node.
+ * Calling toString on a node returns the string representation of the titles
+ * property value, and setUserObject will update that value directly in the
+ * corresponding object. Moving a node from one parent to another will remove
+ * the actual object in the parent display group and insert it into the
+ * destination display group.<br>
+ * <br>
+ *
+ * In short, any nodes obtained from this class' implementation of TreeModel may
+ * be cast as either EODisplayGroup or MutableTreeNode and maybe be
+ * programmatically manipulated in either manner.
+ *
+ * @author michael@mpowers.net
+ * @author $Author: cgruber $
+ * @version $Revision: 904 $
+ */
+public class TreeModelAssociation extends EOAssociation implements TreeModel, TreeSelectionListener {
+ static final NSArray aspects = new NSArray(new Object[] { TitlesAspect, ChildrenAspect, IsLeafAspect });
+ static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature });
+ static final NSArray objectKeysTaken = new NSArray(new Object[] { "model" });
+
+ private final static NSSelector getSelectionModel = new NSSelector("getSelectionModel");
+ private final static NSSelector setModel = new NSSelector("setModel", new Class[] { TreeModel.class });
+
+ EODisplayGroup titlesDisplayGroup, childrenDisplayGroup, leafDisplayGroup;
+ String titlesKey, childrenKey, leafKey;
+ DisplayGroupNode rootNode;
+ Vector listeners;
+ Object rootLabel;
+
+ TreeSelectionModel selectionModel;
+ boolean selectionPaintedImmediately;
+
+ boolean insertingChild;
+ boolean insertingAfter;
+
+ EOObserverProxy recentChangesObserver;
+
+ private boolean pleaseSelectRootNode;
+
+ /**
+ * Constructor expecting a JTree or any other object that has void
+ * setModel(TreeModel) and TreeModel getSelectionModel() methods. This tree
+ * association will be used for the TreeModel. The root node will be labeled
+ * "Root". <br>
+ * <br>
+ *
+ * As an alternate way to use a TreeModelAssociation, you may pass a
+ * TreeSelectionModel to the constructor and then manually set your component to
+ * use this class as its TreeModel.
+ */
+ public TreeModelAssociation(Object anObject) {
+ super(anObject);
+
+ titlesDisplayGroup = null;
+ titlesKey = null;
+ childrenDisplayGroup = null;
+ childrenKey = null;
+ leafDisplayGroup = null;
+ leafKey = null;
+ listeners = new Vector();
+
+ selectionPaintedImmediately = false;
+
+ // after display group nodes process recent changes
+ recentChangesObserver = new EOObserverProxy(this, new NSSelector("processRecentChanges"),
+ EODelayedObserver.ObserverPrioritySixth);
+ EOObserverCenter.addObserver(recentChangesObserver, this);
+
+ insertingChild = true;
+ insertingAfter = true;
+
+ pleaseSelectRootNode = false;
+
+ rootLabel = "Root";
+ rootNode = createNode(null, null);
+ }
+
+ /**
+ * Constructor expecting a JTree or similar component and specifying a label for
+ * the root node.
+ */
+ public TreeModelAssociation(Object anObject, Object aRootLabel) {
+ this(anObject);
+ rootLabel = aRootLabel;
+ rootNode.setUserObject(aRootLabel);
+ }
+
+ /**
+ * Gets the current root label.
+ */
+ public Object rootLabel() {
+ return rootLabel;
+ }
+
+ /**
+ * Gets the current root label.
+ */
+ public Object getRootLabel() {
+ return rootLabel();
+ }
+
+ /**
+ * Sets the root label.
+ */
+ public void setRootLabel(Object aLabel) {
+ rootLabel = aLabel;
+ }
+
+ /**
+ * Returns a List of aspect signatures whose contents correspond with the
+ * aspects list. Each element is a string whose characters represent a
+ * capability of the corresponding aspect.
+ * <ul>
+ * <li>"A" attribute: the aspect can be bound to an attribute.</li>
+ * <li>"1" to-one: the aspect can be bound to a property that returns a single
+ * object.</li>
+ * <li>"M" to-one: the aspect can be bound to a property that returns multiple
+ * objects.</li>
+ * </ul>
+ * An empty signature "" means that the aspect can bind without needing a key.
+ * This implementation returns "A1M" for each element in the aspects array.
+ */
+ public static NSArray aspectSignatures() {
+ return aspectSignatures;
+ }
+
+ /**
+ * Returns a List that describes the aspects supported by this class. Each
+ * element in the list is the string name of the aspect. This implementation
+ * returns an empty list.
+ */
+ public static NSArray aspects() {
+ return aspects;
+ }
+
+ /**
+ * Returns a List of EOAssociation subclasses that, for the objects that are
+ * usable for this association, are less suitable than this association.
+ */
+ public static NSArray associationClassesSuperseded() {
+ return new NSArray();
+ }
+
+ /**
+ * Returns whether this class can control the specified object.
+ */
+ public static boolean isUsableWithObject(Object anObject) {
+ return setModel.implementedByObject(anObject);
+ }
+
+ /**
+ * Returns a List of properties of the controlled object that are controlled by
+ * this class. For example, "stringValue", or "selected".
+ */
+ public static NSArray objectKeysTaken() {
+ return objectKeysTaken;
+ }
+
+ /**
+ * Returns the aspect that is considered primary or default. This is typically
+ * "value" or somesuch.
+ */
+ public static String primaryAspect() {
+ return TitlesAspect;
+ }
+
+ /**
+ * Returns whether this association can bind to the specified display group on
+ * the specified key for the specified aspect.
+ */
+ public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) {
+ return (aspects.containsObject(anAspect));
+ }
+
+ /**
+ * Binds the specified aspect of this association to the specified key on the
+ * specified display group.
+ */
+ public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) {
+ if (TitlesAspect.equals(anAspect)) {
+ titlesDisplayGroup = aDisplayGroup;
+ titlesKey = aKey;
+ }
+ if (ChildrenAspect.equals(anAspect)) {
+ childrenDisplayGroup = aDisplayGroup;
+ childrenKey = aKey;
+ }
+ if (IsLeafAspect.equals(anAspect)) {
+ leafDisplayGroup = aDisplayGroup;
+ leafKey = aKey;
+ }
+ if (childrenDisplayGroup == null) {
+ childrenDisplayGroup = titlesDisplayGroup;
+ }
+ super.bindAspect(anAspect, aDisplayGroup, aKey);
+ }
+
+ /**
+ * Establishes a connection between this association and the controlled object.
+ * Subclasses should begin listening for events from their controlled object
+ * here.
+ */
+ public void establishConnection() {
+ if (titlesDisplayGroup == null) {
+ throw new WotonomyException("TreeModelAssociation: Titles aspect must be bound");
+ }
+
+ // populate the root node
+ rootNode = createNode(titlesDisplayGroup, null);
+ rootNode.setObjectArray(titlesDisplayGroup.displayedObjects());
+ rootNode.setSortOrderings(titlesDisplayGroup.sortOrderings());
+
+ EODataSource dataSource = childrenDisplayGroup.dataSource();
+ if (dataSource == null)
+ dataSource = titlesDisplayGroup.dataSource();
+ while (dataSource instanceof DelegatingTreeDataSource) { // unwrap any existing delegating data sources
+ dataSource = ((DelegatingTreeDataSource) dataSource).delegateDataSource;
+ }
+ // create a new delegating data source
+ childrenDisplayGroup.setDataSource(new DelegatingTreeDataSource(this, dataSource));
+
+ // TODO: find out why omitting this line causes weird selection behavior
+ childrenDisplayGroup.setSortOrderings(new NSArray());
+
+ // check for alternate usage
+ if (object() instanceof TreeSelectionModel) {
+ selectionModel = (TreeSelectionModel) object();
+ } else // use specified object
+ {
+ try {
+ setModel.invoke(object(), new Object[] { this });
+ selectionModel = (TreeSelectionModel) getSelectionModel.invoke(object(), new Object[] {});
+ } catch (Exception exc) {
+ throw new WotonomyException(exc);
+ }
+ }
+
+ addAsListener();
+ super.establishConnection();
+ /*
+ * fireRootStructureChanged();
+ *
+ * // titlesGroupChanged = true; // subjectChanged();
+ *
+ * // update the children group removeAsListener();
+ * childrenDisplayGroup.fetch(); addAsListener();
+ *
+ * // update selection selectFromDisplayGroup( titlesDisplayGroup );
+ */
+ }
+
+ protected void fireRootStructureChanged() {
+ int count = rootNode.displayedObjects().count();
+ int[] childIndices = new int[count];
+ Object[] children = new Object[count];
+ for (int i = 0; i < count; i++) {
+ childIndices[i] = i;
+ children[i] = rootNode.getChildNodeAt(i);
+ }
+
+ // must fire a tree structure changed with children,
+ // otherwise the tree gets weird selection behavior
+ fireTreeStructureChanged(this, new Object[] { rootNode }, childIndices, children);
+ }
+
+ /**
+ * Breaks the connection between this association and its object. Override to
+ * stop listening for events from the object.
+ */
+ public void breakConnection() {
+ if (childrenDisplayGroup != null) {
+ if (childrenDisplayGroup.dataSource() instanceof DelegatingTreeDataSource) {
+ if (titlesDisplayGroup == childrenDisplayGroup) {
+ titlesDisplayGroup.setDataSource(
+ ((DelegatingTreeDataSource) childrenDisplayGroup.dataSource()).delegateDataSource);
+ } else {
+ childrenDisplayGroup.setDataSource(null);
+ }
+ }
+ }
+
+ removeAsListener();
+ super.breakConnection();
+ }
+
+ protected void addAsListener() {
+ isListening = true;
+ selectionModel.addTreeSelectionListener(this);
+ }
+
+ protected void removeAsListener() {
+ isListening = false;
+ selectionModel.removeTreeSelectionListener(this);
+ }
+
+ protected boolean isListening = false;
+ private boolean pleaseIgnore = false;
+ protected boolean titlesGroupChanged = false;
+ protected boolean childrenGroupChanged = false;
+
+ /**
+ * Overridden to better discriminate what is changed.
+ */
+ public void objectWillChange(Object anObject) {
+ if (!isListening)
+ return;
+
+ if (anObject == titlesDisplayGroup) {
+ titlesGroupChanged = true;
+ }
+ if (anObject == childrenDisplayGroup) {
+ childrenGroupChanged = true;
+ if (childrenDisplayGroup.qualifier() != null) {
+ if ((rootNode.qualifier() == null) || !childrenDisplayGroup.qualifier().equals(rootNode.qualifier())) {
+ // quietly move qualifier from children group to root node
+ rootNode.setQualifier(childrenDisplayGroup.qualifier());
+ childrenDisplayGroup.setQualifier(null);
+ rootNode.updateDisplayedObjects();
+ }
+ }
+ }
+ super.objectWillChange(anObject);
+ }
+
+ /**
+ * Called when either the selection or the contents of an associated display
+ * group have changed.
+ */
+ public void subjectChanged() {
+ // titles aspect
+ if (titlesGroupChanged) {
+ if (titlesDisplayGroup.contentsChanged()) {
+ NSArray displayedObjects = titlesDisplayGroup.displayedObjects();
+ NSArray childrenObjects;
+ if (titlesDisplayGroup != childrenDisplayGroup
+ || displayedObjects.count() != (childrenObjects = objectsFetchedIntoChildrenGroup()).count()
+ || !displayedObjects.containsAll(childrenObjects)) {
+ populateFromDisplayGroup(displayedObjects);
+ }
+ }
+ }
+
+ if (childrenDisplayGroup.selectionChanged() && !childrenDisplayGroup.contentsChanged()) {
+ selectFromDisplayGroup(childrenDisplayGroup);
+ }
+
+ titlesGroupChanged = false;
+ childrenGroupChanged = false;
+ }
+
+ /**
+ * Called by subjectChanged() in response to an external change in the titles
+ * display group.
+ */
+ void populateFromDisplayGroup(List displayedObjects) {
+ // trigger processRecentChanges
+ willChange();
+
+ // workaround: see below
+ int previousCount = rootNode.previouslyDisplayedObjects.length;
+
+ // update the root node
+ rootNode.setObjectArray(displayedObjects);
+
+ // FIXME: workaround for what appears to be a bug in JTree:
+ // if root node is not visible and has no children, insert events are ignored
+ if (previousCount == 0) {
+ fireRootStructureChanged();
+ }
+ }
+
+ /**
+ * Package access so DisplayGroupNode can replace the selection after an update.
+ */
+ void selectFromDisplayGroup(EODisplayGroup aDisplayGroup) { // System.out.println( "selectFromDisplayGroup: " +
+ // aDisplayGroup.selectedObjects() );
+
+ removeAsListener();
+
+ TreePath[] paths = selectionModel.getSelectionPaths();
+ NSArray selectedObjects = aDisplayGroup.selectedObjects();
+
+ // assemble current selection list
+ List treeSelection = new LinkedList();
+ if (paths != null) {
+ for (int i = 0; i < paths.length; i++) {
+ treeSelection.add(((DisplayGroupNode) paths[i].getLastPathComponent()).getUserObject());
+ }
+ }
+
+ if (!(selectedObjects.size() == treeSelection.size() && treeSelection.containsAll(selectedObjects))) {
+ selectionModel.clearSelection();
+
+ // workaround to select root node from valueChanged()
+ if (pleaseSelectRootNode) {
+ selectionModel.addSelectionPath(new TreePath(this.getRoot()));
+ pleaseSelectRootNode = false;
+ }
+
+ // FIXME: display group is assumed to have only one instance of each object
+ for (int i = 0; i < selectedObjects.count(); i++) {
+ // FIXME: selects only the first instance for now
+ // selectionModel.addSelectionPaths(
+ // getPathsForObject(
+ // selectedObjects.objectAtIndex( i ) ) );
+ selectionModel.addSelectionPath(getPathForObject(selectedObjects.objectAtIndex(i)));
+ }
+ }
+
+ addAsListener();
+ }
+
+ /**
+ * Returns the first node found that represents the specified object, or null if
+ * not found. This implementation simply calls getPathForObject.
+ */
+ public Object getNodeForObject(Object anObject) {
+ TreePath result = getPathForObject(anObject);
+ if (result != null) {
+ return result.getLastPathComponent();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the object represented by the specified node which must be a display
+ * group node from this tree.
+ */
+ public Object getObjectForNode(Object aNode) {
+ if (aNode instanceof DisplayGroupNode) {
+ return ((DisplayGroupNode) aNode).getUserObject();
+ }
+
+ // not a display group node
+ throw new WotonomyException("Not a display group node: " + aNode);
+ }
+
+ /**
+ * Returns the tree path for the specified node, which must be a display group
+ * node from this tree.
+ */
+ public TreePath getPathForNode(Object aNode) {
+ if (aNode instanceof DisplayGroupNode) {
+ return ((DisplayGroupNode) aNode).treePath();
+ }
+
+ // not a display group node
+ throw new WotonomyException("Not a display group node: " + aNode);
+ }
+
+ /**
+ * Returns the first tree path for the node that represents the specified
+ * object, or null if the object does not exist in this tree. This
+ * implementation does a breadth-first search of the tree for the object,
+ * looking only at nodes that have been loaded. This means that if the object
+ * does not exist in the tree, the entire tree must be traversed.
+ */
+ public TreePath getPathForObject(Object anObject) {
+ return getPathForObjectInPath(anObject, new TreePath(this.getRoot()));
+ }
+
+ /**
+ * Returns the tree path for the node that represents the specified object, or
+ * null if the object does not exist in this tree. This implementation does a
+ * breadth-first search of the tree for the object, looking only at nodes that
+ * have been loaded. This means that the entire tree is traversed.
+ */
+ public TreePath[] getPathsForObject(Object anObject) {
+ return getPathsForObjectInPath(anObject, new TreePath(this.getRoot()));
+ }
+
+ /**
+ * A breadth-first search of the tree starting at the specified tree path,
+ * comparing by reference. Returns immediately with the first match.
+ */
+ private TreePath getPathForObjectInPath(Object anObject, TreePath aPath) {
+ LinkedList queue = new LinkedList();
+
+ // add the specified path
+ queue.addLast(aPath);
+
+ return processQueue(anObject, queue, null);
+ }
+
+ /**
+ * A breadth-first search of the tree starting at the specified tree path,
+ * comparing by reference. The entire branch is searched before returning an
+ * array of all matches.
+ */
+ private TreePath[] getPathsForObjectInPath(Object anObject, TreePath aPath) {
+ LinkedList queue = new LinkedList();
+
+ // add the specified path
+ queue.addLast(aPath);
+
+ List result = new LinkedList();
+ processQueue(anObject, queue, result);
+ TreePath[] paths = new TreePath[result.size()];
+ for (int i = 0; i < paths.length; i++) {
+ paths[i] = (TreePath) result.get(i);
+ }
+ return paths;
+ }
+
+ /**
+ * Processes the specified queue, appending results to aResult if it exists, or
+ * returning immediately with a TreePath is aResult is null.
+ */
+ private TreePath processQueue(Object anObject, LinkedList aQueue, List aResult) {
+ TreePath path;
+ while (!aQueue.isEmpty()) {
+ path = (TreePath) aQueue.removeFirst();
+ path = checkNode(anObject, path, aQueue);
+ if (path != null) {
+ if (aResult != null) {
+ aResult.add(path);
+ } else {
+ return path;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Compares the specified object by reference each of the children of the node
+ * at the end of the specified tree path, adding nodes that do not match but
+ * have fetched object to the end of the specified queue. Returns the path of
+ * the first child node that matches the specified object, or null if no match
+ * was found.
+ */
+ private TreePath checkNode(Object anObject, TreePath aPath, LinkedList aQueue) {
+ TreePath result = null;
+ Object child;
+ Object parent = aPath.getLastPathComponent();
+ int count = getChildCount(parent);
+
+ for (int i = 0; i < count; i++) {
+ child = getChild(parent, i);
+
+ // add to queue if node has fetched children
+ if (((DisplayGroupNode) child).isFetched) {
+ aQueue.addLast(aPath.pathByAddingChild(child));
+ }
+
+ // compares by reference
+ if (((DisplayGroupNode) child).object() == anObject) {
+ // assumes same object cannot be in display group twice
+ result = aPath.pathByAddingChild(child);
//System.out.println( "TRUE: " + ((DisplayGroupNode)child).object() + " == " + anObject );
- }
+ }
// else
// {
//System.out.println( ((DisplayGroupNode)child).object() + " != " + anObject );
// }
- }
- return result;
- }
-
- // interface TreeSelectionListener
-
- public void valueChanged(TreeSelectionEvent e)
- {
- if ( ! isListening ) return;
- selectFromSelectionModel();
- }
-
- /**
- * Determines whether the selection should be painted
- * immediately after the user clicks and therefore
- * before the children display group is updated.
- * When the children group is bound to many associations
- * or is bound to a master-detail association, updating
- * the display group can take a perceptibly long time.
- * This property defaults to false.
- * @see #setSelectionPaintedImmediately
- */
- public boolean isSelectionPaintedImmediately()
- {
- return selectionPaintedImmediately;
- }
-
- /**
- * Sets whether the selection should be painted immediately.
- * Setting this property to true will let the tree paint
- * first before the display group is updated.
- * This means that any tree selection listers will
- * also be notified before the display group is updated
- * and will have to invokeLater if they want to see the
- * updated display group.
- */
- public void setSelectionPaintedImmediately( boolean isImmediate )
- {
- selectionPaintedImmediately = isImmediate;
- }
-
- /**
- * Package access so DisplayGroupNode can replace the selection.
- * Returns the display group containing the current selection: titles or children.
- */
- EODisplayGroup selectFromSelectionModel()
- { // System.out.print( "selectFromSelectionModel: " );
- removeAsListener();
- DisplayGroupNode node;
- TreePath parentPath;
- TreePath[] selectedPaths = selectionModel.getSelectionPaths();
- NSMutableArray selectionList = new NSMutableArray();
- if ( selectedPaths != null )
- {
- for ( int i = 0; i < selectedPaths.length; i++ )
- {
- // root node is zero - ignore root node
- if ( ( selectedPaths[i].getLastPathComponent() == rootNode ) )
- {
- // select root in selectFromDisplayGroup()
- pleaseSelectRootNode = true;
- }
- else
- {
- node = (DisplayGroupNode)
- selectedPaths[i].getLastPathComponent();
- Object o = node.object();
-
- if ( selectionList.indexOfIdenticalObject(o) == NSArray.NotFound )
- {
- selectionList.addObject( o );
- }
- }
- }
- }
- childrenDisplayGroup.fetch(); //note that we're not currently listening for changes
- if ( ! childrenDisplayGroup.selectObjectsIdenticalTo( selectionList ) )
- {
- addAsListener(); // because we don't have a listener stack
- selectFromDisplayGroup( childrenDisplayGroup );
- removeAsListener();
- }
- addAsListener();
- return childrenDisplayGroup; // titles is now children if children not explicitly set
- }
-
- // interface TreeModel
-
- public Object getRoot()
- {
- return rootNode;
- }
-
- public Object getChild(Object parent, int index)
- {
- // interestingly, this gets called by
- // BasicTreeUI.paintVerticalPartOfLeg for
- // the last child of each expanded tree node.
- Object result = ((DisplayGroupNode)parent).getChildNodeAt( index );
+ }
+ return result;
+ }
+
+ // interface TreeSelectionListener
+
+ public void valueChanged(TreeSelectionEvent e) {
+ if (!isListening)
+ return;
+ selectFromSelectionModel();
+ }
+
+ /**
+ * Determines whether the selection should be painted immediately after the user
+ * clicks and therefore before the children display group is updated. When the
+ * children group is bound to many associations or is bound to a master-detail
+ * association, updating the display group can take a perceptibly long time.
+ * This property defaults to false.
+ *
+ * @see #setSelectionPaintedImmediately
+ */
+ public boolean isSelectionPaintedImmediately() {
+ return selectionPaintedImmediately;
+ }
+
+ /**
+ * Sets whether the selection should be painted immediately. Setting this
+ * property to true will let the tree paint first before the display group is
+ * updated. This means that any tree selection listers will also be notified
+ * before the display group is updated and will have to invokeLater if they want
+ * to see the updated display group.
+ */
+ public void setSelectionPaintedImmediately(boolean isImmediate) {
+ selectionPaintedImmediately = isImmediate;
+ }
+
+ /**
+ * Package access so DisplayGroupNode can replace the selection. Returns the
+ * display group containing the current selection: titles or children.
+ */
+ EODisplayGroup selectFromSelectionModel() { // System.out.print( "selectFromSelectionModel: " );
+ removeAsListener();
+ DisplayGroupNode node;
+ TreePath parentPath;
+ TreePath[] selectedPaths = selectionModel.getSelectionPaths();
+ NSMutableArray selectionList = new NSMutableArray();
+ if (selectedPaths != null) {
+ for (int i = 0; i < selectedPaths.length; i++) {
+ // root node is zero - ignore root node
+ if ((selectedPaths[i].getLastPathComponent() == rootNode)) {
+ // select root in selectFromDisplayGroup()
+ pleaseSelectRootNode = true;
+ } else {
+ node = (DisplayGroupNode) selectedPaths[i].getLastPathComponent();
+ Object o = node.object();
+
+ if (selectionList.indexOfIdenticalObject(o) == NSArray.NotFound) {
+ selectionList.addObject(o);
+ }
+ }
+ }
+ }
+ childrenDisplayGroup.fetch(); // note that we're not currently listening for changes
+ if (!childrenDisplayGroup.selectObjectsIdenticalTo(selectionList)) {
+ addAsListener(); // because we don't have a listener stack
+ selectFromDisplayGroup(childrenDisplayGroup);
+ removeAsListener();
+ }
+ addAsListener();
+ return childrenDisplayGroup; // titles is now children if children not explicitly set
+ }
+
+ // interface TreeModel
+
+ public Object getRoot() {
+ return rootNode;
+ }
+
+ public Object getChild(Object parent, int index) {
+ // interestingly, this gets called by
+ // BasicTreeUI.paintVerticalPartOfLeg for
+ // the last child of each expanded tree node.
+ Object result = ((DisplayGroupNode) parent).getChildNodeAt(index);
//((DisplayGroupNode)parent).suppressRecentChangeProcessing();
-return result;
+ return result;
// return ((DisplayGroupNode)parent).getChildNodeAt( index );
- }
+ }
- public int getChildCount(Object parent)
- {
- int result = ((DisplayGroupNode)parent).getChildCount();
+ public int getChildCount(Object parent) {
+ int result = ((DisplayGroupNode) parent).getChildCount();
//((DisplayGroupNode)parent).suppressRecentChangeProcessing();
-return result;
+ return result;
// return ((DisplayGroupNode)parent).getChildCount();
- }
+ }
- public boolean isLeaf(Object node)
- {
- boolean result = ((DisplayGroupNode)node).isLeaf();
+ public boolean isLeaf(Object node) {
+ boolean result = ((DisplayGroupNode) node).isLeaf();
//((DisplayGroupNode)node).suppressRecentChangeProcessing();
-return result;
+ return result;
// return ((DisplayGroupNode)node).isLeaf();
- }
-
- /**
- * Returns whether this node is visible in the UI.
- * This implementation returns true.
- * <br><br>
- * Subclasses should return false if they can
- * determine that the node is not displayed or
- * expanded or otherwise visible. Non-visible
- * nodes will fetch only when they are shown.
- */
- public boolean isVisible(Object node)
- {
- return true;
- }
-
- public void valueForPathChanged(TreePath path, Object newValue)
- {
- ((DisplayGroupNode)path.getLastPathComponent()).setUserObject( newValue );
- }
-
- public int getIndexOfChild(Object parent, Object child)
- {
- int result = ((DisplayGroupNode)parent).getIndex( (DisplayGroupNode) child );
+ }
+
+ /**
+ * Returns whether this node is visible in the UI. This implementation returns
+ * true. <br>
+ * <br>
+ * Subclasses should return false if they can determine that the node is not
+ * displayed or expanded or otherwise visible. Non-visible nodes will fetch only
+ * when they are shown.
+ */
+ public boolean isVisible(Object node) {
+ return true;
+ }
+
+ public void valueForPathChanged(TreePath path, Object newValue) {
+ ((DisplayGroupNode) path.getLastPathComponent()).setUserObject(newValue);
+ }
+
+ public int getIndexOfChild(Object parent, Object child) {
+ int result = ((DisplayGroupNode) parent).getIndex((DisplayGroupNode) child);
//((DisplayGroupNode)parent).suppressRecentChangeProcessing();
-return result;
+ return result;
// return ((DisplayGroupNode)parent).getIndex( (DisplayGroupNode) child );
- }
-
- public void addTreeModelListener(TreeModelListener aListener)
- {
- listeners.add( aListener );
- }
- public void removeTreeModelListener(TreeModelListener aListener)
- {
- listeners.remove( aListener );
- }
-
- /**
- * Fires a tree nodes changed event to all listeners.
- * Provided as a convenience if you need to make manual
- * changes to the tree model.
- */
- public void fireTreeNodesChanged(Object source,
- Object[] path,
- int[] childIndices,
- Object[] children)
- {
-
- willChange(); // queue processRecentChanges
- TreeModelEvent event = new TreeModelEvent(
- source, path, childIndices, children );
+ }
+
+ public void addTreeModelListener(TreeModelListener aListener) {
+ listeners.add(aListener);
+ }
+
+ public void removeTreeModelListener(TreeModelListener aListener) {
+ listeners.remove(aListener);
+ }
+
+ /**
+ * Fires a tree nodes changed event to all listeners. Provided as a convenience
+ * if you need to make manual changes to the tree model.
+ */
+ public void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) {
+
+ willChange(); // queue processRecentChanges
+ TreeModelEvent event = new TreeModelEvent(source, path, childIndices, children);
//System.out.println( "fireTreeNodesChanged: " + event );
- Enumeration it = listeners.elements();
- while ( it.hasMoreElements() )
- {
- try
- {
- ((TreeModelListener)it.nextElement()).treeNodesChanged( event );
- }
- catch ( Exception exc )
- {
- System.out.println( "TreeModelAssociation.fireTreeNodesChanged: caught: " + exc );
- System.out.println( "Source:" + source );
- System.out.println( "Path:" );
- for ( int i = 0; i < path.length; i++ )
- {
- System.out.print( path[i] + "-" );
- }
- System.out.println();
- System.out.println( "Indices:" );
- for ( int i = 0; i < childIndices.length; i++ )
- {
- System.out.print( childIndices[i] + "-" );
- }
- System.out.println();
- System.out.println( "Children:" );
- for ( int i = 0; i < children.length; i++ )
- {
- System.out.print( children[i] + "-" );
- }
- System.out.println();
- exc.printStackTrace();
- }
- }
- }
-
- /**
- * Fires a tree nodes inserted event to all listeners.
- * Provided as a convenience if you need to make manual
- * changes to the tree model.
- */
- public void fireTreeNodesInserted(Object source,
- Object[] path,
- int[] childIndices,
- Object[] children)
- {
-
- willChange(); // queue processRecentChanges
- TreeModelEvent event = new TreeModelEvent(
- source, path, childIndices, children );
+ Enumeration it = listeners.elements();
+ while (it.hasMoreElements()) {
+ try {
+ ((TreeModelListener) it.nextElement()).treeNodesChanged(event);
+ } catch (Exception exc) {
+ System.out.println("TreeModelAssociation.fireTreeNodesChanged: caught: " + exc);
+ System.out.println("Source:" + source);
+ System.out.println("Path:");
+ for (int i = 0; i < path.length; i++) {
+ System.out.print(path[i] + "-");
+ }
+ System.out.println();
+ System.out.println("Indices:");
+ for (int i = 0; i < childIndices.length; i++) {
+ System.out.print(childIndices[i] + "-");
+ }
+ System.out.println();
+ System.out.println("Children:");
+ for (int i = 0; i < children.length; i++) {
+ System.out.print(children[i] + "-");
+ }
+ System.out.println();
+ exc.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Fires a tree nodes inserted event to all listeners. Provided as a convenience
+ * if you need to make manual changes to the tree model.
+ */
+ public void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) {
+
+ willChange(); // queue processRecentChanges
+ TreeModelEvent event = new TreeModelEvent(source, path, childIndices, children);
//System.out.println( "fireTreeNodesInserted: " + event );
- Enumeration it = listeners.elements();
- while ( it.hasMoreElements() )
- {
- try
- {
- ((TreeModelListener)it.nextElement()).treeNodesInserted( event );
- }
- catch ( Exception exc )
- {
- System.out.println( "TreeModelAssociation.fireTreeNodesInserted: caught: " + exc );
- }
- }
- }
-
- /**
- * Fires a tree nodes removed event to all listeners.
- * Provided as a convenience if you need to make manual
- * changes to the tree model.
- */
- public void fireTreeNodesRemoved(Object source,
- Object[] path,
- int[] childIndices,
- Object[] children)
- {
-
- willChange(); // queue processRecentChanges
- TreeModelEvent event = new TreeModelEvent(
- source, path, childIndices, children );
+ Enumeration it = listeners.elements();
+ while (it.hasMoreElements()) {
+ try {
+ ((TreeModelListener) it.nextElement()).treeNodesInserted(event);
+ } catch (Exception exc) {
+ System.out.println("TreeModelAssociation.fireTreeNodesInserted: caught: " + exc);
+ }
+ }
+ }
+
+ /**
+ * Fires a tree nodes removed event to all listeners. Provided as a convenience
+ * if you need to make manual changes to the tree model.
+ */
+ public void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) {
+
+ willChange(); // queue processRecentChanges
+ TreeModelEvent event = new TreeModelEvent(source, path, childIndices, children);
//System.out.println( "fireTreeNodesRemoved: " + event );
- Enumeration it = listeners.elements();
- while ( it.hasMoreElements() )
- {
- try
- {
- //NOTE: removing nodes causes tree to fire selection change event
- // which confuses us if we're rearranging nodes (when sorting, for example).
- boolean wasListening = isListening;
- if ( wasListening ) isListening = false;
- ((TreeModelListener)it.nextElement()).treeNodesRemoved( event );
- if ( wasListening ) isListening = true;
- }
- catch ( Exception exc )
- {
- System.out.println( "TreeModelAssociation.fireTreeNodesRemoved: caught: " + exc );
- }
- }
- }
-
- /**
- * Fires a tree structure changed event to all listeners.
- * Provided as a convenience if you need to make manual
- * changes to the tree model.
- */
- public void fireTreeStructureChanged(Object source,
- Object[] path,
- int[] childIndices,
- Object[] children)
- {
-
- willChange(); // queue processRecentChanges
- TreeModelEvent event = new TreeModelEvent(
- source, path, childIndices, children );
+ Enumeration it = listeners.elements();
+ while (it.hasMoreElements()) {
+ try {
+ // NOTE: removing nodes causes tree to fire selection change event
+ // which confuses us if we're rearranging nodes (when sorting, for example).
+ boolean wasListening = isListening;
+ if (wasListening)
+ isListening = false;
+ ((TreeModelListener) it.nextElement()).treeNodesRemoved(event);
+ if (wasListening)
+ isListening = true;
+ } catch (Exception exc) {
+ System.out.println("TreeModelAssociation.fireTreeNodesRemoved: caught: " + exc);
+ }
+ }
+ }
+
+ /**
+ * Fires a tree structure changed event to all listeners. Provided as a
+ * convenience if you need to make manual changes to the tree model.
+ */
+ public void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) {
+
+ willChange(); // queue processRecentChanges
+ TreeModelEvent event = new TreeModelEvent(source, path, childIndices, children);
//System.out.println( "fireStructureChanged: " + event );
- Enumeration it = listeners.elements();
- while ( it.hasMoreElements() )
- {
- ((TreeModelListener)it.nextElement()).treeStructureChanged( event );
- }
- }
-
- /**
- * Creates and returns a new display group node.
- */
- public DisplayGroupNode createNode( EODisplayGroup aParentGroup, Object anObject )
- {
- return new MutableDisplayGroupNode( this, aParentGroup, anObject );
- }
-
- /**
- * Gets whether new objects programmatically inserted into the children
- * display group should be inserted as a child of the first selected node.
- * If false, new objects are inserted as siblings of the first selected node.
- * Default value is true.
- */
- public boolean isInsertingChild()
- {
- return insertingChild;
- }
-
- /**
- * Sets whether new objects programmatically inserted into the children
- * display group should be inserted as a child of the first selected node.
- * If false, new objects are inserted as siblings of the first selected node.
- * Default value is true.
- */
- public void setInsertingChild( boolean asChild )
- {
- insertingChild = asChild;
- }
-
- /**
- * Determines where new objects programmatically inserted into the children
- * display group should be inserted, based on the value of insertingChild.
- * If insertingChild, isInsertingAfter causes objects to be inserted at
- * the end of the selected node's child list; otherwise, objects are inserted
- * at the beginning of the list.
- * If inserting as a sibling, isInsertingAfter causes objects to be inserted
- * before the selected node in the selected node's parent's child list;
- * otherwise, objects are inserted after the selected node in the child list.
- * Default value is true.
- */
- public boolean isInsertingAfter()
- {
- return insertingAfter;
- }
-
- /**
- * Determines where new objects programmatically inserted into the children
- * display group should be inserted, based on the value of insertingChild.
- * If insertingChild, isInsertingAfter causes objects to be inserted at
- * the end of the selected node's child list; otherwise, objects are inserted
- * at the beginning of the list.
- * If inserting as a sibling, isInsertingAfter causes objects to be inserted
- * before the selected node in the selected node's parent's child list;
- * otherwise, objects are inserted after the selected node in the child list.
- * Default value is true.
- */
- public void setInsertingAfter( boolean after )
- {
- insertingAfter = after;
- }
-
- /**
- * Called to by the children group's data source when it receives
- * an insertObject message, usually after an object has been inserted
- * into the children display group.
- * Return the object that should be passed to the titles display
- * group's data source's implementation of insertObject, or return
- * null to prevent that method from being called. <br><br>
- * This implementation inserts the specified object into the tree
- * as determined by calling isInsertingChild and isInsertingAfter,
- * then returns the unmodified object. If there's no selection, or
- * no selection model, the root node is assumed to be selected.
- * And if the root node is selected, the new node will obviously be
- * inserted as a child. Override to customize.
- */
- protected Object objectInsertedIntoChildrenGroup( Object anObject )
- {
- // determine selection
- DisplayGroupNode selectedNode = (DisplayGroupNode) getRoot();
- if ( selectionModel != null )
- {
- // get selected path
- TreePath path = selectionModel.getSelectionPath();
-
- // get selected node
- if ( path != null )
- {
- selectedNode = (DisplayGroupNode) path.getLastPathComponent();
- }
- }
- // determine location of insertion
- int index = 0;
- if ( ( isInsertingChild() ) || ( selectedNode == getRoot() ) )
- {
- if ( isInsertingAfter() )
- {
- index = selectedNode.getChildCount();
- }
- }
- else // inserting as sibling
- {
- DisplayGroupNode parentNode = selectedNode.getParentGroup();
- index = parentNode.getIndex( selectedNode );
- if ( isInsertingAfter() )
- {
- index++;
- }
- selectedNode = parentNode;
- }
-
- // insert and return
- selectedNode.insertObjectAtIndex( anObject, index );
- return anObject;
- }
-
- /**
- * Called to by the children group's data source when it receives
- * a deleteObject message, usually after an object has been deleted
- * from the children display group.
- * Return the object that should be passed to the titles display
- * group's data source's implementation of deleteObject, or return
- * null to prevent that method from being called. <br><br>
- * This implementation deletes all instances of the selected object
- * from the tree nodes that are currently loaded, and returns the
- * unmodified object. Override to customize.
- */
- protected Object objectDeletedFromChildrenGroup( Object anObject )
- {
- TreePath[] paths = getPathsForObject( anObject );
- if ( paths != null )
- {
- for ( int i = 0; i < paths.length; i++ )
- {
- ((DisplayGroupNode)paths[i].getLastPathComponent()).removeFromParent();
- }
- }
- return anObject;
- }
-
- /**
- * Called to by the children group's data source to populate it
- * with all selected nodes and their siblings. To customize,
- * override this method, or specify a different data source for
- * the children display group.
- */
- protected NSArray objectsFetchedIntoChildrenGroup()
- {
- DisplayGroupNode node;
- TreePath parentPath;
- TreePath[] selectedPaths = selectionModel.getSelectionPaths();
- NSMutableArray objectList = new NSMutableArray();
- if ( selectedPaths != null )
- {
- for ( int i = 0; i < selectedPaths.length; i++ )
- {
- // root node is zero - ignore root node
- if ( ( selectedPaths[i].getLastPathComponent() == rootNode ) )
- {
- // select root in selectFromDisplayGroup()
- pleaseSelectRootNode = true;
- }
- else
- {
- node = (DisplayGroupNode)
- selectedPaths[i].getLastPathComponent();
- Object o = node.object();
-
- // add all children of parent to object list - includes self
- if ( node.parentGroup != null )
- {
- Enumeration e =
- node.parentGroup.displayedObjects().objectEnumerator();
- while ( e.hasMoreElements() )
- {
- // add only if not already in list
- o = e.nextElement();
- if ( objectList.indexOfIdenticalObject(o) == NSArray.NotFound )
- {
- objectList.addObject( o );
- }
- }
- }
- else // no parent node - add the node by itself
- {
- // add only if not already in list
- if ( objectList.indexOfIdenticalObject(o) == NSArray.NotFound )
- {
- objectList.addObject( o );
- }
- }
- }
- }
- }
-
- // if no selection
- if ( objectList.size() == 0 )
- {
- // populate with children of root
- objectList.addAll( rootNode.displayedObjects() );
- }
- return objectList;
- }
-
- /**
- * Queues processRecentChanges to be run in the event queue.
- */
- private void willChange()
- {
- EOObserverCenter.notifyObserversObjectWillChange( this );
- }
-
- /**
- * Tells the children display group to refetch, so that it reflects
- * any changes that were made in the node tree,
- * and then updates the selection in the selection model.
- * Triggered in response to willChange().
- */
- public void processRecentChanges()
- {
- Runnable update = new Runnable()
- {
- public void run()
- {
- removeAsListener(); // prevent data source refetch: see fetchObjects()
- childrenDisplayGroup.fetch();
- addAsListener();
- selectFromDisplayGroup( childrenDisplayGroup );
- }
- };
- if ( isListening )
- {
- if ( selectionPaintedImmediately )
- {
- // if painting selection immediately, run even later
- // so that AWT's repaint event fires before we do.
- SwingUtilities.invokeLater( update );
- }
- else
- {
- // otherwise run now
- update.run();
- }
- }
- }
-
- /**
- * Delegates most behaviors to the specified data source,
- * except fetchObjects, which calls fetchObjectsIntoChildrenGroup
- * on the tree model association. If delegate is null,
- * calls are passed to the superclass which is a PropertyDataSource.
- */
- static class DelegatingTreeDataSource extends PropertyDataSource
- {
- TreeModelAssociation parentAssociation;
- EODataSource delegateDataSource;
-
- public DelegatingTreeDataSource(
- TreeModelAssociation aTreeModelAssociation, EODataSource aDataSource )
- {
- parentAssociation = aTreeModelAssociation;
- delegateDataSource = aDataSource;
- }
-
- /**
- * Calls to delegateDataSource if it exists, otherwise
- * calls to super.
- */
- public Object createObject()
- {
- if ( delegateDataSource != null )
- {
- return delegateDataSource.createObject();
- }
- return super.createObject();
- }
-
- /**
- * Calls objectInsertedIntoChildrenGroup, and if not null
- * calls to delegateDataSource.insertObject if it exists,
- * and super.insertObjectAtIndex if not.
- */
- public void insertObjectAtIndex( Object anObject, int anIndex )
- {
- anObject =
- parentAssociation.objectInsertedIntoChildrenGroup(
- anObject );
- if ( anObject != null )
- {
- if ( delegateDataSource != null )
- {
- if ( delegateDataSource instanceof OrderedDataSource )
- {
- ((OrderedDataSource)delegateDataSource).insertObjectAtIndex( anObject, anIndex );
- }
- else
- {
- delegateDataSource.insertObject( anObject );
- }
- }
- else
- {
- super.insertObjectAtIndex( anObject, anIndex );
- }
- }
- }
-
- /**
- * Calls objectDeletedIntoChildrenGroup, and if not null
- * calls to delegateDataSource if it exists.
- */
- public void deleteObject( Object anObject )
- {
- anObject =
- parentAssociation.objectDeletedFromChildrenGroup(
- anObject );
- if ( anObject != null )
- {
- if ( delegateDataSource != null )
- {
- delegateDataSource.deleteObject( anObject );
- }
- super.deleteObject( anObject );
- }
- }
-
- /**
- * Overridden to return the delegate's editing context,
- * the titles display group's editing context,
- * and failing that calling to super.
- */
- public EOEditingContext editingContext ()
- {
- EOEditingContext result = null;
- if ( delegateDataSource != null )
- {
- result = delegateDataSource.editingContext();
- }
- if ( result == null )
- {
- EODataSource parentDataSource =
- parentAssociation.titlesDisplayGroup.dataSource();
- if ( parentDataSource != this && parentDataSource != null )
- {
- result = parentAssociation.titlesDisplayGroup.
- dataSource().editingContext();
- }
- }
- if ( result == null )
- {
- result = super.editingContext();
- }
- return result;
- }
-
- /**
- * Returns a List containing the objects in this
- * data source.
- */
- public NSArray fetchObjects ()
- {
- // if titles group is doing double-duty as children group
- if ( parentAssociation.titlesDisplayGroup == parentAssociation.childrenDisplayGroup )
- {
- // if we're not initiating this fetch
- if ( parentAssociation.isListening )
- {
- // need to call to delegate to see if we should update values
- if ( delegateDataSource != null )
- {
+ Enumeration it = listeners.elements();
+ while (it.hasMoreElements()) {
+ ((TreeModelListener) it.nextElement()).treeStructureChanged(event);
+ }
+ }
+
+ /**
+ * Creates and returns a new display group node.
+ */
+ public DisplayGroupNode createNode(EODisplayGroup aParentGroup, Object anObject) {
+ return new MutableDisplayGroupNode(this, aParentGroup, anObject);
+ }
+
+ /**
+ * Gets whether new objects programmatically inserted into the children display
+ * group should be inserted as a child of the first selected node. If false, new
+ * objects are inserted as siblings of the first selected node. Default value is
+ * true.
+ */
+ public boolean isInsertingChild() {
+ return insertingChild;
+ }
+
+ /**
+ * Sets whether new objects programmatically inserted into the children display
+ * group should be inserted as a child of the first selected node. If false, new
+ * objects are inserted as siblings of the first selected node. Default value is
+ * true.
+ */
+ public void setInsertingChild(boolean asChild) {
+ insertingChild = asChild;
+ }
+
+ /**
+ * Determines where new objects programmatically inserted into the children
+ * display group should be inserted, based on the value of insertingChild. If
+ * insertingChild, isInsertingAfter causes objects to be inserted at the end of
+ * the selected node's child list; otherwise, objects are inserted at the
+ * beginning of the list. If inserting as a sibling, isInsertingAfter causes
+ * objects to be inserted before the selected node in the selected node's
+ * parent's child list; otherwise, objects are inserted after the selected node
+ * in the child list. Default value is true.
+ */
+ public boolean isInsertingAfter() {
+ return insertingAfter;
+ }
+
+ /**
+ * Determines where new objects programmatically inserted into the children
+ * display group should be inserted, based on the value of insertingChild. If
+ * insertingChild, isInsertingAfter causes objects to be inserted at the end of
+ * the selected node's child list; otherwise, objects are inserted at the
+ * beginning of the list. If inserting as a sibling, isInsertingAfter causes
+ * objects to be inserted before the selected node in the selected node's
+ * parent's child list; otherwise, objects are inserted after the selected node
+ * in the child list. Default value is true.
+ */
+ public void setInsertingAfter(boolean after) {
+ insertingAfter = after;
+ }
+
+ /**
+ * Called to by the children group's data source when it receives an
+ * insertObject message, usually after an object has been inserted into the
+ * children display group. Return the object that should be passed to the titles
+ * display group's data source's implementation of insertObject, or return null
+ * to prevent that method from being called. <br>
+ * <br>
+ * This implementation inserts the specified object into the tree as determined
+ * by calling isInsertingChild and isInsertingAfter, then returns the unmodified
+ * object. If there's no selection, or no selection model, the root node is
+ * assumed to be selected. And if the root node is selected, the new node will
+ * obviously be inserted as a child. Override to customize.
+ */
+ protected Object objectInsertedIntoChildrenGroup(Object anObject) {
+ // determine selection
+ DisplayGroupNode selectedNode = (DisplayGroupNode) getRoot();
+ if (selectionModel != null) {
+ // get selected path
+ TreePath path = selectionModel.getSelectionPath();
+
+ // get selected node
+ if (path != null) {
+ selectedNode = (DisplayGroupNode) path.getLastPathComponent();
+ }
+ }
+ // determine location of insertion
+ int index = 0;
+ if ((isInsertingChild()) || (selectedNode == getRoot())) {
+ if (isInsertingAfter()) {
+ index = selectedNode.getChildCount();
+ }
+ } else // inserting as sibling
+ {
+ DisplayGroupNode parentNode = selectedNode.getParentGroup();
+ index = parentNode.getIndex(selectedNode);
+ if (isInsertingAfter()) {
+ index++;
+ }
+ selectedNode = parentNode;
+ }
+
+ // insert and return
+ selectedNode.insertObjectAtIndex(anObject, index);
+ return anObject;
+ }
+
+ /**
+ * Called to by the children group's data source when it receives a deleteObject
+ * message, usually after an object has been deleted from the children display
+ * group. Return the object that should be passed to the titles display group's
+ * data source's implementation of deleteObject, or return null to prevent that
+ * method from being called. <br>
+ * <br>
+ * This implementation deletes all instances of the selected object from the
+ * tree nodes that are currently loaded, and returns the unmodified object.
+ * Override to customize.
+ */
+ protected Object objectDeletedFromChildrenGroup(Object anObject) {
+ TreePath[] paths = getPathsForObject(anObject);
+ if (paths != null) {
+ for (int i = 0; i < paths.length; i++) {
+ ((DisplayGroupNode) paths[i].getLastPathComponent()).removeFromParent();
+ }
+ }
+ return anObject;
+ }
+
+ /**
+ * Called to by the children group's data source to populate it with all
+ * selected nodes and their siblings. To customize, override this method, or
+ * specify a different data source for the children display group.
+ */
+ protected NSArray objectsFetchedIntoChildrenGroup() {
+ DisplayGroupNode node;
+ TreePath parentPath;
+ TreePath[] selectedPaths = selectionModel.getSelectionPaths();
+ NSMutableArray objectList = new NSMutableArray();
+ if (selectedPaths != null) {
+ for (int i = 0; i < selectedPaths.length; i++) {
+ // root node is zero - ignore root node
+ if ((selectedPaths[i].getLastPathComponent() == rootNode)) {
+ // select root in selectFromDisplayGroup()
+ pleaseSelectRootNode = true;
+ } else {
+ node = (DisplayGroupNode) selectedPaths[i].getLastPathComponent();
+ Object o = node.object();
+
+ // add all children of parent to object list - includes self
+ if (node.parentGroup != null) {
+ Enumeration e = node.parentGroup.displayedObjects().objectEnumerator();
+ while (e.hasMoreElements()) {
+ // add only if not already in list
+ o = e.nextElement();
+ if (objectList.indexOfIdenticalObject(o) == NSArray.NotFound) {
+ objectList.addObject(o);
+ }
+ }
+ } else // no parent node - add the node by itself
+ {
+ // add only if not already in list
+ if (objectList.indexOfIdenticalObject(o) == NSArray.NotFound) {
+ objectList.addObject(o);
+ }
+ }
+ }
+ }
+ }
+
+ // if no selection
+ if (objectList.size() == 0) {
+ // populate with children of root
+ objectList.addAll(rootNode.displayedObjects());
+ }
+ return objectList;
+ }
+
+ /**
+ * Queues processRecentChanges to be run in the event queue.
+ */
+ private void willChange() {
+ EOObserverCenter.notifyObserversObjectWillChange(this);
+ }
+
+ /**
+ * Tells the children display group to refetch, so that it reflects any changes
+ * that were made in the node tree, and then updates the selection in the
+ * selection model. Triggered in response to willChange().
+ */
+ public void processRecentChanges() {
+ Runnable update = new Runnable() {
+ public void run() {
+ removeAsListener(); // prevent data source refetch: see fetchObjects()
+ childrenDisplayGroup.fetch();
+ addAsListener();
+ selectFromDisplayGroup(childrenDisplayGroup);
+ }
+ };
+ if (isListening) {
+ if (selectionPaintedImmediately) {
+ // if painting selection immediately, run even later
+ // so that AWT's repaint event fires before we do.
+ SwingUtilities.invokeLater(update);
+ } else {
+ // otherwise run now
+ update.run();
+ }
+ }
+ }
+
+ /**
+ * Delegates most behaviors to the specified data source, except fetchObjects,
+ * which calls fetchObjectsIntoChildrenGroup on the tree model association. If
+ * delegate is null, calls are passed to the superclass which is a
+ * PropertyDataSource.
+ */
+ static class DelegatingTreeDataSource extends PropertyDataSource {
+ TreeModelAssociation parentAssociation;
+ EODataSource delegateDataSource;
+
+ public DelegatingTreeDataSource(TreeModelAssociation aTreeModelAssociation, EODataSource aDataSource) {
+ parentAssociation = aTreeModelAssociation;
+ delegateDataSource = aDataSource;
+ }
+
+ /**
+ * Calls to delegateDataSource if it exists, otherwise calls to super.
+ */
+ public Object createObject() {
+ if (delegateDataSource != null) {
+ return delegateDataSource.createObject();
+ }
+ return super.createObject();
+ }
+
+ /**
+ * Calls objectInsertedIntoChildrenGroup, and if not null calls to
+ * delegateDataSource.insertObject if it exists, and super.insertObjectAtIndex
+ * if not.
+ */
+ public void insertObjectAtIndex(Object anObject, int anIndex) {
+ anObject = parentAssociation.objectInsertedIntoChildrenGroup(anObject);
+ if (anObject != null) {
+ if (delegateDataSource != null) {
+ if (delegateDataSource instanceof OrderedDataSource) {
+ ((OrderedDataSource) delegateDataSource).insertObjectAtIndex(anObject, anIndex);
+ } else {
+ delegateDataSource.insertObject(anObject);
+ }
+ } else {
+ super.insertObjectAtIndex(anObject, anIndex);
+ }
+ }
+ }
+
+ /**
+ * Calls objectDeletedIntoChildrenGroup, and if not null calls to
+ * delegateDataSource if it exists.
+ */
+ public void deleteObject(Object anObject) {
+ anObject = parentAssociation.objectDeletedFromChildrenGroup(anObject);
+ if (anObject != null) {
+ if (delegateDataSource != null) {
+ delegateDataSource.deleteObject(anObject);
+ }
+ super.deleteObject(anObject);
+ }
+ }
+
+ /**
+ * Overridden to return the delegate's editing context, the titles display
+ * group's editing context, and failing that calling to super.
+ */
+ public EOEditingContext editingContext() {
+ EOEditingContext result = null;
+ if (delegateDataSource != null) {
+ result = delegateDataSource.editingContext();
+ }
+ if (result == null) {
+ EODataSource parentDataSource = parentAssociation.titlesDisplayGroup.dataSource();
+ if (parentDataSource != this && parentDataSource != null) {
+ result = parentAssociation.titlesDisplayGroup.dataSource().editingContext();
+ }
+ }
+ if (result == null) {
+ result = super.editingContext();
+ }
+ return result;
+ }
+
+ /**
+ * Returns a List containing the objects in this data source.
+ */
+ public NSArray fetchObjects() {
+ // if titles group is doing double-duty as children group
+ if (parentAssociation.titlesDisplayGroup == parentAssociation.childrenDisplayGroup) {
+ // if we're not initiating this fetch
+ if (parentAssociation.isListening) {
+ // need to call to delegate to see if we should update values
+ if (delegateDataSource != null) {
// System.out.println( "fetching from delegate (slow!)" );
- NSArray result = delegateDataSource.fetchObjects();
- NSArray rootObjects = parentAssociation.rootNode.displayedObjects();
- // if titles data source has different objects, return them
- if ( rootObjects.count() != result.count()
- || ! rootObjects.containsAll( result ) )
- {
- // this will force the root node to repopulate in subjectChanged()
+ NSArray result = delegateDataSource.fetchObjects();
+ NSArray rootObjects = parentAssociation.rootNode.displayedObjects();
+ // if titles data source has different objects, return them
+ if (rootObjects.count() != result.count() || !rootObjects.containsAll(result)) {
+ // this will force the root node to repopulate in subjectChanged()
//System.out.println( "fetchObjects: data source" );
- return result;
- }
- }
- }
- }
- // otherwise: just repopulate the titles group
+ return result;
+ }
+ }
+ }
+ }
+ // otherwise: just repopulate the titles group
//System.out.println( "fetchObjects: objectsFetchedIntoChildrenGroup" );
- return parentAssociation.objectsFetchedIntoChildrenGroup();
- }
-
- /**
- * Returns a data source that is capable of
- * manipulating objects of the type returned by
- * applying the specified key to objects
- * vended by this data source.
- * @see #qualifyWithRelationshipKey
- */
- public EODataSource
- dataSourceQualifiedByKey ( String aKey )
- {
- if ( delegateDataSource != null )
- {
- return delegateDataSource.dataSourceQualifiedByKey( aKey );
- }
- return null;
- }
-
- /**
- * Restricts this data source to vend those
- * objects that are associated with the specified
- * key on the specified object.
- */
- public void
- qualifyWithRelationshipKey (
- String aKey, Object anObject )
- {
- if ( delegateDataSource != null )
- {
- delegateDataSource.qualifyWithRelationshipKey( aKey, anObject );
- }
- }
-
- /**
- * Returns the value from the delegateDataSource, if it exists.
- * Otherwise calls super.
- */
- public EOClassDescription classDescriptionForObjects()
- {
- if ( delegateDataSource != null )
- {
- return delegateDataSource.classDescriptionForObjects();
- }
- return super.classDescriptionForObjects();
- }
-
- }
-
+ return parentAssociation.objectsFetchedIntoChildrenGroup();
+ }
+
+ /**
+ * Returns a data source that is capable of manipulating objects of the type
+ * returned by applying the specified key to objects vended by this data source.
+ *
+ * @see #qualifyWithRelationshipKey
+ */
+ public EODataSource dataSourceQualifiedByKey(String aKey) {
+ if (delegateDataSource != null) {
+ return delegateDataSource.dataSourceQualifiedByKey(aKey);
+ }
+ return null;
+ }
+
+ /**
+ * Restricts this data source to vend those objects that are associated with the
+ * specified key on the specified object.
+ */
+ public void qualifyWithRelationshipKey(String aKey, Object anObject) {
+ if (delegateDataSource != null) {
+ delegateDataSource.qualifyWithRelationshipKey(aKey, anObject);
+ }
+ }
+
+ /**
+ * Returns the value from the delegateDataSource, if it exists. Otherwise calls
+ * super.
+ */
+ public EOClassDescription classDescriptionForObjects() {
+ if (delegateDataSource != null) {
+ return delegateDataSource.classDescriptionForObjects();
+ }
+ return super.classDescriptionForObjects();
+ }
+
+ }
+
}
/*
- * $Log$
- * Revision 1.2 2006/02/18 23:19:05 cgruber
- * Update imports and maven dependencies.
+ * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven
+ * dependencies.
*
- * Revision 1.1 2006/02/16 13:22:22 cgruber
- * Check in all sources in eclipse-friendly maven-enabled packages.
+ * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in
+ * eclipse-friendly maven-enabled packages.
*
- * Revision 1.20 2003/08/06 23:07:52 chochos
- * general code cleanup (mostly, removing unused imports)
+ * Revision 1.20 2003/08/06 23:07:52 chochos general code cleanup (mostly,
+ * removing unused imports)
*
- * Revision 1.19 2002/05/03 21:41:18 mpowers
- * No longer clearing the selection model when updating from display group:
- * we now only modify if a change needs to be made.
- * No longer listening for selection change during firing of delete events:
- * delete events cause JTree's to update their selection model.
- * Fix for paintsSelectionImmediately: TreeAssociation.processRecentChanges()
- * must happen after the screen is painted, or the selection is not displayed.
+ * Revision 1.19 2002/05/03 21:41:18 mpowers No longer clearing the selection
+ * model when updating from display group: we now only modify if a change needs
+ * to be made. No longer listening for selection change during firing of delete
+ * events: delete events cause JTree's to update their selection model. Fix for
+ * paintsSelectionImmediately: TreeAssociation.processRecentChanges() must
+ * happen after the screen is painted, or the selection is not displayed.
*
- * Revision 1.18 2002/04/23 19:12:28 mpowers
- * Reimplemented fireEventsForChanges. Fitter and happier.
+ * Revision 1.18 2002/04/23 19:12:28 mpowers Reimplemented fireEventsForChanges.
+ * Fitter and happier.
*
- * Revision 1.17 2002/04/19 21:18:46 mpowers
- * Removed tree event coalescing, which was causing way too many problems.
- * The fireChangeEvent algorithm is way faster than before, so we should
- * still be better off than before. At least now, we don't have to track
- * whether the view component has encountered a particular node.
+ * Revision 1.17 2002/04/19 21:18:46 mpowers Removed tree event coalescing,
+ * which was causing way too many problems. The fireChangeEvent algorithm is way
+ * faster than before, so we should still be better off than before. At least
+ * now, we don't have to track whether the view component has encountered a
+ * particular node.
*
- * Revision 1.16 2002/04/18 20:36:11 mpowers
- * TreeModelAssociation now populates children group before selected objects.
- * Got rid of the forceOnSync workaround for cancelled selection change.
+ * Revision 1.16 2002/04/18 20:36:11 mpowers TreeModelAssociation now populates
+ * children group before selected objects. Got rid of the forceOnSync workaround
+ * for cancelled selection change.
*
- * Revision 1.15 2002/04/15 21:52:50 mpowers
- * Tightening up TreeModelAssociation and DisplayGroupNode.
- * Now only firing root structure changed once.
- * Now disposing of root's children.
- * Better event coalescing.
+ * Revision 1.15 2002/04/15 21:52:50 mpowers Tightening up TreeModelAssociation
+ * and DisplayGroupNode. Now only firing root structure changed once. Now
+ * disposing of root's children. Better event coalescing.
*
- * Revision 1.14 2002/04/12 21:05:58 mpowers
- * Now distinguishing changes in titles group even better.
+ * Revision 1.14 2002/04/12 21:05:58 mpowers Now distinguishing changes in
+ * titles group even better.
*
- * Revision 1.11 2002/04/10 21:20:04 mpowers
- * Better handling for tree nodes when working with editing contexts.
- * Better handling for invalidation. No longer broadcasting events
- * when nodes have not been "registered" in the tree.
+ * Revision 1.11 2002/04/10 21:20:04 mpowers Better handling for tree nodes when
+ * working with editing contexts. Better handling for invalidation. No longer
+ * broadcasting events when nodes have not been "registered" in the tree.
*
- * Revision 1.10 2002/04/03 20:01:24 mpowers
- * Removed printlns.
+ * Revision 1.10 2002/04/03 20:01:24 mpowers Removed printlns.
*
- * Revision 1.8 2002/03/11 03:16:28 mpowers
- * Better handling of change events; coalescing changes to children group.
+ * Revision 1.8 2002/03/11 03:16:28 mpowers Better handling of change events;
+ * coalescing changes to children group.
*
- * Revision 1.7 2002/03/08 23:19:57 mpowers
- * Refactoring of DelegatingTreeDataSource to facilitate binding of titles
- * and children aspects to the same display group.
+ * Revision 1.7 2002/03/08 23:19:57 mpowers Refactoring of
+ * DelegatingTreeDataSource to facilitate binding of titles and children aspects
+ * to the same display group.
*
- * Revision 1.6 2002/03/07 23:04:36 mpowers
- * Refining TreeColumnAssociation.
+ * Revision 1.6 2002/03/07 23:04:36 mpowers Refining TreeColumnAssociation.
*
- * Revision 1.5 2002/03/06 13:04:16 mpowers
- * Implemented cascading qualifiers in tree nodes.
+ * Revision 1.5 2002/03/06 13:04:16 mpowers Implemented cascading qualifiers in
+ * tree nodes.
*
- * Revision 1.4 2002/03/04 22:47:48 mpowers
- * Fixed sort ordering for titles group. Optimization for delegate selection.
+ * Revision 1.4 2002/03/04 22:47:48 mpowers Fixed sort ordering for titles
+ * group. Optimization for delegate selection.
*
- * Revision 1.3 2002/03/04 12:28:47 mpowers
- * Revised case where children and titles are bound to same display group.
+ * Revision 1.3 2002/03/04 12:28:47 mpowers Revised case where children and
+ * titles are bound to same display group.
*
- * Revision 1.2 2002/03/01 23:42:09 mpowers
- * Implemented TreeColumnAssociation, and updated documentation.
+ * Revision 1.2 2002/03/01 23:42:09 mpowers Implemented TreeColumnAssociation,
+ * and updated documentation.
*
- * Revision 1.1 2002/02/27 23:19:17 mpowers
- * Refactoring of TreeAssociation to create TreeModelAssociation parent.
+ * Revision 1.1 2002/02/27 23:19:17 mpowers Refactoring of TreeAssociation to
+ * create TreeModelAssociation parent.
*
- * Revision 1.38 2002/02/18 03:46:08 mpowers
- * Implemented TreeTableCellRenderer.
+ * Revision 1.38 2002/02/18 03:46:08 mpowers Implemented TreeTableCellRenderer.
*
- * Revision 1.37 2002/02/13 21:20:15 mpowers
- * Updated comments.
+ * Revision 1.37 2002/02/13 21:20:15 mpowers Updated comments.
*
- * Revision 1.36 2001/11/21 15:13:25 mpowers
- * Better repainting for selectionPaintedImmediately.
- * Better handling for selection with multiple instances of the same
- * object in the tree (from yjcheung).
+ * Revision 1.36 2001/11/21 15:13:25 mpowers Better repainting for
+ * selectionPaintedImmediately. Better handling for selection with multiple
+ * instances of the same object in the tree (from yjcheung).
*
- * Revision 1.35 2001/11/20 19:13:51 mpowers
- * Finished implementation of children group's specialized data source.
+ * Revision 1.35 2001/11/20 19:13:51 mpowers Finished implementation of children
+ * group's specialized data source.
*
- * Revision 1.34 2001/11/19 16:30:37 mpowers
- * Tree repaint strategy is now a preference: selectionPaintedImmediately.
+ * Revision 1.34 2001/11/19 16:30:37 mpowers Tree repaint strategy is now a
+ * preference: selectionPaintedImmediately.
*
- * Revision 1.33 2001/11/15 17:56:41 mpowers
- * Initial implementation of data source for the children display group.
+ * Revision 1.33 2001/11/15 17:56:41 mpowers Initial implementation of data
+ * source for the children display group.
*
- * Revision 1.32 2001/11/14 00:05:54 mpowers
- * Eliminated the run later in favor of repainting the component immediately.
- * This makes things more predictable for users of the association that
- * want to listen to mouse or selection events on the tree.
+ * Revision 1.32 2001/11/14 00:05:54 mpowers Eliminated the run later in favor
+ * of repainting the component immediately. This makes things more predictable
+ * for users of the association that want to listen to mouse or selection events
+ * on the tree.
*
- * Revision 1.31 2001/11/02 20:43:15 mpowers
- * Fixes for delegate's shouldChangeSelection veto (from yjcheung).
+ * Revision 1.31 2001/11/02 20:43:15 mpowers Fixes for delegate's
+ * shouldChangeSelection veto (from yjcheung).
*
- * Revision 1.30 2001/10/29 20:42:56 mpowers
- * On selection change, repainting tree before notifying display group;
- * using NSRunLoop instead of SwingUtilities.
+ * Revision 1.30 2001/10/29 20:42:56 mpowers On selection change, repainting
+ * tree before notifying display group; using NSRunLoop instead of
+ * SwingUtilities.
*
- * Revision 1.29 2001/10/12 20:12:53 mpowers
- * Better handling of selection change vetoing when changing selection
- * to a node that is not the sibling of the originally selected node.
+ * Revision 1.29 2001/10/12 20:12:53 mpowers Better handling of selection change
+ * vetoing when changing selection to a node that is not the sibling of the
+ * originally selected node.
*
- * Revision 1.28 2001/09/14 13:40:26 mpowers
- * User-initiated selection changes are now handled on the next event loop
- * so that the component repaints the new selection before any potentially
- * lengthy logic is triggered by the selection change.
+ * Revision 1.28 2001/09/14 13:40:26 mpowers User-initiated selection changes
+ * are now handled on the next event loop so that the component repaints the new
+ * selection before any potentially lengthy logic is triggered by the selection
+ * change.
*
- * Revision 1.27 2001/09/10 14:10:03 mpowers
- * Tree now handles multiple instances of the same object.
+ * Revision 1.27 2001/09/10 14:10:03 mpowers Tree now handles multiple instances
+ * of the same object.
*
- * Revision 1.26 2001/07/18 13:03:32 mpowers
- * TreeNodes now refetch only on demand. Previously, once a node had
- * been fetched, it was always refetched after an invalidate, even if
- * the node was not being displayed.
+ * Revision 1.26 2001/07/18 13:03:32 mpowers TreeNodes now refetch only on
+ * demand. Previously, once a node had been fetched, it was always refetched
+ * after an invalidate, even if the node was not being displayed.
*
- * Revision 1.25 2001/05/14 15:25:35 mpowers
- * No longer copying titles group's data source to children group.
+ * Revision 1.25 2001/05/14 15:25:35 mpowers No longer copying titles group's
+ * data source to children group.
*
- * Revision 1.24 2001/05/08 18:47:34 mpowers
- * Minor fixes for d3.
+ * Revision 1.24 2001/05/08 18:47:34 mpowers Minor fixes for d3.
*
- * Revision 1.23 2001/05/01 00:52:32 mpowers
- * Implemented breadth-first traversal of tree for node.
+ * Revision 1.23 2001/05/01 00:52:32 mpowers Implemented breadth-first traversal
+ * of tree for node.
*
- * Revision 1.22 2001/04/26 01:15:19 mpowers
- * Major clean-up of DisplayGroupNode: fitter, happier, more productive.
+ * Revision 1.22 2001/04/26 01:15:19 mpowers Major clean-up of DisplayGroupNode:
+ * fitter, happier, more productive.
*
- * Revision 1.21 2001/04/22 23:13:35 mpowers
- * Minor bug.
+ * Revision 1.21 2001/04/22 23:13:35 mpowers Minor bug.
*
- * Revision 1.20 2001/04/22 23:05:33 mpowers
- * Totally revised DisplayGroupNode so each object gets its own node
- * (so the nodes are no longer fixed by index).
+ * Revision 1.20 2001/04/22 23:05:33 mpowers Totally revised DisplayGroupNode so
+ * each object gets its own node (so the nodes are no longer fixed by index).
*
- * Revision 1.19 2001/04/21 23:06:33 mpowers
- * A major revisiting to support the revising of DisplayGroupNode.
+ * Revision 1.19 2001/04/21 23:06:33 mpowers A major revisiting to support the
+ * revising of DisplayGroupNode.
*
- * Revision 1.18 2001/04/03 20:36:01 mpowers
- * Fixed refaulting/reverting/invalidating to be self-consistent.
+ * Revision 1.18 2001/04/03 20:36:01 mpowers Fixed
+ * refaulting/reverting/invalidating to be self-consistent.
*
- * Revision 1.17 2001/03/29 21:35:08 mpowers
- * Now handling circular references in the graph.
+ * Revision 1.17 2001/03/29 21:35:08 mpowers Now handling circular references in
+ * the graph.
*
- * Revision 1.16 2001/03/22 21:25:42 mpowers
- * Fixed some nasty issues with jtree's internal state and array bounds.
+ * Revision 1.16 2001/03/22 21:25:42 mpowers Fixed some nasty issues with
+ * jtree's internal state and array bounds.
*
- * Revision 1.15 2001/03/19 21:37:58 mpowers
- * Improved refresh of titles display group.
- * Fixed dangling selection problem after refresh.
+ * Revision 1.15 2001/03/19 21:37:58 mpowers Improved refresh of titles display
+ * group. Fixed dangling selection problem after refresh.
*
- * Revision 1.14 2001/03/09 22:08:57 mpowers
- * Trying to handle the dangling reference problem after an update.
+ * Revision 1.14 2001/03/09 22:08:57 mpowers Trying to handle the dangling
+ * reference problem after an update.
*
- * Revision 1.13 2001/02/17 17:23:49 mpowers
- * More changes to support compiling with jdk1.1 collections.
+ * Revision 1.13 2001/02/17 17:23:49 mpowers More changes to support compiling
+ * with jdk1.1 collections.
*
- * Revision 1.12 2001/01/25 02:16:25 mpowers
- * TreeModelAssociation now returns DisplayGroupNode.getUserObject.
+ * Revision 1.12 2001/01/25 02:16:25 mpowers TreeModelAssociation now returns
+ * DisplayGroupNode.getUserObject.
*
- * Revision 1.11 2001/01/24 18:14:40 mpowers
- * Fixed problem with leaving children aspect unspecified.
+ * Revision 1.11 2001/01/24 18:14:40 mpowers Fixed problem with leaving children
+ * aspect unspecified.
*
- * Revision 1.10 2001/01/24 17:49:15 mpowers
- * Added getObjectForNode and getNodeForObject convenience methods.
+ * Revision 1.10 2001/01/24 17:49:15 mpowers Added getObjectForNode and
+ * getNodeForObject convenience methods.
*
- * Revision 1.9 2001/01/24 17:44:11 mpowers
- * Renamed getPathForNode to getPathForObject to be more precise.
- * And created a new getPathForNode method.
+ * Revision 1.9 2001/01/24 17:44:11 mpowers Renamed getPathForNode to
+ * getPathForObject to be more precise. And created a new getPathForNode method.
*
- * Revision 1.8 2001/01/24 17:20:29 mpowers
- * Children display group now holds siblings of selected objects
- * in addition to the selected objects.
+ * Revision 1.8 2001/01/24 17:20:29 mpowers Children display group now holds
+ * siblings of selected objects in addition to the selected objects.
*
- * Revision 1.5 2001/01/19 23:21:15 mpowers
- * Fine tuning events broadcast from TreeModelAssociation.
+ * Revision 1.5 2001/01/19 23:21:15 mpowers Fine tuning events broadcast from
+ * TreeModelAssociation.
*
- * Revision 1.4 2001/01/18 21:27:29 mpowers
- * Major rework of TreeModelAssociation.
+ * Revision 1.4 2001/01/18 21:27:29 mpowers Major rework of
+ * TreeModelAssociation.
*
- * Revision 1.2 2001/01/11 20:29:19 mpowers
- * Expanded access to tree event firing methods.
+ * Revision 1.2 2001/01/11 20:29:19 mpowers Expanded access to tree event firing
+ * methods.
*
- * Revision 1.1.1.1 2000/12/21 15:49:18 mpowers
- * Contributing wotonomy.
+ * Revision 1.1.1.1 2000/12/21 15:49:18 mpowers Contributing wotonomy.
*
- * Revision 1.20 2000/12/20 16:25:42 michael
- * Added log to all files.
+ * Revision 1.20 2000/12/20 16:25:42 michael Added log to all files.
*
*
*/
-