diff options
| author | Benjamin Culkin <scorpress@gmail.com> | 2024-05-20 17:58:16 -0400 |
|---|---|---|
| committer | Benjamin Culkin <scorpress@gmail.com> | 2024-05-20 17:58:16 -0400 |
| commit | 40a9d99496e098562f090fb7ffce9e749011b131 (patch) | |
| tree | 437df24d65470582e943e494a52db8ed65a881ae /projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java | |
| parent | ff072dfe782f6f22123cd4ba050828d35c0d0fbd (diff) | |
Formatting pass
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.java | 2930 |
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. * * */ - |
