KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > swing > outline > EventBroadcaster


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.swing.outline;
21
22 import java.util.ArrayList JavaDoc;
23 import java.util.Arrays JavaDoc;
24 import java.util.List JavaDoc;
25 import javax.swing.SwingUtilities JavaDoc;
26 import javax.swing.event.TableModelEvent JavaDoc;
27 import javax.swing.event.TableModelListener JavaDoc;
28 import javax.swing.event.TreeExpansionEvent JavaDoc;
29 import javax.swing.event.TreeExpansionListener JavaDoc;
30 import javax.swing.event.TreeModelEvent JavaDoc;
31 import javax.swing.event.TreeModelListener JavaDoc;
32 import javax.swing.table.TableModel JavaDoc;
33 import javax.swing.tree.AbstractLayoutCache JavaDoc;
34 import javax.swing.tree.ExpandVetoException JavaDoc;
35 import javax.swing.tree.TreeModel JavaDoc;
36 import javax.swing.tree.TreePath JavaDoc;
37
38 /** Responsible for handling tree model events from the user-supplied treemodel
39  * portion of a DefaultOutlineModel, translating them into appropriate
40  * TableModelEvents and refiring these events to listeners on the table model.
41  * <p>
42  * This class could be (and originally was) incorporated directly into
43  * DefaultOutlineModel, but is separated for better readability and separation
44  * of concerns.
45  *
46  * @author Tim Boudreau
47  */

