1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
|
/*
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.
*
*
*/
|