diff options
Diffstat (limited to 'projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java')
| -rw-r--r-- | projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java | 582 |
1 files changed, 582 insertions, 0 deletions
diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java new file mode 100644 index 0000000..728643b --- /dev/null +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java @@ -0,0 +1,582 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.ui.swing; + +import java.awt.EventQueue; +import java.awt.Rectangle; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Vector; + +import javax.swing.JTree; +import javax.swing.event.TreeExpansionEvent; +import javax.swing.event.TreeExpansionListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeWillExpandListener; +import javax.swing.tree.ExpandVetoException; +import javax.swing.tree.TreePath; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.ui.EODisplayGroup; + +/** +* TreeAssociation is a TreeModelAssociation further +* customized for JTrees. It binds a JTree 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. TreeAssociation 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. +* Note that the children aspect requires the bound +* display group to have a DataSource that can vend a +* DataSource appropriate for the bound key That data +* source is then used to create data sources for +* child nodes, and so on. +* +* <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 visible nodes 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 visible nodes 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> +* +* All other usage is as TreeModelAssociation. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 904 $ +*/ +public class TreeAssociation extends TreeModelAssociation + implements FocusListener, TreeExpansionListener, TreeWillExpandListener, Runnable +{ + private boolean isExpanding; + private Vector nodeQueue; + private Vector structureQueue; + private boolean isRunning; + + /** + * Constructor expecting a JTree. + */ + public TreeAssociation ( Object anObject ) + { + super( anObject ); + init(); + } + + /** + * Constructor expecting a JTree or similar component + * and specifying a label for the root node. + */ + public TreeAssociation( Object anObject, Object aRootLabel ) + { + super( anObject ); + init(); + rootLabel = aRootLabel; + rootNode.setUserObject( aRootLabel ); + } + + // convenience + private JTree component() + { + return (JTree) object(); + } + + /** + * Called by both constructors. + */ + protected void init() + { + isExpanding = false; + isRunning = false; + nodeQueue = new Vector(); + structureQueue = new Vector(); + component().addFocusListener( this ); + component().addTreeExpansionListener( this ); + component().addTreeWillExpandListener( this ); + } + + /** + * Returns whether this class can control the specified + * object. + */ + public static boolean isUsableWithObject ( Object anObject ) + { + return ( anObject instanceof JTree ); + } + + /** + * Overridden to not fire events during initial population. + */ + public void establishConnection () + { + isExpanding = true; + super.establishConnection(); + isExpanding = false; + } + + // interface TreeSelectionListener + + public void valueChanged(TreeSelectionEvent e) + { + if ( ! isListening ) return; +// NOTE: This approach causes focus rectangle to perceptibly trail +// the selection rectangle, presumably because we're called after the +// new selection has been processed but before the focus is processed. +// Users don't like it, so we're going with a preference to use +// invokeLater. +/* + // paint immediately before updating the display group and + // before any potentially lengthy second-order effects happen: + // this improves user-perceived responsiveness of big apps + if ( object() instanceof javax.swing.JComponent ) + { + javax.swing.JComponent component = (javax.swing.JComponent)object(); + component.paintImmediately( component.getBounds() ); + } + selectFromSelectionModel(); +*/ + +// NOTE: This approach uses invoke later to cause the update of +// the display group (which could be lengthly if that in turn +// causes other things to update) to happen after the tree repaints. +// Users like this because it "feels faster", but developers have +// to remember that if they listen to tree selection events they +// will have to do a similar invoke later if they check the display +// group. + + Runnable select = new Runnable() + { + public void run() + { + selectFromSelectionModel(); + } + }; + + if ( selectionPaintedImmediately ) + { + if ( object() instanceof java.awt.Component ) + { + ((java.awt.Component)object()).repaint(); + } + EventQueue.invokeLater( select ); + } + else + { + select.run(); + } + } + + /** + * Overridden to check whether the node is visible + * in the tree on screen. Offscreen in a scrollpane + * does not count. + */ + public boolean isVisible(Object node) + { + JTree tree = (JTree) object(); + TreePath path = ((DisplayGroupNode)node).treePath(); + if ( tree.isVisible( path ) ) + { + Rectangle rowRect = tree.getPathBounds( path ); + if ( rowRect != null ) + { + Rectangle visible = tree.getVisibleRect(); + if ( visible != null ) + { +//System.out.println( "isVisible: intersects: " + visible.intersects( rowRect ) ); + return visible.intersects( rowRect ); + } + } + } +//System.out.println( "isVisible: false" ); + return false; + } + + /** + * 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(final Object source, + final Object[] path, + final int[] childIndices, + final Object[] children) + { + if ( !isExpanding ) + { + for ( int i = 0; i < children.length; i++ ) + { + nodeQueue.add( children[i] ); + } + if ( !isRunning ) + { + isRunning = true; + EventQueue.invokeLater( this ); + } + } + } + + /** + * 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) + { + if ( !isExpanding ) + { + super.fireTreeNodesInserted( source, path, childIndices, children ); + EventQueue.invokeLater( this ); + } + } + + /** + * 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) + { + if ( !isExpanding ) + { + super.fireTreeNodesRemoved( source, path, childIndices, children ); + EventQueue.invokeLater( this ); + } + } + + /** + * 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) + { + if ( !isExpanding ) + { + structureQueue.add( path[path.length-1] ); + if ( !isRunning ) + { + isRunning = true; + EventQueue.invokeLater( this ); + } + } + } + + /** + * Overridden to return all visible rows in the tree. + */ + public NSArray objectsFetchedIntoChildrenGroup() + { + JTree tree = (JTree) object(); + NSMutableArray objectList = new NSMutableArray(); + + int count = tree.getRowCount(); + for ( int i = 0; i < count; i++ ) + { + objectList.add( + ((DisplayGroupNode) tree.getPathForRow( i ).getLastPathComponent()).object() ); + } + +//new net.wotonomy.ui.swing.util.StackTraceInspector( Integer.toString( objectList.size() ) ); + return objectList; + } + + // interface FocusListener + + /** + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) + { + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while ( e.hasMoreElements() ) + { + displayGroup = + displayGroupForAspect( e.nextElement().toString() ); + if ( displayGroup != null ) + { + displayGroup.associationDidBeginEditing( this ); + } + } + } + + /** + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) + { + if ( ! component().isEditing() ) + { + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while ( e.hasMoreElements() ) + { + displayGroup = + displayGroupForAspect( e.nextElement().toString() ); + if ( displayGroup != null ) + { + displayGroup.associationDidEndEditing( this ); + } + } + } + } + + // interface TreeWillExpandListener + + public void treeWillExpand(TreeExpansionEvent event) + throws ExpandVetoException + { + isExpanding = true; + } + + public void treeWillCollapse(TreeExpansionEvent event) + throws ExpandVetoException + { + // do nothing + } + + // interface TreeExpansionListener + + /** + * Updates the children display group, if any. + */ + public void treeExpanded(TreeExpansionEvent event) + { //System.out.println( "treeExpanded: " + event.getPath().getLastPathComponent() ); + isExpanding = false; + if ( childrenDisplayGroup != null ) + { + removeAsListener(); // prevent data source refetch: see fetchObjects() + childrenDisplayGroup.fetch(); + addAsListener(); + } + } + + /** + * Updates the children display group, if any. + */ + public void treeCollapsed(TreeExpansionEvent event) + { + if ( childrenDisplayGroup != null ) + { + removeAsListener(); // prevent data source refetch: see fetchObjects() + childrenDisplayGroup.fetch(); + addAsListener(); + } + } + + // interface Runnable + + /** + * Fires any queued node changed and structure changed events. + * Typically invoked on a delayed event loop. + */ + public void run() + { + DisplayGroupNode node; + int index; + Iterator i; + + i = nodeQueue.iterator(); + while ( i.hasNext() ) + { + node = (DisplayGroupNode) i.next(); + index = ((DisplayGroupNode)node.parentGroup).getIndex( node ); + if ( ( index != -1 ) + && ( node.treePath().getParentPath() != null ) ) + { + super.fireTreeNodesChanged( + node, + node.treePath().getParentPath().getPath(), + new int[] { index }, + new Object[] { node } ); + } + } + nodeQueue.clear(); + + i = structureQueue.iterator(); + while ( i.hasNext() ) + { + node = (DisplayGroupNode) i.next(); + super.fireTreeStructureChanged( + node, + node.treePath().getPath(), + null, + null ); + } + structureQueue.clear(); + + isRunning = false; +/* + EventQueue.invokeLater( new Runnable() { public void run() { + ((JTree)object()).treeDidChange(); + ((JTree)object()).getParent().invalidate(); + ((JTree)object()).getParent().validate(); + ((JTree)object()).getParent().update( ((JTree)object()).getGraphics() ); + +// ((JTree)object()).getParent().doLayout(); +// ((JTree)object()).getParent().repaint(); +// ((JTree)object()).repaint(); +// ((JTree)object()).updateUI(); + } } ); +*/ + } +} + +/* + * $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.55 2004/02/05 02:18:50 mpowers + * Super was calling back into this class before init() was called. + * + * Revision 1.54 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.53 2003/06/03 14:49:48 mpowers + * Now correctly calculating isVisible based on the component visible rect. + * Now deferring node changed events to a later event queue to allow repaints + * to happen after all changes have taken effect. + * + * Revision 1.52 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.51 2002/04/19 21:18:45 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.49 2002/04/12 21:05:58 mpowers + * Now distinguishing changes in titles group even better. + * + * Revision 1.48 2002/04/12 20:36:31 mpowers + * Now distinguishing between changes made on titles group by tree expansion + * versus external changes which should cause us to repopulate root nodes. + * + * Revision 1.47 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.46 2002/03/27 20:44:53 mpowers + * Added isVisible test for node. + * + * Revision 1.45 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.44 2002/03/07 23:04:36 mpowers + * Refining TreeColumnAssociation. + * + * Revision 1.43 2002/03/06 13:04:15 mpowers + * Implemented cascading qualifiers in tree nodes. + * + * Revision 1.42 2002/03/05 23:18:28 mpowers + * Added documentation. + * Added isSelectionPaintedImmediate and isSelectionTracking attributes + * to TableAssociation. + * Added getTableAssociation to TableColumnAssociation. + * + * Revision 1.41 2002/03/01 23:42:08 mpowers + * Implemented TreeColumnAssociation, and updated documentation. + * + * Revision 1.40 2002/03/01 20:41:39 mpowers + * Now a focus listener and an expansion listener. + * + * Revision 1.39 2002/02/27 23:19:17 mpowers + * Refactoring of TreeAssociation to create TreeModelAssociation parent. + * + * + */ + |
