summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java
diff options
context:
space:
mode:
authorBenjamin Culkin <scorpress@gmail.com>2024-05-19 17:56:33 -0400
committerBenjamin Culkin <scorpress@gmail.com>2024-05-19 17:56:33 -0400
commitaedc34d55462a75e329bbf342251ff6504cd117e (patch)
treebcc8f1f2352582717b484df302aeea6696b8f000 /projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java
Initial import from SVN
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.java582
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.
+ *
+ *
+ */
+