KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jdesktop > swing > JXTreeTable


1 /*
2  * $Id: JXTreeTable.java,v 1.18 2005/02/22 17:12:07 kleopatra Exp $
3  *
4  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
5  * Santa Clara, California 95054, U.S.A. All rights reserved.
6  */

7
8
9 package org.jdesktop.swing;
10
11 import java.util.Enumeration JavaDoc;
12 import java.util.EventObject JavaDoc;
13 import java.util.Enumeration JavaDoc;
14
15 import java.awt.Color JavaDoc;
16 import java.awt.Component JavaDoc;
17 import java.awt.Dimension JavaDoc;
18 import java.awt.Graphics JavaDoc;
19 import java.awt.Point JavaDoc;
20 import java.awt.Rectangle JavaDoc;
21 import java.awt.event.ActionEvent JavaDoc;
22 import java.awt.event.InputEvent JavaDoc;
23 import java.awt.event.MouseEvent JavaDoc;
24 import java.beans.PropertyChangeEvent JavaDoc;
25 import java.beans.PropertyChangeListener JavaDoc;
26
27 import javax.swing.ActionMap JavaDoc;
28 import javax.swing.BorderFactory JavaDoc;
29 import javax.swing.Icon JavaDoc;
30 import javax.swing.JTable JavaDoc;
31 import javax.swing.JTree JavaDoc;
32 import javax.swing.ListSelectionModel JavaDoc;
33 import javax.swing.SwingUtilities JavaDoc;
34 import javax.swing.UIManager JavaDoc;
35 import javax.swing.border.Border JavaDoc;
36 import javax.swing.event.ListSelectionEvent JavaDoc;
37 import javax.swing.event.ListSelectionListener JavaDoc;
38 import javax.swing.event.TreeExpansionEvent JavaDoc;
39 import javax.swing.event.TreeExpansionListener JavaDoc;
40 import javax.swing.event.TreeModelEvent JavaDoc;
41 import javax.swing.event.TreeModelListener JavaDoc;
42 import javax.swing.plaf.UIResource JavaDoc;
43 import javax.swing.plaf.basic.BasicTreeUI JavaDoc;
44 import javax.swing.table.AbstractTableModel JavaDoc;
45 import javax.swing.table.TableCellRenderer JavaDoc;
46 import javax.swing.table.TableModel JavaDoc;
47 import javax.swing.tree.DefaultTreeCellRenderer JavaDoc;
48 import javax.swing.tree.DefaultTreeSelectionModel JavaDoc;
49 import javax.swing.tree.TreeCellRenderer JavaDoc;
50 import javax.swing.tree.TreePath JavaDoc;
51 import javax.swing.tree.TreeSelectionModel JavaDoc;
52
53 import org.jdesktop.swing.decorator.ComponentAdapter;
54 import org.jdesktop.swing.treetable.AbstractTreeTableModel;
55 import org.jdesktop.swing.treetable.DefaultTreeTableModel;
56 import org.jdesktop.swing.treetable.TreeTableCellEditor;
57 import org.jdesktop.swing.treetable.TreeTableModel;
58
59 /**
60  * <p><code>JXTreeTable</code> is a specialized {@link javax.swing.JTable table}
61  * consisting of a single column in which to display hierarchical data, and any
62  * number of other columns in which to display regular data. The interface for
63  * the data model used by a <code>JXTreeTable</code> is
64  * {@link org.jdesktop.swing.treetable.TreeTableModel}. It extends the
65  * {@link javax.swing.tree.TreeModel} interface to allow access to cell data by
66  * column indices within each node of the tree hierarchy.</p>
67  *
68  * <p>The most straightforward way create and use a <code>JXTreeTable</code>, is to
69  * first create a suitable data model for it, and pass that to a
70  * <code>JXTreeTable</code> constructor, as shown below:
71  * <pre>
72  * TreeTableModel treeTableModel = new FileSystemModel(); // any TreeTableModel
73  * JXTreeTable treeTable = new JXTreeTable(treeTableModel);
74  * JScrollPane scrollpane = new JScrollPane(treeTable);
75  * </pre>
76  * See {@link javax.swing.JTable} for an explanation of why putting the treetable
77  * inside a scroll pane is necessary.</p>
78  *
79  * <p>A single treetable model instance may be shared among more than one
80  * <code>JXTreeTable</code> instances. To access the treetable model, always call
81  * {@link #getTreeTableModel() getTreeTableModel} and
82  * {@link #setTreeTableModel(org.jdesktop.swing.treetable.TreeTableModel) setTreeTableModel}.
83  * <code>JXTreeTable</code> wraps the supplied treetable model inside a private
84  * adapter class to adapt it to a {@link javax.swing.table.TableModel}. Although
85  * the model adapter is accessible through the {@link #getModel() getModel} method, you
86  * should avoid accessing and manipulating it in any way. In particular, each
87  * model adapter instance is tightly bound to a single table instance, and any
88  * attempt to share it with another table (for example, by calling
89  * {@link #setModel(javax.swing.table.TableModel) setModel})
90  * will throw an <code>IllegalArgumentException</code>!
91  *
92  * @author Philip Milne
93  * @author Scott Violet
94  * @author Ramesh Gupta
95  */

