1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.wotonomy.ui.swing;
20
21 import java.awt.EventQueue;
22 import java.awt.Rectangle;
23 import java.awt.event.FocusEvent;
24 import java.awt.event.FocusListener;
25 import java.util.Enumeration;
26 import java.util.Iterator;
27 import java.util.Vector;
28
29 import javax.swing.JTree;
30 import javax.swing.event.TreeExpansionEvent;
31 import javax.swing.event.TreeExpansionListener;
32 import javax.swing.event.TreeSelectionEvent;
33 import javax.swing.event.TreeWillExpandListener;
34 import javax.swing.tree.ExpandVetoException;
35 import javax.swing.tree.TreePath;
36
37 import net.wotonomy.foundation.NSArray;
38 import net.wotonomy.foundation.NSMutableArray;
39 import net.wotonomy.ui.EODisplayGroup;
40
41 /***
42 * TreeAssociation is a TreeModelAssociation further
43 * customized for JTrees. It binds a JTree to a display group's
44 * list of displayable objects, each of which may have
45 * a list of child objects managed by another display
46 * group, and so on. TreeAssociation works exactly
47 * like a ListAssociation, with the additional capability
48 * to specify a "Children" aspect, that will allow child
49 * objects to be retrieved from a parent display group.
50 * Note that the children aspect requires the bound
51 * display group to have a DataSource that can vend a
52 * DataSource appropriate for the bound key That data
53 * source is then used to create data sources for
54 * child nodes, and so on.
55 *
56 * <ul>
57 *
58 * <li>titles: a property convertable to a string for
59 * display in the nodes of the tree. The objects in
60 * the bound display group will be used to populate the
61 * initial root nodes of the tree (more accurately,
62 * children of the offscreen root node in the tree).</li>
63 *
64 * <li>children: a property of a node value that returns
65 * zero, one or many objects, each of which will correspond
66 * to a child node for the corresponding node in the tree.
67 * The data source of the bound display group is replaced
68 * a data source that populates the display group with
69 * the visible nodes in the tree component as determined by
70 * calling fetchObjectsIntoChildrenGroup.
71 * If this aspect is not bound, the tree behaves like a list.
72 * <br><br>
73 * Binding this aspect with a null display group is the same
74 * as binding it with the titles display group.
75 * In this configuration the contents of the titles
76 * display group will be replaced with the visible nodes in the
77 * tree component, as specified above, replacing the existing
78 * data source.
79 * <br><br>
80 * In that case, the display groups for the nodes in
81 * the tree will still use the original data source
82 * for resolving their children key, and programmatically
83 * setting the contents of the display group will still
84 * repopulate the root nodes of the tree.
85 * </li>
86 *
87 * <li>isLeaf: a property of a node value that returns
88 * a value convertable to a boolean value (aside from
89 * an actual boolean value, zeroes evaluate to true,
90 * as does any String containing "yes" or "true" or that
91 * is convertable to a number equal to zero; other values
92 * evaluate to false).
93 * <br><br>
94 * If the isLeaf aspect is not bound,
95 * the tree must force nodes to load their children to
96 * determine whether they are leaf nodes (in effect
97 * loading the grandchildren for any expanded node).
98 * If bound, child loading is deferred until the node
99 * is actually expanded.
100 * <br><br>
101 * For example, binding this value to a null
102 * display group and the key "false" will result in a
103 * deferred-loading tree that works much like Windows
104 * Explorer's network volume browser - all nodes appear
105 * with "pluses" until they are expanded.
106 * <br><br>
107 * Note that the display group is ignored: the property
108 * will be applied directly to the object corresponding
109 * to the node.</li>
110 *
111 * </ul>
112 *
113 * All other usage is as TreeModelAssociation.
114 *
115 * @author michael@mpowers.net
116 * @author $Author: cgruber $
117 * @version $Revision: 904 $
118 */
119 public class TreeAssociation extends TreeModelAssociation
120 implements FocusListener, TreeExpansionListener, TreeWillExpandListener, Runnable
121 {
122 private boolean isExpanding;
123 private Vector nodeQueue;
124 private Vector structureQueue;
125 private boolean isRunning;
126
127 /***
128 * Constructor expecting a JTree.
129 */
130 public TreeAssociation ( Object anObject )
131 {
132 super( anObject );
133 init();
134 }
135
136 /***
137 * Constructor expecting a JTree or similar component
138 * and specifying a label for the root node.
139 */
140 public TreeAssociation( Object anObject, Object aRootLabel )
141 {
142 super( anObject );
143 init();
144 rootLabel = aRootLabel;
145 rootNode.setUserObject( aRootLabel );
146 }
147
148
149 private JTree component()
150 {
151 return (JTree) object();
152 }
153
154 /***
155 * Called by both constructors.
156 */
157 protected void init()
158 {
159 isExpanding = false;
160 isRunning = false;
161 nodeQueue = new Vector();
162 structureQueue = new Vector();
163 component().addFocusListener( this );
164 component().addTreeExpansionListener( this );
165 component().addTreeWillExpandListener( this );
166 }
167
168 /***
169 * Returns whether this class can control the specified
170 * object.
171 */
172 public static boolean isUsableWithObject ( Object anObject )
173 {
174 return ( anObject instanceof JTree );
175 }
176
177 /***
178 * Overridden to not fire events during initial population.
179 */
180 public void establishConnection ()
181 {
182 isExpanding = true;
183 super.establishConnection();
184 isExpanding = false;
185 }
186
187
188
189 public void valueChanged(TreeSelectionEvent e)
190 {
191 if ( ! isListening ) return;
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217 Runnable select = new Runnable()
218 {
219 public void run()
220 {
221 selectFromSelectionModel();
222 }
223 };
224
225 if ( selectionPaintedImmediately )
226 {
227 if ( object() instanceof java.awt.Component )
228 {
229 ((java.awt.Component)object()).repaint();
230 }
231 EventQueue.invokeLater( select );
232 }
233 else
234 {
235 select.run();
236 }
237 }
238
239 /***
240 * Overridden to check whether the node is visible
241 * in the tree on screen. Offscreen in a scrollpane
242 * does not count.
243 */
244 public boolean isVisible(Object node)
245 {
246 JTree tree = (JTree) object();
247 TreePath path = ((DisplayGroupNode)node).treePath();
248 if ( tree.isVisible( path ) )
249 {
250 Rectangle rowRect = tree.getPathBounds( path );
251 if ( rowRect != null )
252 {
253 Rectangle visible = tree.getVisibleRect();
254 if ( visible != null )
255 {
256
257 return visible.intersects( rowRect );
258 }
259 }
260 }
261
262 return false;
263 }
264
265 /***
266 * Fires a tree nodes changed event to all listeners.
267 * Provided as a convenience if you need to make manual
268 * changes to the tree model.
269 */
270 public void fireTreeNodesChanged(final Object source,
271 final Object[] path,
272 final int[] childIndices,
273 final Object[] children)
274 {
275 if ( !isExpanding )
276 {
277 for ( int i = 0; i < children.length; i++ )
278 {
279 nodeQueue.add( children[i] );
280 }
281 if ( !isRunning )
282 {
283 isRunning = true;
284 EventQueue.invokeLater( this );
285 }
286 }
287 }
288
289 /***
290 * Fires a tree nodes inserted event to all listeners.
291 * Provided as a convenience if you need to make manual
292 * changes to the tree model.
293 */
294 public void fireTreeNodesInserted(Object source,
295 Object[] path,
296 int[] childIndices,
297 Object[] children)
298 {
299 if ( !isExpanding )
300 {
301 super.fireTreeNodesInserted( source, path, childIndices, children );
302 EventQueue.invokeLater( this );
303 }
304 }
305
306 /***
307 * Fires a tree nodes removed event to all listeners.
308 * Provided as a convenience if you need to make manual
309 * changes to the tree model.
310 */
311 public void fireTreeNodesRemoved(Object source,
312 Object[] path,
313 int[] childIndices,
314 Object[] children)
315 {
316 if ( !isExpanding )
317 {
318 super.fireTreeNodesRemoved( source, path, childIndices, children );
319 EventQueue.invokeLater( this );
320 }
321 }
322
323 /***
324 * Fires a tree structure changed event to all listeners.
325 * Provided as a convenience if you need to make manual
326 * changes to the tree model.
327 */
328 public void fireTreeStructureChanged(Object source,
329 Object[] path,
330 int[] childIndices,
331 Object[] children)
332 {
333 if ( !isExpanding )
334 {
335 structureQueue.add( path[path.length-1] );
336 if ( !isRunning )
337 {
338 isRunning = true;
339 EventQueue.invokeLater( this );
340 }
341 }
342 }
343
344 /***
345 * Overridden to return all visible rows in the tree.
346 */
347 public NSArray objectsFetchedIntoChildrenGroup()
348 {
349 JTree tree = (JTree) object();
350 NSMutableArray objectList = new NSMutableArray();
351
352 int count = tree.getRowCount();
353 for ( int i = 0; i < count; i++ )
354 {
355 objectList.add(
356 ((DisplayGroupNode) tree.getPathForRow( i ).getLastPathComponent()).object() );
357 }
358
359
360 return objectList;
361 }
362
363
364
365 /***
366 * Notifies of beginning of edit.
367 */
368 public void focusGained(FocusEvent evt)
369 {
370 Object o;
371 EODisplayGroup displayGroup;
372 Enumeration e = aspects().objectEnumerator();
373 while ( e.hasMoreElements() )
374 {
375 displayGroup =
376 displayGroupForAspect( e.nextElement().toString() );
377 if ( displayGroup != null )
378 {
379 displayGroup.associationDidBeginEditing( this );
380 }
381 }
382 }
383
384 /***
385 * Updates object on focus lost and notifies of end of edit.
386 */
387 public void focusLost(FocusEvent evt)
388 {
389 if ( ! component().isEditing() )
390 {
391 Object o;
392 EODisplayGroup displayGroup;
393 Enumeration e = aspects().objectEnumerator();
394 while ( e.hasMoreElements() )
395 {
396 displayGroup =
397 displayGroupForAspect( e.nextElement().toString() );
398 if ( displayGroup != null )
399 {
400 displayGroup.associationDidEndEditing( this );
401 }
402 }
403 }
404 }
405
406
407
408 public void treeWillExpand(TreeExpansionEvent event)
409 throws ExpandVetoException
410 {
411 isExpanding = true;
412 }
413
414 public void treeWillCollapse(TreeExpansionEvent event)
415 throws ExpandVetoException
416 {
417
418 }
419
420
421
422 /***
423 * Updates the children display group, if any.
424 */
425 public void treeExpanded(TreeExpansionEvent event)
426 {
427 isExpanding = false;
428 if ( childrenDisplayGroup != null )
429 {
430 removeAsListener();
431 childrenDisplayGroup.fetch();
432 addAsListener();
433 }
434 }
435
436 /***
437 * Updates the children display group, if any.
438 */
439 public void treeCollapsed(TreeExpansionEvent event)
440 {
441 if ( childrenDisplayGroup != null )
442 {
443 removeAsListener();
444 childrenDisplayGroup.fetch();
445 addAsListener();
446 }
447 }
448
449
450
451 /***
452 * Fires any queued node changed and structure changed events.
453 * Typically invoked on a delayed event loop.
454 */
455 public void run()
456 {
457 DisplayGroupNode node;
458 int index;
459 Iterator i;
460
461 i = nodeQueue.iterator();
462 while ( i.hasNext() )
463 {
464 node = (DisplayGroupNode) i.next();
465 index = ((DisplayGroupNode)node.parentGroup).getIndex( node );
466 if ( ( index != -1 )
467 && ( node.treePath().getParentPath() != null ) )
468 {
469 super.fireTreeNodesChanged(
470 node,
471 node.treePath().getParentPath().getPath(),
472 new int[] { index },
473 new Object[] { node } );
474 }
475 }
476 nodeQueue.clear();
477
478 i = structureQueue.iterator();
479 while ( i.hasNext() )
480 {
481 node = (DisplayGroupNode) i.next();
482 super.fireTreeStructureChanged(
483 node,
484 node.treePath().getPath(),
485 null,
486 null );
487 }
488 structureQueue.clear();
489
490 isRunning = false;
491
492
493
494
495
496
497
498
499
500
501
502
503
504 }
505 }
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582