48 final class EventBroadcaster implements TableModelListener JavaDoc, TreeModelListener JavaDoc, ExtTreeWillExpandListener, TreeExpansionListener JavaDoc {
49     
50     /** Debugging constant for whether logging should be enabled */
51     static boolean log = false;
52     
53     /** Debugging message counter to differentiate log entries */
54     private int logcount = 0;
55     
56     /** The model we will proxy */
57     private DefaultOutlineModel model;
58     
59     /** The last event sent to treeWillExpand/Collapse, used to compare against the
60      * next value sent to treeExpanded/Collapse */

61     private TreeExpansionEvent JavaDoc inProgressEvent = null;
62     
63     /** A TableModelEvent generated in treeWillExpand/Collapse (so, generated when
64      * data about the rows/columns in the tree model is still in sync with the
65      * TableModel), which will be fired from treeExpanded/Collapsed if the
66      * expansion event is not vetoed */

67     private TableModelEvent JavaDoc pendingExpansionEvent = null;
68
69     /** Are we in the middle of firing multiple TableModelEvents for a single
70      * TreeModelEvent. */

71     private boolean inMultiEvent = false;
72     
73     //Some constants we use to have a single method handle all translated
74
//event firing
75
private static final int NODES_CHANGED = 0;
76     private static final int NODES_INSERTED = 1;
77     private static final int NODES_REMOVED = 2;
78     private static final int STRUCTURE_CHANGED = 3;
79     
80     //XXX deleteme - string version of the avoid constants debug output:
81
private static final String JavaDoc[] types = new String JavaDoc[] {
82         "nodesChanged", "nodesInserted", "nodesRemoved", "structureChanged"
83     }; //NOI18N
84

85     /** List of table model listeners */
86     private List JavaDoc tableListeners = new ArrayList JavaDoc();
87     
88     /** List of tree model listeners */
89     private List JavaDoc treeListeners = new ArrayList JavaDoc();
90     
91     
92     /** Creates a new instance of EventBroadcaster which will
93      * produce events for the passed DefaultOutlineModel model. */

94     public EventBroadcaster(DefaultOutlineModel model) {
95         setModel (model);
96     }
97     
98     /** Debug logging */
99     private void log (String JavaDoc method, Object JavaDoc o) {
100         if (log) {
101             if (o instanceof TableModelEvent JavaDoc) {
102                 //TableModelEvents just give their hash code in toString()
103
o = tableModelEventToString ((TableModelEvent JavaDoc) o);
104             }
105             System.err.println("EB-" + (logcount++) + " " + method + ":" +
106                 (o instanceof String JavaDoc ?
107                 (String JavaDoc) o : o.toString()));
108         }
109     }
110     
111     
112 //***************** Bean properties/convenience getters & setters ************
113
/** Flag which is set to true while multiple TableModelEvents generated
114      * from a single TreeModelEvent are being fired, so clients can avoid
115      * any model queries until all pending changes have been fired. The
116      * main thing to avoid is any mid-process repaints, which can only happen
117      * if the response to an event will be to call paintImmediately().
118      * <p>
119      * This value is guaranteed to be true for the first of a group of
120      * related events, and false if tested in response to the final event.
121      */

122     public boolean areMoreEventsPending() {
123         return inMultiEvent;
124     }
125     
126     /** Get the outline model for which this broadcaster will proxy events*/
127     private DefaultOutlineModel getModel() {
128         return model;
129     }
130     
131     /** Set the outline model this broadcaster will proxy events for */
132     private void setModel(DefaultOutlineModel model) {
133         this.model = model;
134     }
135     
136     /** Convenience getter for the proxied model's layout cache */
137     private AbstractLayoutCache JavaDoc getLayout() {
138         return getModel().getLayout();
139     }
140     
141     /** Convenience getter for the proxied model's TreePathSupport */
142     private TreePathSupport getTreePathSupport() {
143         return getModel().getTreePathSupport();
144     }
145     
146     /** Convenience getter for the proxied model's user-supplied TreeModel */
147     private TreeModel JavaDoc getTreeModel() {
148         return getModel().getTreeModel();
149     }
150     
151     /** Convenience getter for the proxied model's user-supplied TableModel (in
152      * practice, an instance of ProxyTableModel driven by the tree model and a
153      * RowModel) */

154     private TableModel JavaDoc getTableModel() {
155         return getModel().getTableModel();
156     }
157    
158     
159     
160 //******************* Event source implementation **************************
161

162     /** Add a table model listener. All events fired by this EventBroadcaster
163      * will have the OutlineModel as the event source */

164     public synchronized void addTableModelListener(TableModelListener JavaDoc l) {
165         tableListeners.add (l);
166     }
167     
168     /** Add a tree model listener. All events fired by this EventBroadcaster
169      * will have the OutlineModel as the event source */

170     public synchronized void addTreeModelListener(TreeModelListener JavaDoc l) {
171         treeListeners.add (l);
172     }
173     
174     /** Remove a table model listener. */
175     public synchronized void removeTableModelListener(TableModelListener JavaDoc l) {
176         tableListeners.remove(l);
177     }
178     
179     /** Remove a tree model listener. */
180     public synchronized void removeTreeModelListener(TreeModelListener JavaDoc l) {
181         treeListeners.remove(l);
182     }
183     
184     /** Fire a table change to the list of listeners supplied. The event should
185      * already have its source set to be the OutlineModel we're proxying for. */

186     private void fireTableChange (TableModelEvent JavaDoc e, TableModelListener JavaDoc[] listeners) {
187         //Event may be null for offscreen info, etc.
188
if (e == null) {
189             return;
190         }
191         
192         assert (e.getSource() == getModel());
193         
194         log ("fireTableChange", e);
195         
196         for (int i=0; i < listeners.length; i++) {
197             listeners[i].tableChanged(e);
198         }
199     }
200     
201     /** Convenience method to fire a single table change to all listeners */
202     private void fireTableChange (TableModelEvent JavaDoc e) {
203         //Event may be null for offscreen info, etc.
204
if (e == null) {
205             return;
206         }
207         inMultiEvent = false;
208         TableModelListener JavaDoc[] listeners = getTableModelListeners();
209         fireTableChange(e, getTableModelListeners());
210     }
211     
212     /** Fires multiple table model events, setting the inMultiEvent flag
213      * as appropriate. */

214     private void fireTableChange (TableModelEvent JavaDoc[] e) {
215         //Event may be null for offscreen info, etc.
216
if (e == null || e.length==0) {
217             return;
218         }
219         
220         TableModelListener JavaDoc[] listeners = getTableModelListeners();
221         inMultiEvent = e.length > 1;
222         try {
223             for (int i=0; i < e.length; i++) {
224                 fireTableChange (e[i], listeners);
225                 if (i == e.length-1) {
226                     inMultiEvent = false;
227                 }
228             }
229         } finally {
230             inMultiEvent = false;
231         }
232     }
233     
234     /** Fetch an array of the currently registered table model listeners */
235     private TableModelListener JavaDoc[] getTableModelListeners() {
236         TableModelListener JavaDoc[] listeners = null;
237         synchronized (this) {
238             listeners = new TableModelListener JavaDoc[
239                 tableListeners.size()];
240             
241             listeners = (TableModelListener JavaDoc[])
242                 tableListeners.toArray(listeners);
243         }
244         return listeners;
245     }
246     
247     /** Fire the passed TreeModelEvent of the specified type to all
248      * registered TreeModelListeners. The passed event should already have
249      * its source set to be the model. */

250     private synchronized void fireTreeChange (TreeModelEvent JavaDoc e, int type) {
251         //Event may be null for offscreen info, etc.
252
if (e == null) {
253             return;
254         }
255         assert (e.getSource() == getModel());
256         
257         TreeModelListener JavaDoc[] listeners = null;
258         synchronized (this) {
259             listeners = new TreeModelListener JavaDoc[treeListeners.size()];
260             listeners = (TreeModelListener JavaDoc[]) treeListeners.toArray(listeners);
261         }
262         
263         log ("fireTreeChange-" + types[type], e);
264         
265         //Now refire it to any listeners
266
for (int i=0; i < listeners.length; i++) {
267             switch (type) {
268                 case NODES_CHANGED :
269                     listeners[i].treeNodesChanged(e);
270                     break;
271                 case NODES_INSERTED :
272                     listeners[i].treeNodesInserted(e);
273                     break;
274                 case NODES_REMOVED :
275                     listeners[i].treeNodesRemoved(e);
276                     break;
277                 case STRUCTURE_CHANGED :
278                     listeners[i].treeStructureChanged(e);
279                     break;
280                 default :
281                     assert false;
282             }
283         }
284     }
285     
286 //******************* Event listener implementations ************************
287

288     /** Process a change event from the user-supplied tree model. This
289      * method will throw an assertion failure if it receives any event type
290      * other than TableModelEvent.UPDATE - the ProxyTableModel should never,
291      * ever fire structural changes - only the tree model is allowed to do
292      * that. */

293     public void tableChanged(TableModelEvent JavaDoc e) {
294         assert SwingUtilities.isEventDispatchThread();
295         //The *ONLY* time we should see events here is due to user
296
//data entry. The ProxyTableModel should never change out
297
//from under us - all structural changes happen through the
298
//table model.
299
assert (e.getType() == e.UPDATE) : "Table model should only fire " +
300             "updates, never structural changes";
301         
302         fireTableChange (translateEvent(e));
303     }
304     
305     /** Process a change event from the user-supplied tree model.
306      * Order of operations:
307      * <ol><li>Refire the same tree event with the OutlineModel we're
308      * proxying as the source</li>
309      * <li>Create one or more table model events (more than one if the
310      * incoming event affects discontiguous rows) reflecting the effect
311      * of the tree change</li>
312      * <li>Call the method with the same signature as this one on the
313      * layout cache, so it will update its state appropriately</li>
314      * <li>Fire the generated TableModelEvent(s)</li></ol>
315      */

316     public void treeNodesChanged(TreeModelEvent JavaDoc e) {
317         assert SwingUtilities.isEventDispatchThread();
318         
319         fireTreeChange (translateEvent(e), NODES_CHANGED);
320         
321         TableModelEvent JavaDoc[] events = translateEvent(e, NODES_CHANGED);
322         getLayout().treeNodesChanged(e);
323         fireTableChange(events);
324     }
325     
326     /** Process a node insertion event from the user-supplied tree model
327      * Order of operations:
328      * <ol><li>Refire the same tree event with the OutlineModel we're
329      * proxying as the source</li>
330      * <li>Create one or more table model events (more than one if the
331      * incoming event affects discontiguous rows) reflecting the effect
332      * of the tree change</li>
333      * <li>Call the method with the same signature as this one on the
334      * layout cache, so it will update its state appropriately</li>
335      * <li>Fire the generated TableModelEvent(s)</li></ol>
336      */

337     public void treeNodesInserted(TreeModelEvent JavaDoc e) {
338         assert SwingUtilities.isEventDispatchThread();
339         
340         fireTreeChange (translateEvent(e), NODES_INSERTED);
341         
342         TableModelEvent JavaDoc[] events = translateEvent(e, NODES_INSERTED);
343         getLayout().treeNodesInserted(e);
344         fireTableChange(events);
345     }
346     
347     /** Process a node removal event from the user-supplied tree model
348      * Order of operations:
349      * <ol><li>Refire the same tree event with the OutlineModel we're
350      * proxying as the source</li>
351      * <li>Create one or more table model events (more than one if the
352      * incoming event affects discontiguous rows) reflecting the effect
353      * of the tree change</li>
354      * <li>Call the method with the same signature as this one on the
355      * layout cache, so it will update its state appropriately</li>
356      * <li>Fire the generated TableModelEvent(s)</li></ol>
357      */

358     public void treeNodesRemoved(TreeModelEvent JavaDoc e) {
359         assert SwingUtilities.isEventDispatchThread();
360         
361         fireTreeChange (e, NODES_REMOVED);
362         
363         TableModelEvent JavaDoc[] events = translateEvent(e, NODES_REMOVED);
364         getLayout().treeNodesRemoved(e);
365         fireTableChange(events);
366     }
367     
368     /** Process a structural change event from the user-supplied tree model.
369      * This will result in a generic &quot;something changed&quot;
370      * TableModelEvent being fired. */

371     public void treeStructureChanged(TreeModelEvent JavaDoc e) {
372         assert SwingUtilities.isEventDispatchThread();
373         
374         getLayout().treeStructureChanged(e);
375         fireTreeChange (e, STRUCTURE_CHANGED);
376         
377         //If it's a structural change, we need to dump all our info about the
378
//existing tree structure - it can be bogus now. Similar to JTree,
379
//this will have the effect of collapsing all expanded paths. The
380
//TreePathSupport takes care of dumping the layout cache's copy of
381
//such data
382
getTreePathSupport().clear();
383         
384         //We will just fire a "Something happened. Go figure out what." event.
385
fireTableChange (new TableModelEvent JavaDoc (getModel()));
386     }
387     
388     /** Receives a TreeWillCollapse event and constructs a TableModelEvent
389      * based on the pending changes while the model still reflects the unchanged
390      * state */

391     public void treeWillCollapse(TreeExpansionEvent JavaDoc event) throws ExpandVetoException JavaDoc {
392         assert SwingUtilities.isEventDispatchThread();
393         
394         log ("treeWillCollapse", event);
395         
396         //Construct the TableModelEvent here, before data structures have
397
//changed. We will fire it from TreeCollapsed if the change is
398
//not vetoed.
399
pendingExpansionEvent = translateEvent (event, false);
400         log ("treeWillCollapse generated ", pendingExpansionEvent);
401         inProgressEvent = event;
402     }
403     
404     /** Receives a TreeWillExpand event and constructs a TableModelEvent
405      * based on the pending changes while the model still reflects the unchanged
406      * state */

407     public void treeWillExpand(TreeExpansionEvent JavaDoc event) throws ExpandVetoException JavaDoc {
408         assert SwingUtilities.isEventDispatchThread();
409
410         log ("treeWillExpand", event);
411         
412         //Construct the TableModelEvent here, before data structures have
413
//changed. We will fire it from TreeExpanded if the change is not
414
//vetoed
415
pendingExpansionEvent = translateEvent (event, true);
416         
417         log ("treeWillExpand generated", pendingExpansionEvent);
418         inProgressEvent = event;
419     }
420
421     public void treeCollapsed(TreeExpansionEvent JavaDoc event) {
422         assert SwingUtilities.isEventDispatchThread();
423
424         log ("treeExpanded", event);
425         
426         //FixedHeightLayoutCache tests if the event is null.
427
//Don't know how it could be, but there's probably a reason...
428
if(event != null) {
429             TreePath JavaDoc path = event.getPath();
430
431             //Tell the layout about the change
432
if(path != null && getTreePathSupport().isVisible(path)) {
433                 getLayout().setExpandedState(path, false);
434             }
435         }
436
437         
438         log ("about to fire", pendingExpansionEvent);
439         
440         //Now fire a change on the owning row so its display is updated (it
441
//may have just become an expandable node)
442
TreePath JavaDoc path = event.getPath();
443         int row = getLayout().getRowForPath(path);
444         TableModelEvent JavaDoc evt = new TableModelEvent JavaDoc (getModel(), row, row, 0,
445             TableModelEvent.UPDATE);
446         fireTableChange(new TableModelEvent JavaDoc[] {evt, pendingExpansionEvent});
447         
448         pendingExpansionEvent = null;
449         inProgressEvent = null;
450     }
451     
452     /** Updates the layout to mark the descendants of the events path as also
453      * expanded if they were the last it was expanded, then fires a table change. */

454     public void treeExpanded(TreeExpansionEvent JavaDoc event) {
455         assert SwingUtilities.isEventDispatchThread();
456         
457         log ("treeExpanded", event);
458         
459         //Mysterious how the event could be null, but JTree tests it
460
//so we will too.
461
if(event != null) {
462             updateExpandedDescendants(event.getPath());
463         }
464
465         log ("about to fire", pendingExpansionEvent);
466         
467         //Now fire a change on the owning row so its display is updated (it
468
//may have just become an expandable node)
469
TreePath JavaDoc path = event.getPath();
470         int row = getLayout().getRowForPath(path);
471         TableModelEvent JavaDoc evt = new TableModelEvent JavaDoc (getModel(), row, row, 0,
472             TableModelEvent.UPDATE);
473         fireTableChange(new TableModelEvent JavaDoc[] {evt, pendingExpansionEvent});
474         
475         pendingExpansionEvent = null;
476         inProgressEvent = null;
477     }
478     
479     /** Messaged if the tree expansion event (for which we will have already
480      * constructed a TableModelEvent) was vetoed; disposes of the constructed
481      * TableModelEvent in that circumstance. */

482     public void treeExpansionVetoed(TreeExpansionEvent JavaDoc event, ExpandVetoException JavaDoc exception) {
483         assert SwingUtilities.isEventDispatchThread();
484         
485         log ("treeExpansionVetoed", exception);
486         
487         //Make sure the event that was vetoed is the one we're interested in
488
if (event == inProgressEvent) {
489             //If so, delete the expansion event we thought we were going
490
//to use in treeExpanded/treeCollapsed, so that it doesn't
491
//stick around forever holding references to objects from the
492
//model
493
pendingExpansionEvent = null;
494             inProgressEvent = null;
495         }
496     }
497     
498 //******************* Support routines for handling events ******************
499
//do I date myself by using the word "routines"? :-)
500

501     /** Re&euml;expand descendants of a newly expanded path which were
502      * expanded the last time their parent was expanded */

503     private void updateExpandedDescendants(TreePath JavaDoc path) {
504         getLayout().setExpandedState(path, true);
505
506         TreePath JavaDoc[] descendants =
507             getTreePathSupport().getExpandedDescendants(path);
508
509         if(descendants.length > 0) {
510             for (int i=0; i < descendants.length; i++) {
511                 getLayout().setExpandedState(descendants[i], true);
512             }
513         }
514     }
515
516     
517 //******************* Event translation routines ****************************
518

519     /** Creates a TableModelEvent identical to the original except that the
520      * column index has been shifted by +1. This is used to refire events
521      * from the ProxyTableModel (generated by RowModel.setValueFor()) as
522      * change events on the OutlineModel. */

523     private TableModelEvent JavaDoc translateEvent (TableModelEvent JavaDoc e) {
524         TableModelEvent JavaDoc nue = new TableModelEvent JavaDoc (getModel(),
525             e.getFirstRow(), e.getLastRow(), e.getColumn()+1, e.getType());
526         return nue;
527     }
528     
529     /** Creates an identical TreeModelEvent with the model we are proxying
530      * as the event source */

531     private TreeModelEvent JavaDoc translateEvent (TreeModelEvent JavaDoc e) {
532         //Create a new TreeModelEvent with us as the source
533
TreeModelEvent JavaDoc nue = new TreeModelEvent JavaDoc (getModel(), e.getPath(),
534             e.getChildIndices(), e.getChildren());
535         return nue;
536     }
537     
538     /** Tranlates a TreeModelEvent into one or more contiguous TableModelEvents
539      */

540     private TableModelEvent JavaDoc[] translateEvent (TreeModelEvent JavaDoc e, int type) {
541
542         TreePath JavaDoc path = e.getTreePath();
543         int row = getLayout().getRowForPath(path);
544         
545         //If the node is not expanded, we simply fire a change
546
//event for the parent
547
boolean inClosedNode = !getLayout().isExpanded(path);
548         if (inClosedNode) {
549             //If the node is closed, no expensive checks are needed - just
550
//fire a change on the parent node in case it needs to update
551
//its display
552
if (row != -1) {
553                 switch (type) {
554                     case NODES_CHANGED :
555                     case NODES_INSERTED :
556                     case NODES_REMOVED :
557                         return new TableModelEvent JavaDoc[] {
558                             new TableModelEvent JavaDoc (getModel(), row, row,
559                               0, TableModelEvent.UPDATE)
560                         };
561                     default:
562                         assert false : "Unknown event type " + type;
563                 }
564             }
565             //In a closed node that is not visible, no event needed
566
return new TableModelEvent JavaDoc[0];
567         }
568         
569         boolean discontiguous = isDiscontiguous(e);
570         
571         Object JavaDoc[] blocks;
572         if (discontiguous) {
573             blocks = getContiguousIndexBlocks(e, type == NODES_REMOVED);
574             log ("discontiguous " + types[type] + " event", blocks.length + " blocks");
575         } else {
576             blocks = new Object JavaDoc[] {e.getChildIndices()};
577         }
578         
579         
580         TableModelEvent JavaDoc[] result = new TableModelEvent JavaDoc[blocks.length];
581         for (int i=0; i < blocks.length; i++) {
582             
583             int[] currBlock = (int[]) blocks[i];
584             switch (type) {
585                 case NODES_CHANGED :
586                     result[i] = createTableChangeEvent (e, currBlock);
587                     break;
588                 case NODES_INSERTED :
589                     result[i] = createTableInsertionEvent (e, currBlock);
590                     break;
591                 case NODES_REMOVED :
592                     result[i] = createTableDeletionEvent (e, currBlock);
593                     break;
594                 default :
595                     assert false : "Unknown event type: " + type;
596             }
597         }
598         log ("translateEvent", e);
599         log ("generated table events", new Integer JavaDoc(result.length));
600         if (log) {
601             for (int i=0; i < result.length; i++) {
602                 log (" Event " + i, result[i]);
603             }
604         }
605         return result;
606     }
607     
608     /** Translates tree expansion event into an appropriate TableModelEvent
609      * indicating the number of rows added/removed at the appropriate index */

610     private TableModelEvent JavaDoc translateEvent (TreeExpansionEvent JavaDoc e, boolean expand) {
611         //PENDING: This code should be profiled - the descendent paths search
612
//is not cheap, and it might be less expensive (at least if the table
613
//does not have expensive painting logic) to simply fire a generic
614
//"something changed" table model event and be done with it.
615

616         TreePath JavaDoc path = e.getPath();
617         
618         //Add one because it is a child of the row.
619
int firstRow = getLayout().getRowForPath(path) + 1;
620         if (firstRow == -1) {
621             //This does not mean nothing happened, it may just be that we are
622
//a large model tree, and the FixedHeightLayoutCache says the
623
//change happened in a row that is not showing.
624

625             //TODO: Just to make the table scrollbar adjust itself appropriately,
626
//we may want to look up the number of children in the model and
627
//fire an event that says that that many rows were added. Waiting
628
//to see if anybody actually will use this (i.e. fires changes in
629
//offscreen nodes as a normal part of usage
630
return null;
631         }
632         
633         //Get all the expanded descendants of the path that was expanded/collapsed
634
TreePath JavaDoc[] paths = getTreePathSupport().getExpandedDescendants(path);
635         
636         //Start with the number of children of whatever was expanded/collapsed
637
int count = getTreeModel().getChildCount(path.getLastPathComponent());
638         
639         //Iterate any of the expanded children, adding in their child counts
640
for (int i=0; i < paths.length; i++) {
641             count += getTreeModel().getChildCount(paths[i].getLastPathComponent());
642         }
643         
644         //Now we can calculate the last row affected for real
645
int lastRow = firstRow + count -1;
646         
647         //Construct a table model event reflecting this data
648
TableModelEvent JavaDoc result = new TableModelEvent JavaDoc (getModel(), firstRow, lastRow,
649             TableModelEvent.ALL_COLUMNS, expand ? TableModelEvent.INSERT :
650             TableModelEvent.DELETE);
651             
652         return result;
653     }
654
655     /** Create a change TableModelEvent for the passed TreeModelEvent and the
656      * contiguous subrange of the TreeModelEvent's getChildIndices() value */

657     private TableModelEvent JavaDoc createTableChangeEvent (TreeModelEvent JavaDoc e, int[] indices) {
658         TableModelEvent JavaDoc result = null;
659         TreePath JavaDoc path = e.getTreePath();
660         int row = getLayout().getRowForPath(path);
661         
662         int first = indices[0];
663         int last = indices[indices.length-1];
664         
665         //TODO - does not need to be ALL_COLUMNS, but we need a way to determine
666
//which column index is the tree
667
result = new TableModelEvent JavaDoc (getModel(), first, last,
668             TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE);
669         
670         return result;
671     }
672     
673     /** Create an insertion TableModelEvent for the passed TreeModelEvent and the
674      * contiguous subrange of the TreeModelEvent's getChildIndices() value */

675     private TableModelEvent JavaDoc createTableInsertionEvent (TreeModelEvent JavaDoc e, int[] indices) {
676         TableModelEvent JavaDoc result = null;
677
678         log ("createTableInsertionEvent", e);
679         
680         TreePath JavaDoc path = e.getTreePath();
681         int row = getLayout().getRowForPath(path);
682         
683         boolean realInsert = getLayout().isExpanded(path);
684
685         if (realInsert) {
686             if (indices.length == 1) {
687                 //Only one index to change, fire a simple event. It
688
//will be the first index in the array + the row +
689
//1 because the 0th child of a node is 1 greater than
690
//its row index
691
int affectedRow = row + indices[0] + 1;
692                 result = new TableModelEvent JavaDoc (getModel(), affectedRow, affectedRow,
693                     TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT);
694
695             } else {
696                 //Find the first and last indices. Add one since it is at
697
//minimum the first index after the affected row, since it
698
//is a child of it.
699
int lowest = indices[0] + 1;
700                 int highest = indices[indices.length-1] + 1;
701                 result = new TableModelEvent JavaDoc (getModel(), row + lowest, row + highest,
702                     TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT);
703
704             }
705         } else {
706             //Nodes were inserted in an unexpanded parent. Just fire
707
//a change for that row and column so that it gets repainted
708
//in case the node there changed from leaf to non-leaf
709
result = new TableModelEvent JavaDoc (getModel(), row, row,
710                 TableModelEvent.ALL_COLUMNS); //TODO - specify only the tree column
711
}
712         return result;
713     }
714     
715     
716     /** Create a deletion TableModelEvent for the passed TreeModelEvent and the
717      * contiguous subrange of the TreeModelEvent's getChildIndices() value */

718     private TableModelEvent JavaDoc createTableDeletionEvent (TreeModelEvent JavaDoc e, int[] indices) {
719         TableModelEvent JavaDoc result = null;
720         
721         log ("createTableDeletionEvent " + Arrays.asList(toArrayOfInteger(indices)), e);
722         
723         TreePath JavaDoc path = e.getTreePath();
724         int row = getLayout().getRowForPath(path);
725         if (row == -1) {
726             //XXX could calculate based on last visible row?
727
return null;
728         }
729         
730         int countRemoved = indices.length;
731         
732         //Get the subset of the children in the event that correspond
733
//to the passed indices
734
Object JavaDoc[] children = getChildrenForIndices(e, indices);
735         
736         for (int i=0; i < children.length; i++) {
737             TreePath JavaDoc childPath = path.pathByAddingChild(children[i]);
738             if (getTreePathSupport().isExpanded(childPath)) {
739                 
740                 int visibleChildren =
741                     getLayout().getVisibleChildCount(childPath);
742                 
743                 if (log) {
744                     log (childPath + " has ", new Integer JavaDoc(visibleChildren));
745                 }
746                 
747                 countRemoved += visibleChildren;
748             }
749             getTreePathSupport().removePath(path);
750         }
751
752         //Add in the first index, and add one to it since the 0th
753
//will have the row index of its parent + 1
754
int firstRow = row + indices[0] + 1;
755         
756         log ("firstRow", new Integer JavaDoc(firstRow));
757         /*
758         if (countRemoved == 1) {
759             System.err.println("Only one removed: " + (row + indices[0] + 1));
760             result = new TableModelEvent (getModel(), firstRow, firstRow,
761                 TableModelEvent.ALL_COLUMNS,
762                 TableModelEvent.DELETE);
763         } else {
764          */

765             System.err.println("Count removed is " + countRemoved);
766
767             int lastRow = firstRow + (countRemoved - 1);
768
769             System.err.println("TableModelEvent: fromRow: " + firstRow + " toRow: " + lastRow);
770
771             result = new TableModelEvent JavaDoc (getModel(), firstRow, lastRow,
772                 TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE);
773         //}
774

775         /* //old code
776         
777             //Okay, one or more nodes was removed. The event's tree path
778             //will be the parent. Now we need to find out about any children
779             //that were also removed so we can create a TreeModelEvent with
780             //the right number of removed rows.
781             
782             //Note there is a slight impedance mismatch between TreeModel and
783             //TableModel here - if we're using a large model layout cache,
784             //we don't actually know what was offscreen - the data is already
785             //gone from the model, so even if we know it was expanded, we
786             //can't find out how many children it had.
787             
788             //The only thing this really affects is the scrollbar, and in
789             //fact, the standard JTable UIs will update it correctly, since
790             //the scrollbar will read getRowCount() to calculate its position.
791             //In theory, this could break on a hyper-efficient TableUI that
792             //attempted to manage scrollbar position *only* based on the
793             //content of table model events. That's pretty unlikely; but if
794             //it happens, the solution is for Outline.getPreferredSize() to
795             //proxy the preferred size from the layout cache
796             
797             TreePath path = e.getTreePath();
798             boolean lastRemoveWasExpanded = getTreePathSupport().isExpanded(path);
799             int countRemoved = 1;
800             
801             //See if it's expanded - if it wasn't we're just going to blow
802             //away one row anyway
803             if (lastRemoveWasExpanded) {
804                 Object[] kids = e.getChildren();
805                 
806                 //TranslateEvent uses countRemoved to set the TableModelEvent
807                 countRemoved = kids.length;
808                 
809                 //Iterate the removed children
810                 for (int i=0; i < kids.length; i++) {
811                     //Get the child's path
812                     TreePath childPath = path.pathByAddingChild(kids[i]);
813                     
814                     //If it's not expanded, we don't care
815                     if (getTreePathSupport().isExpanded(childPath)) {
816                         //Find the number of *visible* children. This may not
817                         //be all the children, but it's the best information we have.
818                         int visibleChildren =
819                             getLayout().getVisibleChildCount(childPath);
820
821                         //add in the number of visible children
822                         countRemoved += visibleChildren;
823                     }
824                     //Kill any references to the dead path to avoid memory leaks
825                     getTreePathSupport().removePath(childPath);
826                 }
827             }
828             
829             //Tell the layout what happened, now that we've mined it for data
830             //about the visible children of the removed paths
831             getLayout().treeNodesRemoved(e);
832             
833                 boolean realRemove = lastRemoveWasExpanded;//getLayout().isExpanded(path);
834                 if (realRemove) {
835                     System.err.println("Nodes removed from open countainer");
836                     int[] indices = e.getChildIndices();
837                     
838                     //Comments in FixedHeightLayoutCache suggest we cannot
839                     //assume array is sorted, though it should be
840                     Arrays.sort(indices);
841                     if (indices.length == 0) {
842                         //well, that's a little weird
843                         return null;
844                     } else if (countRemoved == 1) {
845                         System.err.println("Only one removed: " + (row + indices[0] + 1));
846                         return new TableModelEvent (this, row + indices[0] + 1,
847                             row + indices[0] + 1, TableModelEvent.ALL_COLUMNS,
848                             TableModelEvent.DELETE);
849                     }
850                     System.err.println("Count removed is " + countRemoved);
851                     
852                     //Add in the first index, and add one to it since the 0th
853                     //will have the row index of its parent + 1
854                     int firstRow = row + indices[0] + 1;
855                     int lastRow = firstRow + (countRemoved - 1);
856                     
857                     System.err.println("TableModelEvent: fromRow: " + firstRow + " toRow: " + lastRow);
858                      
859                     return new TableModelEvent (this, firstRow, lastRow,
860                         TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE);
861                 } else {
862                     System.err.println("Nodes removed from a closed container. Change for row " + row);
863                     //Nodes were removed in an unexpanded parent. Just fire
864                     //a change for that row and column so that it gets repainted
865                     //in case the node there changed from leaf to non-leaf
866                     TableModelEvent evt = new TableModelEvent (this, row, row, 0); //XXX 0 may not be tree column
867                     System.err.println(" Returning " + evt);
868                     return evt;
869                 }
870          */

871         
872         return result;
873     }
874
875
876 //**************** Static utility routines *****************************
877

878     /** Determine if the indices referred to by a TreeModelEvent are
879      * contiguous. If they are not, we will need to generate multiple
880      * TableModelEvents for each contiguous block */

881     private static boolean isDiscontiguous (TreeModelEvent JavaDoc e) {
882         int[] indices = e.getChildIndices();
883         if (indices.length == 1) {
884             return false;
885         }
886         Arrays.sort(indices);
887         int lastVal = indices[0];
888         for (int i=1; i < indices.length; i++) {
889             if (indices[i] != lastVal + 1) {
890                 return true;
891             } else {
892                 lastVal++;
893             }
894         }
895         return false;
896     }
897     
898     /** Returns an array of int[]s each one representing a contiguous set of
899      * indices in the tree model events child indices - each of which can be
900      * fired as a single TableModelEvent. The length of the return value is
901      * the number of TableModelEvents required to represent this TreeModelEvent.
902      * If reverseOrder is true (needed for remove events, where the last indices
903      * must be removed first or the indices of later removals will be changed),
904      * the returned int[]s will be sorted in reverse order, and the order in
905      * which they are returned will also be from highest to lowest. */

906     private static Object JavaDoc[] getContiguousIndexBlocks (TreeModelEvent JavaDoc e, boolean reverseOrder) {
907         int[] indices = e.getChildIndices();
908         
909         //Quick check if there's only one index
910
if (indices.length == 1) {
911             return new Object JavaDoc[] {indices};
912         }
913         
914         //The array of int[]s we'll return
915
ArrayList JavaDoc al = new ArrayList JavaDoc();
916         
917         //Sort the indices as requested
918
if (reverseOrder) {
919             inverseSort (indices);
920         } else {
921             Arrays.sort (indices);
922         }
923
924
925         //The starting block
926
ArrayList JavaDoc currBlock = new ArrayList JavaDoc(indices.length / 2);
927         al.add(currBlock);
928         
929         //The value we'll check against the previous one to detect the
930
//end of contiguous segment
931
int lastVal = -1;
932         
933         //Iterate the indices
934
for (int i=0; i < indices.length; i++) {
935             if (i != 0) {
936                 //See if we've hit a discontinuity
937
boolean newBlock = reverseOrder ? indices[i] != lastVal - 1 :
938                     indices[i] != lastVal + 1;
939                     
940                 if (newBlock) {
941                     currBlock = new ArrayList JavaDoc(indices.length - 1);
942                     al.add(currBlock);
943                 }
944             }
945             currBlock.add (new Integer JavaDoc(indices[i]));
946             lastVal = indices[i];
947         }
948         
949         for (int i=0; i < al.size(); i++) {
950             ArrayList JavaDoc curr = (ArrayList JavaDoc) al.get(i);
951             Integer JavaDoc[] ints = (Integer JavaDoc[]) curr.toArray(new Integer JavaDoc[0]);
952             
953             al.set(i, toArrayOfInt(ints));
954         }
955         
956         return al.toArray();
957     }
958     
959     /** Get the children from a TreeModelEvent associated with the set of
960      * indices passed. */

961     private Object JavaDoc[] getChildrenForIndices (TreeModelEvent JavaDoc e, int[] indices) {
962         //XXX performance - better way to do this may be to have
963
//getContinguousIndexBlocks instead construct sub-treemodelevents -
964
//that would save having to do these iterations later to extract the
965
//children.
966

967         //At the same time, discontiguous child removals are relatively rare
968
//events - optimizing them heavily may not be a good use of time.
969
Object JavaDoc[] children = e.getChildren();
970         int[] allIndices = e.getChildIndices();
971         
972         ArrayList JavaDoc al = new ArrayList JavaDoc();
973         
974         for (int i=0; i < indices.length; i++) {
975             int pos = Arrays.binarySearch (allIndices, indices[i]);
976             if (pos > -1) {
977                 al.add (children[pos]);
978             }
979             if (al.size() == indices.length) {
980                 break;
981             }
982         }
983         return al.toArray();
984     }
985     
986     
987     /** Converts an Integer[] to an int[] */
988     private static int[] toArrayOfInt (Integer JavaDoc[] ints) {
989         int[] result = new int[ints.length];
990         for (int i=0; i < ints.length; i++) {
991             result[i] = ints[i].intValue();
992         }
993         return result;
994     }
995     
996     /** Converts an Integer[] to an int[] */
997     //XXX deleteme - used for debug logging only
998
private static Integer JavaDoc[] toArrayOfInteger (int[] ints) {
999         Integer JavaDoc[] result = new Integer JavaDoc[ints.length];
1000        for (int i=0; i < ints.length; i++) {
1001            result[i] = new Integer JavaDoc(ints[i]);
1002        }
1003        return result;
1004    }
1005    
1006    
1007    /** Sort an array of ints from highest to lowest */
1008    private static void inverseSort (int[] array) {
1009        //XXX replace with a proper sort algorithm at some point -
1010
//this is brute force
1011
for (int i=0; i < array.length; i++) {
1012            array[i] *= -1;
1013        }
1014        Arrays.sort(array);
1015        for (int i=0; i < array.length; i++) {
1016            array[i] *= -1;
1017        }
1018    }
1019    
1020    private static String JavaDoc tableModelEventToString (TableModelEvent JavaDoc e) {
1021        StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
1022        sb.append ("TableModelEvent ");
1023        switch (e.getType()) {
1024            case TableModelEvent.INSERT : sb.append ("insert ");
1025                 break;
1026            case TableModelEvent.DELETE : sb.append ("delete ");
1027                 break;
1028            case TableModelEvent.UPDATE : sb.append ("update ");
1029                 break;
1030            default : sb.append ("Unknown type " + e.getType());
1031        }
1032        sb.append ("from ");
1033        switch (e.getFirstRow()) {
1034            case TableModelEvent.HEADER_ROW : sb.append ("header row ");
1035                break;
1036            default : sb.append (e.getFirstRow());
1037                      sb.append (' ');
1038        }
1039        sb.append ("to ");
1040        sb.append (e.getLastRow());
1041        sb.append (" column ");
1042        switch (e.getColumn()) {
1043            case TableModelEvent.ALL_COLUMNS :
1044                sb.append ("ALL_COLUMNS");
1045                break;
1046            default : sb.append (e.getColumn());
1047        }
1048        return sb.toString();
1049    }
1050}
1051
Popular Tags