96 public class JXTreeTable extends JXTable {
97     // TOTAL HACK to fix icons that disappeared in a regression after M3!
98
private Icon JavaDoc collapsedIcon = null;
99     private Icon JavaDoc expandedIcon = null;
100     private Icon JavaDoc closedIcon = null;
101     private Icon JavaDoc openIcon = null;
102     private Icon JavaDoc leafIcon = null;
103
104     /**
105      * Renderer used to render cells within the
106      * {@link #isHierarchical(int) hierarchical} column.
107      */

108     private TreeTableCellRenderer renderer = null;
109     // renderer extends JXTree and implements TableCellRenderer
110

111     /**
112      * Constructs a JXTreeTable using a
113      * {@link org.jdesktop.swing.treetable.DefaultTreeTableModel}.
114      */

115     public JXTreeTable() {
116         this(new DefaultTreeTableModel());
117     }
118
119     /**
120      * Constructs a JXTreeTable using the specified
121      * {@link org.jdesktop.swing.treetable.TreeTableModel}.
122      *
123      * @param treeModel model for the JXTreeTable
124      */

125     public JXTreeTable(TreeTableModel treeModel) {
126         // Implementation note:
127
// Make sure that the SAME instance of treeModel is passed to the
128
// constructor for TreeTableCellRenderer as is passed in the first
129
// argument to the following chained constructor for this JXTreeTable:
130
this(treeModel, new JXTreeTable.TreeTableCellRenderer(treeModel));
131     }
132
133     /**
134      * Constructs a <code>JXTreeTable</code> using the specified
135      * {@link org.jdesktop.swing.treetable.TreeTableModel} and
136      * {@link org.jdesktop.swing.treetable.TreeTableCellRenderer}. The renderer
137      * must have been constructed using the same instance of
138      * {@link org.jdesktop.swing.treetable.TreeTableModel} as passed to this
139      * constructor.
140      *
141      * @param treeModel model for the JXTreeTable
142      * @param renderer cell renderer for the tree portion of this JXTreeTable instance.
143      * @throws IllegalArgumentException if an attempt is made to instantiate
144      * JXTreeTable and TreeTableCellRenderer with different instances of TreeTableModel.
145      */

146     private JXTreeTable(TreeTableModel treeModel, TreeTableCellRenderer renderer) {
147         // To avoid unnecessary object creation, such as the construction of a
148
// DefaultTableModel, it is better to invoke super(TreeTableModelAdapter)
149
// directly, instead of first invoking super() followed by a call to
150
// setTreeTableModel(TreeTableModel).
151

152         // Adapt tree model to table model before invoking super()
153
super(new TreeTableModelAdapter(treeModel, renderer));
154
155         // Enforce referential integrity; bail on fail
156
if (treeModel != renderer.getModel()) { // do not use assert here!
157
throw new IllegalArgumentException JavaDoc("Mismatched TreeTableModel");
158         }
159
160         // renderer-related initialization -- also called from setTreeTableModel()
161
init(renderer); // private method
162
initActions();
163
164         // Install the default editor.
165
setDefaultEditor(AbstractTreeTableModel.hierarchicalColumnClass,
166             new TreeTableCellEditor(this, renderer));
167
168         // No grid.
169
setShowGrid(false); // superclass default is "true"
170

171         // Default intercell spacing
172
setIntercellSpacing(spacing); // for both row margin and column margin
173

174         // JTable supports row margins and intercell spacing, but JTree doesn't.
175
// We must reconcile the differences in the semantics of rowHeight as
176
// understood by JTable and JTree by overriding both setRowHeight() and
177
// setRowMargin();
178
setRowHeight(getRowHeight()); // call overridden setRowHeight()
179
setRowMargin(getRowMargin()); // call overridden setRowMargin()
180

181     }
182
183
184     private void initActions() {
185         // Register the actions that this class can handle.
186
ActionMap JavaDoc map = getActionMap();
187         map.put("expand-all", new Actions("expand-all"));
188         map.put("collapse-all", new Actions("collapse-all"));
189     }
190
191     /**
192      * A small class which dispatches actions.
193      * TODO: Is there a way that we can make this static?
194      */

195     private class Actions extends UIAction {
196         Actions(String JavaDoc name) {
197             super(name);
198         }
199
200         public void actionPerformed(ActionEvent JavaDoc evt) {
201             if ("expand-all".equals(getName())) {
202         expandAll();
203             }
204             else if ("collapse-all".equals(getName())) {
205                 collapseAll();
206             }
207         }
208     }
209
210     /**
211      * Overrides superclass version to be a no-op.
212      */

213     protected void resetSorter() {
214         // no-op
215
}
216
217     /**
218      * Overrides superclass version to be a no-op.
219      */

220     protected void setSorter(int columnIndex) {
221         // no-op
222
}
223
224     /**
225      * <p>Sets whether the table draws horizontal lines between cells. It draws
226      * the lines if <code>show</code> is true; otherwise it doesn't. By default,
227      * a table draws the lines.</p>
228      *
229      * <p>If you want the lines to be drawn, make sure that the row margin or
230      * horizontal intercell spacing is greater than zero.</p>
231      *
232      * @param show true, if horizontal lines should be drawn; false, if lines
233      * should not be drawn
234      * @see javax.swing.JTable#getShowHorizontalLines() getShowHorizontalLines
235      * @see #setRowMargin(int) setRowMargin
236      * @see javax.swing.JTable#setIntercellSpacing(java.awt.Dimension) setIntercellSpacing
237      */

238     public void setShowHorizontalLines(boolean show) {
239         super.setShowHorizontalLines(show);
240     }
241
242     /**
243      * <p>Sets whether the table draws vertical lines between cells. It draws
244      * the lines if <code>show</code> is true; otherwise it doesn't. By default,
245      * a table draws the lines.</p>
246      *
247      * <p>If you want the lines to be drawn, make sure that the column margin or
248      * vertical intercell spacing is greater than zero.</p>
249      *
250      * @param show true, if vertical lines should be drawn; false, if lines
251      * should not be drawn
252      * @see javax.swing.JTable#getShowVerticalLines() getShowVerticalLines
253      * @see #setColumnMargin(int) setColumnMargin
254      * @see javax.swing.JTable#setIntercellSpacing(java.awt.Dimension) setIntercellSpacing
255      */

256     public void setShowVerticalLines(boolean show) {
257         super.setShowVerticalLines(show);
258     }
259
260     /**
261      * Overriden to invoke repaint for the particular location if
262      * the column contains the tree. This is done as the tree editor does
263      * not fill the bounds of the cell, we need the renderer to paint
264      * the tree in the background, and then draw the editor over it.
265      * You should not need to call this method directly.
266      *
267      * {@inheritDoc}
268      */

269     public boolean editCellAt(int row, int column, EventObject JavaDoc e) {
270         expandOrCollapseNode(e); // RG: Fix Issue 49!
271
boolean canEdit = super.editCellAt(row, column, e);
272         if (canEdit && isHierarchical(column)) {
273             repaint(getCellRect(row, column, false));
274         }
275         return canEdit;
276     }
277
278     private void expandOrCollapseNode(EventObject JavaDoc e) {
279         if (e instanceof MouseEvent JavaDoc) {
280             MouseEvent JavaDoc me = (MouseEvent JavaDoc) e;
281             // If the modifiers are not 0 (or the left mouse button),
282
// tree may try and toggle the selection, and table
283
// will then try and toggle, resulting in the
284
// selection remaining the same. To avoid this, we
285
// only dispatch when the modifiers are 0 (or the left mouse
286
// button).
287
if (me.getModifiers() == 0 ||
288                 me.getModifiers() == InputEvent.BUTTON1_MASK) {
289                 final int count = getColumnCount();
290                 for (int i = count - 1; i >= 0; i--) {
291                     if (isHierarchical(i)) {
292                         
293                         int savedHeight = renderer.getRowHeight();
294                         renderer.setRowHeight(getRowHeight());
295                         MouseEvent JavaDoc pressed = new MouseEvent JavaDoc
296                             (renderer,
297                              me.getID(),
298                              me.getWhen(),
299                              me.getModifiers(),
300                              me.getX() - getCellRect(0, i, false).x,
301                              me.getY(),
302                              me.getClickCount(),
303                              me.isPopupTrigger());
304                         renderer.dispatchEvent(pressed);
305                         // For Mac OS X, we need to dispatch a MOUSE_RELEASED as well
306
MouseEvent JavaDoc released = new MouseEvent JavaDoc
307                             (renderer,
308                              java.awt.event.MouseEvent.MOUSE_RELEASED,
309                              pressed.getWhen(),
310                              pressed.getModifiers(),
311                              pressed.getX(),
312                              pressed.getY(),
313                              pressed.getClickCount(),
314                              pressed.isPopupTrigger());
315                         renderer.dispatchEvent(released);
316                         renderer.setRowHeight(savedHeight);
317                         break;
318                     }
319                 }
320             }
321         }
322     }
323
324     /**
325      * Overridden to provide a workaround for BasicTableUI anomaly. Make sure
326      * the UI never tries to resize the editor. The UI currently uses different
327      * techniques to paint the renderers and editors. So, overriding setBounds()
328      * is not the right thing to do for an editor. Returning -1 for the
329      * editing row in this case, ensures the editor is never painted.
330      *
331      * {@inheritDoc}
332      */

333     public int getEditingRow() {
334         return isHierarchical(editingColumn) ? -1 : editingRow;
335     }
336
337     /**
338      * Returns the actual row that is editing as <code>getEditingRow</code>
339      * will always return -1.
340      */

341     private int realEditingRow() {
342         return editingRow;
343     }
344
345     /**
346      * Sets the data model for this JXTreeTable to the specified
347      * {@link org.jdesktop.swing.treetable.TreeTableModel}. The same data model
348      * may be shared by any number of JXTreeTable instances.
349      *
350      * @param treeModel data model for this JXTreeTable
351      */

352     public void setTreeTableModel(TreeTableModel treeModel) {
353         // The original TreeTable implementation didn't support this signature.
354

355 // CHANGED LINE TO CORRECT ISSUE 151 - rlopes
356
//renderer = new TreeTableCellRenderer(treeModel);
357
renderer.setModel(treeModel);
358         // Adapt tree model to table model before invoking setModel()
359
setModel(new TreeTableModelAdapter(treeModel, renderer));
360         // Enforce referential integrity; bail on fail
361
if (treeModel != renderer.getModel()) { // do not use assert here!
362
throw new IllegalArgumentException JavaDoc("Mismatched TreeTableModel");
363         }
364 // COMMENTED LINE TO CORRECT ISSUE 151 - rlopes
365
// Not needed because the renderer doesn't change, and is already initialized
366
// in the constructor
367
// init(renderer); // renderer is a JTree
368

369         // Install the default editor.
370
setDefaultEditor(AbstractTreeTableModel.hierarchicalColumnClass,
371             new TreeTableCellEditor(this, renderer));
372
373         // JTable supports row margins and intercell spacing, but JTree doesn't.
374
// We must reconcile the differences in the semantics of rowHeight as
375
// understood by JTable and JTree by overriding both setRowHeight() and
376
// setRowMargin();
377
setRowHeight(getRowHeight()); // call overridden setRowHeight()
378
setRowMargin(getRowMargin()); // call overridden setRowMargin()
379
}
380
381     /**
382      * Returns the underlying TreeTableModel for this JXTreeTable.
383      *
384      * @return the underlying TreeTableModel for this JXTreeTable
385      */

386     public TreeTableModel getTreeTableModel() {
387         return ((TreeTableModelAdapter) getModel()).getTreeTableModel();
388     }
389
390     /**
391      * <p>Overrides superclass version to make sure that the specified
392      * {@link javax.swing.table.TableModel} is compatible with JXTreeTable before
393      * invoking the inherited version.</p>
394      *
395      * <p>Because JXTreeTable internally adapts an
396      * {@link org.jdesktop.swing.treetable.TreeTableModel} to make it a compatible
397      * TableModel, <b>this method should never be called directly</b>. Use
398      * {@link #setTreeTableModel(org.jdesktop.swing.treetable.TreeTableModel) setTreeTableModel} instead.</p>
399      *
400      * <p>While it is possible to obtain a reference to this adapted
401      * version of the TableModel by calling {@link javax.swing.JTable#getModel()},
402      * any attempt to call setModel() with that adapter will fail because
403      * the adapter might have been bound to a different JXTreeTable instance. If
404      * you want to extract the underlying TreeTableModel, which, by the way,
405      * <em>can</em> be shared, use {@link #getTreeTableModel() getTreeTableModel}
406      * instead</p>.
407      *
408      * @param tableModel must be a TreeTableModelAdapter
409      * @throws IllegalArgumentException if the specified tableModel is not an
410      * instance of TreeTableModelAdapter
411      */

412     public final void setModel(TableModel JavaDoc tableModel) { // note final keyword
413
if (tableModel instanceof TreeTableModelAdapter) {
414             if (((TreeTableModelAdapter) tableModel).getTreeTable() == null) {
415                 // Passing the above test ensures that this method is being
416
// invoked either from JXTreeTable/JTable constructor or from
417
// setTreeTableModel(TreeTableModel)
418
super.setModel(tableModel); // invoke superclass version
419

420                 ((TreeTableModelAdapter) tableModel).bind(this); // permanently bound
421
// Once a TreeTableModelAdapter is bound to any JXTreeTable instance,
422
// invoking JXTreeTable.setModel() with that adapter will throw an
423
// IllegalArgumentException, because we really want to make sure
424
// that a TreeTableModelAdapter is NOT shared by another JXTreeTable.
425
}
426             else {
427                 throw new IllegalArgumentException JavaDoc("model already bound");
428             }
429         }
430         else {
431             throw new IllegalArgumentException JavaDoc("unsupported model type");
432         }
433     }
434
435     /**
436      * Throws UnsupportedOperationException because variable height rows are
437      * not supported.
438      *
439      * @param row ignored
440      * @param rowHeight ignored
441      * @throws UnsupportedOperationException because variable height rows are
442      * not supported
443      */

444     public final void setRowHeight(int row, int rowHeight) {
445         throw new UnsupportedOperationException JavaDoc("variable height rows not supported");
446     }
447
448     /**
449      * Sets the row height for this JXTreeTable. Reconciles semantic differences
450      * between JTable and JTree regarding row height.
451      *
452      * @param rowHeight height of a row
453      */

454     public void setRowHeight(int rowHeight) {
455         super.setRowHeight(rowHeight);
456         adjustTreeRowHeight(); // JTree doesn't have setRowMargin. So adjust.
457
}
458
459     /**
460      * <p>Sets the margin between columns.</p>
461      *
462      * <p>If you set the column margin to zero, make sure that you also set
463      * <code>showVerticalLines</code> to <code>false</code>.</p>
464      *
465      * @param columnMargin margin between columns; must be greater than or equal to zero.
466      * @see #setShowVerticalLines(boolean) setShowVerticalLines
467      */

468     public void setColumnMargin(int columnMargin) {
469         super.setColumnMargin(columnMargin);
470     }
471
472     /**
473      * <p>Overridden to ensure that private renderer state is kept in sync with the
474      * state of the component. Calls the inherited version after performing the
475      * necessary synchronization. If you override this method, make sure you call
476      * this version from your version of this method.</p>
477      *
478      * <p>If you set row margin to zero, make sure that you also set
479      * <code>showHorizontalLines</code> to <code>false</code>.</p>
480      *
481      * @param rowMargin margin or intercell spacing between rows
482      * @see #setShowHorizontalLines(boolean) setShowHorizontalLines
483      */

484     public void setRowMargin(int rowMargin) {
485         // No need to override setIntercellSpacing, because the change in
486
// rowMargin will be funneled through this method anyway.
487
super.setRowMargin(rowMargin);
488         adjustTreeRowHeight(); // JTree doesn't have setRowMargin. So adjust.
489
}
490
491     /**
492      * Reconciles semantic differences between JTable and JTree regarding
493      * row height.
494      */

495     private void adjustTreeRowHeight() {
496         final int treeRowHeight = rowHeight + (rowMargin << 1);
497         if (renderer != null && renderer.getRowHeight() != treeRowHeight) {
498             renderer.setRowHeight(treeRowHeight);
499         }
500     }
501
502     /**
503      * <p>Overridden to ensure that private renderer state is kept in sync with the
504      * state of the component. Calls the inherited version after performing the
505      * necessary synchronization. If you override this method, make sure you call
506      * this version from your version of this method.</p>
507      *
508      * <p>This version maps the selection mode used by the renderer to match the
509      * selection mode specified for the table. Specifically, the modes are mapped
510      * as follows:
511      * <pre>
512      * ListSelectionModel.SINGLE_INTERVAL_SELECTION: TreeSelectionModel.CONTIGUOUS_TREE_SELECTION;
513      * ListSelectionModel.MULTIPLE_INTERVAL_SELECTION: TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION;
514      * any other (default): TreeSelectionModel.SINGLE_TREE_SELECTION;
515      * </pre>
516      *
517      * {@inheritDoc}
518      *
519      * @param mode any of the table selection modes
520      */

521     public void setSelectionMode(int mode) {
522         if (renderer != null) {
523             switch (mode) {
524                 case ListSelectionModel.SINGLE_INTERVAL_SELECTION: {
525                     renderer.getSelectionModel().setSelectionMode(
526                         TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
527                     break;
528                 }
529                 case ListSelectionModel.MULTIPLE_INTERVAL_SELECTION: {
530                     renderer.getSelectionModel().setSelectionMode(
531                         TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
532                     break;
533                 }
534                 default: {
535                     renderer.getSelectionModel().setSelectionMode(
536                         TreeSelectionModel.SINGLE_TREE_SELECTION);
537                     break;
538                 }
539             }
540         }
541         super.setSelectionMode(mode);
542     }
543
544     /**
545      * Overrides superclass version to provide support for cell decorators.
546      *
547      * @param renderer the <code>TableCellRenderer</code> to prepare
548      * @param row the row of the cell to render, where 0 is the first row
549      * @param column the column of the cell to render, where 0 is the first column
550      * @return the <code>Component</code> used as a stamp to render the specified cell
551      */

552     public Component JavaDoc prepareRenderer(TableCellRenderer JavaDoc renderer, int row,
553         int column) {
554         // TOTAL HACK to fix icons that disappeared in a regression after M3!
555
if (isHierarchical(column)) {
556             if (collapsedIcon != null) {
557                 setCollapsedIcon(collapsedIcon);
558             }
559             if (expandedIcon != null) {
560                 setExpandedIcon(expandedIcon);
561             }
562             if (openIcon != null) {
563                 setOpenIcon(openIcon);
564             }
565             if (closedIcon != null) {
566                 setClosedIcon(closedIcon);
567             }
568             if (leafIcon != null) {
569                 setLeafIcon(leafIcon);
570             }
571         }
572         
573         Component JavaDoc component = super.prepareRenderer(renderer, row, column);
574         // MUST ALWAYS ACCESS dataAdapter through accessor method!!!
575
ComponentAdapter adapter = getComponentAdapter();
576         adapter.row = row;
577         adapter.column = column;
578         
579         return applyRenderer(component, //super.prepareRenderer(renderer, row, column),
580
adapter);
581     }
582
583     /**
584      * Performs necessary housekeeping before the renderer is actually applied.
585      *
586      * @param component
587      * @param adapter component data adapter
588      * @throws NullPointerException if the specified component or adapter is null
589      */

590     protected Component JavaDoc applyRenderer(Component JavaDoc component,
591         ComponentAdapter adapter) {
592         if (component == null) {
593             throw new IllegalArgumentException JavaDoc("null component");
594         }
595         if (adapter == null) {
596             throw new IllegalArgumentException JavaDoc("null component data adapter");
597         }
598
599         if (isHierarchical(adapter.column)) {
600             // After all decorators have been applied, make sure that relevant
601
// attributes of the table cell renderer are applied to the
602
// tree cell renderer before the hierarchical column is rendered!
603
TreeCellRenderer JavaDoc tcr = renderer.getCellRenderer();
604
605             if (tcr instanceof DefaultTreeCellRenderer JavaDoc) {
606                 DefaultTreeCellRenderer JavaDoc dtcr = ((DefaultTreeCellRenderer JavaDoc) tcr);
607                 if (adapter.isSelected()) {
608                     dtcr.setTextSelectionColor(component.getForeground());
609                     dtcr.setBackgroundSelectionColor(component.getBackground());
610                } else {
611                     dtcr.setTextNonSelectionColor(component.getForeground());
612                     dtcr.setBackgroundNonSelectionColor(component.getBackground());
613                 }
614             }
615         }
616         return component;
617     }
618
619     /**
620      * Sets the specified TreeCellRenderer as the Tree cell renderer.
621      *
622      * @param cellRenderer to use for rendering tree cells.
623      */

624     public void setCellRenderer(TreeCellRenderer JavaDoc cellRenderer) {
625         if (renderer != null) {
626             renderer.setCellRenderer(cellRenderer);
627         }
628     }
629
630
631     /**
632      * Sets the specified icon as the icon to use for rendering collapsed nodes.
633      *
634      * @param icon to use for rendering collapsed nodes
635      */

636     public void setCollapsedIcon(Icon JavaDoc icon) {
637         try {
638             ( (BasicTreeUI JavaDoc) (renderer.getUI())).setCollapsedIcon(icon);
639             // TOTAL HACK to fix icons that disappeared in a regression after M3!
640
collapsedIcon = icon;
641         }
642         catch (ClassCastException JavaDoc ex) {
643             /** @todo use logging apis instead */
644             System.err.println(ex);
645         }
646     }
647
648     /**
649      * Sets the specified icon as the icon to use for rendering expanded nodes.
650      *
651      * @param icon to use for rendering expanded nodes
652      */

653     public void setExpandedIcon(Icon JavaDoc icon) {
654         try {
655             ( (BasicTreeUI JavaDoc) (renderer.getUI())).setExpandedIcon(icon);
656             // TOTAL HACK to fix icons that disappeared in a regression after M3!
657
expandedIcon = icon;
658         }
659         catch (ClassCastException JavaDoc ex) {
660             /** @todo use logging apis instead */
661             System.err.println(ex);
662         }
663     }
664
665     /**
666      * Sets the specified icon as the icon to use for rendering open container nodes.
667      *
668      * @param icon to use for rendering open nodes
669      */

670     public void setOpenIcon(Icon JavaDoc icon) {
671         try {
672             ((DefaultTreeCellRenderer JavaDoc) renderer.getCellRenderer()).setOpenIcon(icon);
673             // TOTAL HACK to fix icons that disappeared in a regression after M3!
674
openIcon = icon;
675         }
676         catch (ClassCastException JavaDoc ex) {
677             /** @todo use logging apis instead */
678             System.err.println(ex);
679         }
680     }
681
682     /**
683      * Sets the specified icon as the icon to use for rendering closed container nodes.
684      *
685      * @param icon to use for rendering closed nodes
686      */

687     public void setClosedIcon(Icon JavaDoc icon) {
688         try {
689             ((DefaultTreeCellRenderer JavaDoc) renderer.getCellRenderer()).setClosedIcon(icon);
690             // TOTAL HACK to fix icons that disappeared in a regression after M3!
691
closedIcon = icon;
692         }
693         catch (ClassCastException JavaDoc ex) {
694             /** @todo use logging apis instead */
695             System.err.println(ex);
696         }
697     }
698
699     /**
700      * Sets the specified icon as the icon to use for rendering leaf nodes.
701      *
702      * @param icon to use for rendering leaf nodes
703      */

704     public void setLeafIcon(Icon JavaDoc icon) {
705         try {
706             ((DefaultTreeCellRenderer JavaDoc) renderer.getCellRenderer()).setLeafIcon(icon);
707             // TOTAL HACK to fix icons that disappeared in a regression after M3!
708
leafIcon = icon;
709         }
710         catch (ClassCastException JavaDoc ex) {
711             /** @todo use logging apis instead */
712             System.err.println(ex);
713         }
714     }
715
716     /**
717      * Overridden to ensure that private renderer state is kept in sync with the
718      * state of the component. Calls the inherited version after performing the
719      * necessary synchronization. If you override this method, make sure you call
720      * this version from your version of this method.
721      */

722     public void clearSelection() {
723         if (renderer != null) {
724             renderer.clearSelection();
725         }
726         super.clearSelection();
727     }
728
729     /**
730      * Collapses all nodes in the treetable.
731      */

732     public void collapseAll() {
733         renderer.collapseAll();
734     }
735
736     /**
737      * Expands all nodes in the treetable.
738      */

739     public void expandAll() {
740         renderer.expandAll();
741     }
742
743     /**
744      * Collapses the node at the specified path in the treetable.
745      *
746      * @param path path of the node to collapse
747      */

748     public void collapsePath(TreePath JavaDoc path) {
749         renderer.collapsePath(path);
750     }
751
752     /**
753      * Expands the the node at the specified path in the treetable.
754      *
755      * @param path path of the node to expand
756      */

757     public void expandPath(TreePath JavaDoc path) {
758         renderer.expandPath(path);
759     }
760
761     /**
762      * Collapses the row in the treetable. If the specified row index is
763      * not valid, this method will have no effect.
764      */

765     public void collapseRow(int row) {
766         renderer.collapseRow(row);
767     }
768
769     /**
770      * Expands the specified row in the treetable. If the specified row index is
771      * not valid, this method will have no effect.
772      */

773     public void expandRow(int row) {
774         renderer.expandRow(row);
775     }
776
777     /**
778      * Determines whether or not the root node from the TreeModel is visible.
779      *
780      * @param visible true, if the root node is visible; false, otherwise
781      */

782     public void setRootVisible(boolean visible) {
783         renderer.setRootVisible(visible);
784     }
785
786     /**
787      * Returns true if the root node of the tree is displayed.
788      *
789      * @return true if the root node of the tree is displayed
790      */

791     public boolean isRootVisible() {
792         return renderer.isRootVisible();
793     }
794
795     /**
796      * Returns true if the value identified by path is currently viewable, which
797      * means it is either the root or all of its parents are expanded. Otherwise,
798      * this method returns false.
799      *
800      * @return true, if the value identified by path is currently viewable;
801      * false, otherwise
802      */

803     public boolean isVisible(TreePath JavaDoc path) {
804         return renderer.isVisible(path);
805     }
806
807     /**
808      * Returns true if the node identified by path is currently expanded.
809      * Otherwise, this method returns false.
810      *
811      * @param path path
812      * @return true, if the value identified by path is currently expanded;
813      * false, otherwise
814      */

815     public boolean isExpanded(TreePath JavaDoc path) {
816         return renderer.isExpanded(path);
817     }
818
819     /**
820      * Returns true if the node at the specified display row is currently expanded.
821      * Otherwise, this method returns false.
822      *
823      * @param row row
824      * @return true, if the node at the specified display row is currently expanded.
825      * false, otherwise
826      */

827     public boolean isExpanded(int row) {
828         return renderer.isExpanded(row);
829     }
830
831     /**
832      * Returns true if the node identified by path is currently collapsed,
833      * this will return false if any of the values in path are currently not
834      * being displayed.
835      *
836      * @param path path
837      * @return true, if the value identified by path is currently collapsed;
838      * false, otherwise
839      */

840     public boolean isCollapsed(TreePath JavaDoc path) {
841         return renderer.isCollapsed(path);
842     }
843
844     /**
845      * Returns true if the node at the specified display row is collapsed.
846      *
847      * @param row row
848      * @return true, if the node at the specified display row is currently collapsed.
849      * false, otherwise
850      */

851     public boolean isCollapsed(int row) {
852         return renderer.isCollapsed(row);
853     }
854
855     
856     /**
857      * Returns an <code>Enumeration</code> of the descendants of the
858      * path <code>parent</code> that
859      * are currently expanded. If <code>parent</code> is not currently
860      * expanded, this will return <code>null</code>.
861      * If you expand/collapse nodes while
862      * iterating over the returned <code>Enumeration</code>
863      * this may not return all
864      * the expanded paths, or may return paths that are no longer expanded.
865      *
866      * @param parent the path which is to be examined
867      * @return an <code>Enumeration</code> of the descendents of
868      * <code>parent</code>, or <code>null</code> if
869      * <code>parent</code> is not currently expanded
870      */

871     
872     public Enumeration JavaDoc getExpandedDescendants(TreePath JavaDoc parent) {
873         return renderer.getExpandedDescendants(parent);
874     }
875
876     
877     /**
878      * Sets the value of the <code>expandsSelectedPaths</code> property for the tree
879      * part. This property specifies whether the selected paths should be expanded.
880      *
881      * @param expand true, if selected paths should be expanded; false, otherwise
882      */

883     public void setExpandsSelectedPaths(boolean expand) {
884         renderer.setExpandsSelectedPaths(expand);
885     }
886
887     /**
888      * Returns the value of the <code>expandsSelectedPaths</code> property.
889      *
890      * @return the value of the <code>expandsSelectedPaths</code> property
891      */

892     public boolean getExpandsSelectedPaths() {
893         return renderer.getExpandsSelectedPaths();
894     }
895
896     /**
897      * Returns the TreePath for a given x,y location.
898      *
899      * @param x x value
900      * @param y y value
901      *
902      * @return the <code>TreePath</code> for the givern location.
903      */

904      public TreePath JavaDoc getPathForLocation(int x, int y) {
905         int row = rowAtPoint(new Point JavaDoc(x,y));
906         if (row == -1) {
907           return null;
908         }
909         return renderer.getPathForRow(row);
910      }
911
912     /**
913      * Returns the TreePath for a given row.
914      *
915      * @param row
916      *
917      * @return the <code>TreePath</code> for the given row.
918      */

919      public TreePath JavaDoc getPathForRow(int row) {
920         return renderer.getPathForRow(row);
921      }
922      
923     /**
924      * Sets the value of the <code>scrollsOnExpand</code> property for the tree
925      * part. This property specifies whether the expanded paths should be scrolled
926      * into view. In a look and feel in which a tree might not need to scroll
927      * when expanded, this property may be ignored.
928      *
929      * @param scroll true, if expanded paths should be scrolled into view;
930      * false, otherwise
931      */

932     public void setScrollsOnExpand(boolean scroll) {
933         renderer.setScrollsOnExpand(scroll);
934     }
935
936     /**
937      * Returns the value of the <code>scrollsOnExpand</code> property.
938      *
939      * @return the value of the <code>scrollsOnExpand</code> property
940      */

941     public boolean getScrollsOnExpand() {
942         return renderer.getScrollsOnExpand();
943     }
944
945     /**
946      * Sets the value of the <code>showsRootHandles</code> property for the tree
947      * part. This property specifies whether the node handles should be displayed.
948      * If handles are not supported by a particular look and feel, this property
949      * may be ignored.
950      *
951      * @param visible true, if root handles should be shown; false, otherwise
952      */

953     public void setShowsRootHandles(boolean visible) {
954         renderer.setShowsRootHandles(visible);
955     }
956
957     /**
958      * Returns the value of the <code>showsRootHandles</code> property.
959      *
960      * @return the value of the <code>showsRootHandles</code> property
961      */

962     public boolean getShowsRootHandles() {
963         return renderer.getShowsRootHandles();
964     }
965
966     /**
967      * Returns the selection model for the tree portion of the this treetable.
968      *
969      * @return selection model for the tree portion of the this treetable
970      */

971     public TreeSelectionModel JavaDoc getTreeSelectionModel() {
972         return renderer.getSelectionModel(); // RG: Fix JDNC issue 41
973
}
974
975     /**
976      * Overriden to invoke supers implementation, and then,
977      * if the receiver is editing a Tree column, the editors bounds is
978      * reset. The reason we have to do this is because JTable doesn't
979      * think the table is being edited, as <code>getEditingRow</code> returns
980      * -1, and therefore doesn't automaticly resize the editor for us.
981      */

982     public void sizeColumnsToFit(int resizingColumn) {
983         /** @todo Review wrt doLayout() */
984         super.sizeColumnsToFit(resizingColumn);
985         // rg:changed
986
if (getEditingColumn() != -1 && isHierarchical(editingColumn)) {
987             Rectangle JavaDoc cellRect = getCellRect(realEditingRow(),
988                 getEditingColumn(), false);
989             Component JavaDoc component = getEditorComponent();
990             component.setBounds(cellRect);
991             component.validate();
992         }
993     }
994
995     /**
996      * Overridden to message super and forward the method to the tree.
997      * Since the tree is not actually in the component hieachy it will
998      * never receive this unless we forward it in this manner.
999      */

1000    public void updateUI() {
1001        super.updateUI();
1002        if (renderer != null) {
1003            // final int savedHeight = renderer.getRowHeight();
1004
renderer.updateUI();
1005            // renderer.setRowHeight(savedHeight);
1006

1007            // Do this so that the editor is referencing the current renderer
1008
// from the tree. The renderer can potentially change each time
1009
// laf changes.
1010
setDefaultEditor(AbstractTreeTableModel.hierarchicalColumnClass,
1011                new TreeTableCellEditor(this, renderer));
1012
1013            if (getBackground() == null || getBackground()instanceof UIResource JavaDoc) {
1014                setBackground(renderer.getBackground());
1015            }
1016        }
1017    }
1018
1019    /**
1020     * Determines if the specified column contains hierarchical nodes.
1021     *
1022     * @param column zero-based index of the column
1023     * @return true if the class of objects in the specified column implement
1024     * the {@link javax.swing.tree.TreeNode} interface; false otherwise.
1025     */

1026    public boolean isHierarchical(int column) {
1027        return AbstractTreeTableModel.hierarchicalColumnClass.isAssignableFrom(
1028            getColumnClass(column));
1029    }
1030
1031    /**
1032     * Initializes this JXTreeTable and permanently binds the specified renderer
1033     * to it.
1034     *
1035     * @param renderer private tree/renderer permanently and exclusively bound
1036     * to this JXTreeTable.
1037     */

1038    private final void init(TreeTableCellRenderer renderer) {
1039        this.renderer = renderer;
1040        // Force the JTable and JTree to share their row selection models.
1041
ListToTreeSelectionModelWrapper selectionWrapper =
1042            new ListToTreeSelectionModelWrapper();
1043
1044        // JW: when would that happen?
1045
if (renderer != null) {
1046            renderer.bind(this); // IMPORTANT: link back!
1047
renderer.setSelectionModel(selectionWrapper);
1048        }
1049
1050        setSelectionModel(selectionWrapper.getListSelectionModel());
1051        setDefaultRenderer(AbstractTreeTableModel.hierarchicalColumnClass,
1052            renderer);
1053        
1054        // propagate the lineStyle property to the renderer
1055
PropertyChangeListener JavaDoc l = new PropertyChangeListener JavaDoc() {
1056
1057            public void propertyChange(PropertyChangeEvent JavaDoc evt) {
1058                JXTreeTable.this.renderer.putClientProperty(evt.getPropertyName(), evt.getNewValue());
1059                
1060            }
1061            
1062        };
1063        addPropertyChangeListener("JTree.lineStyle", l);
1064        
1065    }
1066
1067
1068    /**
1069     * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
1070     * to listen for changes in the ListSelectionModel it maintains. Once
1071     * a change in the ListSelectionModel happens, the paths are updated
1072     * in the DefaultTreeSelectionModel.
1073     */

1074    class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel JavaDoc {
1075        /** Set to true when we are updating the ListSelectionModel. */
1076        protected boolean updatingListSelectionModel;
1077
1078        public ListToTreeSelectionModelWrapper() {
1079            super();
1080            getListSelectionModel().addListSelectionListener
1081                (createListSelectionListener());
1082        }
1083
1084        /**
1085         * Returns the list selection model. ListToTreeSelectionModelWrapper
1086         * listens for changes to this model and updates the selected paths
1087         * accordingly.
1088         */

1089        ListSelectionModel JavaDoc getListSelectionModel() {
1090            return listSelectionModel;
1091        }
1092
1093        /**
1094         * This is overridden to set <code>updatingListSelectionModel</code>
1095         * and message super. This is the only place DefaultTreeSelectionModel
1096         * alters the ListSelectionModel.
1097         */

1098        public void resetRowSelection() {
1099            if (!updatingListSelectionModel) {
1100                updatingListSelectionModel = true;
1101                try {
1102                    super.resetRowSelection();
1103                }
1104                finally {
1105                    updatingListSelectionModel = false;
1106                }
1107            }
1108            // Notice how we don't message super if
1109
// updatingListSelectionModel is true. If
1110
// updatingListSelectionModel is true, it implies the
1111
// ListSelectionModel has already been updated and the
1112
// paths are the only thing that needs to be updated.
1113
}
1114
1115        /**
1116         * Creates and returns an instance of ListSelectionHandler.
1117         */

1118        protected ListSelectionListener JavaDoc createListSelectionListener() {
1119            return new ListSelectionHandler();
1120        }
1121
1122        /**
1123         * If <code>updatingListSelectionModel</code> is false, this will
1124         * reset the selected paths from the selected rows in the list
1125         * selection model.
1126         */

1127        protected void updateSelectedPathsFromSelectedRows() {
1128            if (!updatingListSelectionModel) {
1129                updatingListSelectionModel = true;
1130                try {
1131                    // This is way expensive, ListSelectionModel needs an
1132
// enumerator for iterating.
1133
int min = listSelectionModel.getMinSelectionIndex();
1134                    int max = listSelectionModel.getMaxSelectionIndex();
1135
1136                    clearSelection();
1137                    if (min != -1 && max != -1) {
1138                        for (int counter = min; counter <= max; counter++) {
1139                            if (listSelectionModel.isSelectedIndex(counter)) {
1140                                TreePath JavaDoc selPath = renderer.getPathForRow(
1141                                    counter);
1142
1143                                if (selPath != null) {
1144                                    addSelectionPath(selPath);
1145                                }
1146                            }
1147                        }
1148                    }
1149                }
1150                finally {
1151                    updatingListSelectionModel = false;
1152                }
1153            }
1154        }
1155
1156        /**
1157         * Class responsible for calling updateSelectedPathsFromSelectedRows
1158         * when the selection of the list changse.
1159         */

1160        class ListSelectionHandler implements ListSelectionListener JavaDoc {
1161            public void valueChanged(ListSelectionEvent JavaDoc e) {
1162                updateSelectedPathsFromSelectedRows();
1163            }
1164        }
1165    }
1166
1167    private static class TreeTableModelAdapter extends AbstractTableModel JavaDoc {
1168        /**
1169         * Maintains a TreeTableModel and a JTree as purely implementation details.
1170         * Developers can plug in any type of custom TreeTableModel through a
1171         * JXTreeTable constructor or through setTreeTableModel().
1172         *
1173         * @param model Underlying data model for the JXTreeTable that will ultimately
1174         * be bound to this TreeTableModelAdapter
1175         * @param tree TreeTableCellRenderer instantiated with the same model as
1176         * specified by the model parameter of this constructor
1177         * @throws IllegalArgumentException if a null model argument is passed
1178         * @throws IllegalArgumentException if a null tree argument is passed
1179         */

1180        TreeTableModelAdapter(TreeTableModel model, JTree JavaDoc tree) {
1181            assert model != null;
1182            assert tree != null;
1183
1184            this.tree = tree; // need tree to implement getRowCount()
1185
this.model = model;
1186
1187            // Install a TreeModelListener that can update the table when
1188
// tree changes. We use delayedFireTableDataChanged as we can
1189
// not be guaranteed the tree will have finished processing
1190
// the event before us.
1191
model.addTreeModelListener(new TreeModelListener JavaDoc() {
1192                public void treeNodesChanged(TreeModelEvent JavaDoc e) {
1193                    delayedFireTableDataChanged(e, 0);
1194                }
1195
1196                public void treeNodesInserted(TreeModelEvent JavaDoc e) {
1197                    delayedFireTableDataChanged(e, 1);
1198                }
1199
1200                public void treeNodesRemoved(TreeModelEvent JavaDoc e) {
1201                    delayedFireTableDataChanged(e, 2);
1202                }
1203
1204                public void treeStructureChanged(TreeModelEvent JavaDoc e) {
1205                    delayedFireTableDataChanged();
1206                }
1207            });
1208
1209            tree.addTreeExpansionListener(new TreeExpansionListener JavaDoc() {
1210                // Don't use fireTableRowsInserted() here; the selection model
1211
// would get updated twice.
1212
public void treeExpanded(TreeExpansionEvent JavaDoc event) {
1213                    fireTableDataChanged();
1214                }
1215
1216                public void treeCollapsed(TreeExpansionEvent JavaDoc event) {
1217                    fireTableDataChanged();
1218                }
1219            });
1220        }
1221
1222        /**
1223         * Returns the real TreeTableModel that is wrapped by this TreeTableModelAdapter.
1224         *
1225         * @return the real TreeTableModel that is wrapped by this TreeTableModelAdapter
1226         */

1227        public TreeTableModel getTreeTableModel() {
1228            return model;
1229        }
1230
1231        /**
1232         * Returns the JXTreeTable instance to which this TreeTableModelAdapter is
1233         * permanently and exclusively bound. For use by
1234         * {@link org.jdesktopx.swing.JXTreeTable#setModel(javax.swing.table.TableModel)}.
1235         *
1236         * @return JXTreeTable to which this TreeTableModelAdapter is permanently bound
1237         */

1238        protected JXTreeTable getTreeTable() {
1239            return treeTable;
1240        }
1241
1242        /**
1243         * Immutably binds this TreeTableModelAdapter to the specified JXTreeTable.
1244         *
1245         * @param treeTable the JXTreeTable instance that this adapter is bound to.
1246         */

1247        protected final void bind(JXTreeTable treeTable) {
1248            // Suppress potentially subversive invocation!
1249
// Prevent clearing out the deck for possible hijack attempt later!
1250
if (treeTable == null) {
1251                throw new IllegalArgumentException JavaDoc("null treeTable");
1252            }
1253
1254            if (this.treeTable == null) {
1255                this.treeTable = treeTable;
1256            }
1257            else {
1258                throw new IllegalArgumentException JavaDoc("adapter already bound");
1259            }
1260        }
1261
1262        // Wrappers, implementing TableModel interface.
1263
// TableModelListener management provided by AbstractTableModel superclass.
1264

1265        public Class JavaDoc getColumnClass(int column) {
1266            return model.getColumnClass(column);
1267        }
1268
1269        /**
1270         *
1271         * @return
1272         */

1273        public int getColumnCount() {
1274            return model.getColumnCount();
1275        }
1276
1277        public String JavaDoc getColumnName(int column) {
1278            return model.getColumnName(column);
1279        }
1280
1281        public int getRowCount() {
1282            return tree.getRowCount();
1283        }
1284
1285        public Object JavaDoc getValueAt(int row, int column) {
1286            return model.getValueAt(nodeForRow(row), column);
1287        }
1288
1289        public boolean isCellEditable(int row, int column) {
1290            return model.isCellEditable(nodeForRow(row), column);
1291        }
1292
1293        public void setValueAt(Object JavaDoc value, int row, int column) {
1294            model.setValueAt(value, nodeForRow(row), column);
1295        }
1296
1297        protected Object JavaDoc nodeForRow(int row) {
1298            return tree.getPathForRow(row).getLastPathComponent();
1299        }
1300
1301        /**
1302         * Invokes fireTableDataChanged after all the pending events have been
1303         * processed. SwingUtilities.invokeLater is used to handle this.
1304         */

1305        private void delayedFireTableDataChanged() {
1306            SwingUtilities.invokeLater(new Runnable JavaDoc() {
1307                public void run() {
1308                    fireTableDataChanged();
1309                }
1310            });
1311        }
1312
1313        /**
1314         * Invokes fireTableDataChanged after all the pending events have been
1315         * processed. SwingUtilities.invokeLater is used to handle this.
1316         */

1317        private void delayedFireTableDataChanged(final TreeModelEvent JavaDoc tme, final int typeChange) {
1318            SwingUtilities.invokeLater(new Runnable JavaDoc() {
1319                public void run() {
1320                    int indices[] = tme.getChildIndices();
1321                    TreePath JavaDoc path = tme.getTreePath();
1322                    if (indices != null) {
1323                        if (tree.isExpanded(path)) { // Dont bother to update if the parent
1324
// node is collapsed
1325
int startingRow = tree.getRowForPath(path)+1;
1326                            int min = Integer.MAX_VALUE;
1327                            int max = Integer.MIN_VALUE;
1328                            for (int i=0;i<indices.length;i++) {
1329                                if (indices[i] < min) {
1330                                    min = indices[i];
1331                                }
1332                                if (indices[i] > max) {
1333                                    max = indices[i];
1334                                }
1335                            }
1336                            switch (typeChange) {
1337                                case 0 :
1338                                    fireTableRowsUpdated(startingRow + min, startingRow+max);
1339                                break;
1340                                case 1:
1341                                    fireTableRowsInserted(startingRow + min, startingRow+max);
1342                                break;
1343                                case 2:
1344                                    fireTableRowsDeleted(startingRow + min, startingRow+max);
1345                                break;
1346                            }
1347                        }
1348                    }
1349                    else { // case where the event is fired to identify root.
1350
fireTableDataChanged();
1351                    }
1352                }
1353            });
1354        }
1355
1356
1357
1358        private TreeTableModel model; // immutable
1359
private final JTree JavaDoc tree; // immutable
1360
private JXTreeTable treeTable = null; // logically immutable
1361
}
1362
1363    static class TreeTableCellRenderer extends JXTree implements
1364        TableCellRenderer JavaDoc {
1365        // Force user to specify TreeTableModel instead of more general TreeModel
1366
public TreeTableCellRenderer(TreeTableModel model) {
1367            super(model);
1368            putClientProperty("JTree.lineStyle", "None");
1369            setRootVisible(false); // superclass default is "true"
1370
setShowsRootHandles(true); // superclass default is "false"
1371
/** @todo Support truncated text directly in DefaultTreeCellRenderer. */
1372            setCellRenderer(new ClippedTreeCellRenderer());
1373        }
1374
1375        /**
1376         * Immutably binds this TreeTableModelAdapter to the specified JXTreeTable.
1377         * For internal use by JXTreeTable only.
1378         *
1379         * @param treeTable the JXTreeTable instance that this renderer is bound to
1380         */

1381        public final void bind(JXTreeTable treeTable) {
1382            // Suppress potentially subversive invocation!
1383
// Prevent clearing out the deck for possible hijack attempt later!
1384
if (treeTable == null) {
1385                throw new IllegalArgumentException JavaDoc("null treeTable");
1386            }
1387
1388            if (this.treeTable == null) {
1389                this.treeTable = treeTable;
1390            }
1391            else {
1392                throw new IllegalArgumentException JavaDoc("renderer already bound");
1393            }
1394        }
1395
1396        /**
1397         * updateUI is overridden to set the colors of the Tree's renderer
1398         * to match that of the table.
1399         */

1400        public void updateUI() {
1401            super.updateUI();
1402            // Make the tree's cell renderer use the table's cell selection
1403
// colors.
1404
TreeCellRenderer JavaDoc tcr = getCellRenderer();
1405            if (tcr instanceof DefaultTreeCellRenderer JavaDoc) {
1406                DefaultTreeCellRenderer JavaDoc dtcr = ((DefaultTreeCellRenderer JavaDoc) tcr);
1407                // For 1.1 uncomment this, 1.2 has a bug that will cause an
1408
// exception to be thrown if the border selection color is null.
1409
dtcr.setBorderSelectionColor(null);
1410                dtcr.setTextSelectionColor(
1411                    UIManager.getColor("Table.selectionForeground"));
1412                dtcr.setBackgroundSelectionColor(
1413                    UIManager.getColor("Table.selectionBackground"));
1414            }
1415        }
1416
1417        /**
1418         * Sets the row height of the tree, and forwards the row height to
1419         * the table.
1420         */

1421        public void setRowHeight(int rowHeight) {
1422            super.setRowHeight(rowHeight);
1423            if (rowHeight > 0) {
1424                if (treeTable != null) {
1425                    // Reconcile semantic differences between JTable and JTree
1426
final int tableRowMargin = treeTable.getRowMargin();
1427                    assert tableRowMargin >= 0;
1428                    final int tableRowHeight = rowHeight - (tableRowMargin << 1);
1429                    if (treeTable.getRowHeight() != tableRowHeight) {
1430                        treeTable.setRowHeight(tableRowHeight);
1431                    }
1432                }
1433            }
1434        }
1435
1436        /**
1437         * This is overridden to set the height to match that of the JTable.
1438         */

1439        public void setBounds(int x, int y, int w, int h) {
1440            if (treeTable != null) {
1441                y = 0;
1442                // It is not enough to set the height to treeTable.getHeight()
1443
h = treeTable.getRowCount() * this.getRowHeight();
1444            }
1445            super.setBounds(x, y, w, h);
1446        }
1447
1448        /**
1449         * Sublcassed to translate the graphics such that the last visible
1450         * row will be drawn at 0,0.
1451         */

1452        public void paint(Graphics JavaDoc g) {
1453            int rowMargin = treeTable.getRowMargin();
1454            // MUST account for rowMargin for precise positioning.
1455
// Offset by (rowMargin * 3)/2, and remember to offset by an
1456
// additional pixel if rowMargin is odd!
1457
int margins = rowMargin + (rowMargin >> 1) + (rowMargin % 2);
1458            int translationOffset = margins + visibleRow * getRowHeight();
1459            g.translate(0, - translationOffset);
1460
1461            hierarchicalColumnWidth = getWidth();
1462            super.paint(g);
1463
1464            // Draw the Table border if we have focus.
1465
if (highlightBorder != null) {
1466// highlightBorder.paintBorder(this, g, 0,
1467
// visibleRow * getRowHeight(),
1468
// getWidth(), getRowHeight());
1469
// #170: border not drawn correctly
1470
// JW: position the border to be drawn in translated area
1471
// still not satifying in all cases...
1472
highlightBorder.paintBorder(this, g, 0,
1473                        translationOffset,
1474                        getWidth(),
1475                        // uhhh
1476
getRowHeight() + 1 - 2 * (margins) );
1477                
1478            
1479            }
1480        }
1481
1482        public Component JavaDoc getTableCellRendererComponent(JTable JavaDoc table,
1483            Object JavaDoc value,
1484            boolean isSelected, boolean hasFocus, int row, int column) {
1485            assert table == treeTable;
1486
1487            if (isSelected) {
1488                setBackground(table.getSelectionBackground());
1489                setForeground(table.getSelectionForeground());
1490            }
1491            else {
1492                setBackground(table.getBackground());
1493               setForeground(table.getForeground());
1494            }
1495
1496            highlightBorder = null;
1497            if (treeTable != null) {
1498                if (treeTable.realEditingRow() == row &&
1499                    treeTable.getEditingColumn() == column) {
1500                }
1501                else if (hasFocus) {
1502                    highlightBorder = UIManager.getBorder(
1503                        "Table.focusCellHighlightBorder");
1504                }
1505            }
1506
1507            
1508            visibleRow = row;
1509
1510            return this;
1511        }
1512
1513        private class ClippedTreeCellRenderer extends DefaultTreeCellRenderer JavaDoc {
1514            public void paint(Graphics JavaDoc g) {
1515                String JavaDoc fullText = super.getText();
1516                // getText() calls tree.convertValueToText();
1517
// tree.convertValueToText() should call treeModel.convertValueToText(), if possible
1518

1519                String JavaDoc shortText = SwingUtilities.layoutCompoundLabel(
1520                    this, g.getFontMetrics(), fullText, getIcon(),
1521                    getVerticalAlignment(), getHorizontalAlignment(),
1522                    getVerticalTextPosition(), getHorizontalTextPosition(),
1523                    getItemRect(itemRect), iconRect, textRect,
1524                    getIconTextGap());
1525
1526                /** @todo setText is more heavyweight than we want in this
1527                 * situation. Make JLabel.text protected instead of private.
1528                 */

1529
1530                setText(shortText); // temporarily truncate text
1531
super.paint(g);
1532                setText(fullText); // restore full text
1533
}
1534
1535            private Rectangle JavaDoc getItemRect(Rectangle JavaDoc itemRect) {
1536                getBounds(itemRect);
1537                itemRect.width = hierarchicalColumnWidth - itemRect.x;
1538                return itemRect;
1539            }
1540
1541            // Rectangles filled in by SwingUtilities.layoutCompoundLabel();
1542
private final Rectangle JavaDoc iconRect = new Rectangle JavaDoc();
1543            private final Rectangle JavaDoc textRect = new Rectangle JavaDoc();
1544            // Rectangle filled in by this.getItemRect();
1545
private final Rectangle JavaDoc itemRect = new Rectangle JavaDoc();
1546        }
1547
1548        /** Border to draw around the tree, if this is non-null, it will
1549         * be painted. */

1550        protected Border JavaDoc highlightBorder = null;
1551        protected JXTreeTable treeTable = null;
1552        protected int visibleRow = 0;
1553
1554        // A JXTreeTable may not have more than one hierarchical column
1555
private int hierarchicalColumnWidth = 0;
1556    }
1557
1558    /**
1559     * Returns the adapter that knows how to access the component data model.
1560     * The component data adapter is used by filters, sorters, and highlighters.
1561     *
1562     * @return the adapter that knows how to access the component data model
1563     */

1564    protected ComponentAdapter getComponentAdapter() {
1565        // MUST ALWAYS ACCESS dataAdapter through accessor method!!!
1566
return dataAdapter;
1567    }
1568
1569    private final ComponentAdapter dataAdapter = new TreeTableDataAdapter(this);
1570
1571    // Define defaultRowHeight to allow default document icon for leaf items to
1572
// draw properly
1573
private final static int defaultRowHeight = 20;
1574    private final static Dimension JavaDoc spacing = new Dimension JavaDoc(0, 2);
1575
1576    static class TreeTableDataAdapter extends JXTable.TableAdapter {
1577        private final JXTreeTable table;
1578
1579        /**
1580         * Constructs a <code>TreeTableDataAdapter</code> for the specified
1581         * target component.
1582         *
1583         * @param component the target component
1584         */

1585        public TreeTableDataAdapter(JXTreeTable component) {
1586            super(component);
1587            table = component;
1588        }
1589        public JXTreeTable getTreeTable() {
1590            return table;
1591        }
1592
1593        public boolean isExpanded() {
1594            return super.isExpanded(); /** @todo implement this method */
1595        }
1596
1597        public boolean hasFocus() {
1598            boolean focus = super.hasFocus(); /** @todo implement this method */
1599            return focus;
1600        }
1601
1602        public boolean isLeaf() {
1603            return super.isLeaf(); /** @todo implement this method */
1604        }
1605        /**
1606         *
1607         * @return true if the cell identified by this adapter displays hierarchical
1608         * nodes; false otherwise
1609         */

1610        public boolean isHierarchical() {
1611            return table.isHierarchical(column);
1612        }
1613    }
1614
1615}
1616
Popular Tags